5
class CrumbButton(gtk.ToggleButton):
6
"""A ToggleButton tailored for use as a breadcrumb."""
7
def __init__(self, image, label):
8
gtk.ToggleButton.__init__(self)
10
self.set_properties(can_focus=False, relief=gtk.RELIEF_NONE)
12
# adapt gtk.Button internal layout code:
13
hbox = gtk.HBox(spacing=2)
14
align = gtk.Alignment(xalign=0.5, yalign=0.5)
15
hbox.pack_start(image, False, False)
16
hbox.pack_start(label, True, True)
21
class CrumbBox(gtk.Box):
22
"""A box layout similar to gtk.HBox, but specifically for breadcrumbs.
24
* Crumbs in the middle are replaced with an ellipsis if necessary.
25
* The root, parent, and current element are always kept visible.
26
* The root and parent are put in a condensed form if necessary.
27
* The current element is truncated if necessary.
29
__gtype_name__ = 'CrumbBox'
30
def __init__(self, *args, **kwargs):
31
gtk.Box.__init__(self, *args, **kwargs)
33
# FIXME i can't get an internal child ellipsis to render...
34
# gtk.widget_push_composite_child()
35
# self.ellipsis = gtk.Label("...")
36
# self.ellipsis.props.visible = True
37
# gtk.widget_pop_composite_child()
38
# self.ellipsis.set_parent(self)
40
def do_size_request(self, requisition):
41
"""This gets called to determine the size we request"""
42
# ellipsis_req = self.ellipsis.size_request()
43
reqs = [w.size_request() for w in self]
45
# This would request "natural" size:
46
# pad = 0 if not reqs else (len(reqs)-1)*self.props.spacing
47
# requisition.width = sum( [r[0] for r in reqs]) + pad
48
# requisition.height = max([0]+[r[1] for r in reqs])
51
# Request "minimum" size:
53
height = max([0]+[r[1] for r in reqs])
55
if len(reqs) == 0: # empty
57
elif len(reqs) < 3: # current crumb
59
elif len(reqs) == 3: # parent and current
60
width = height + height + self.props.spacing
61
elif len(reqs) == 4: # root, parent and current
62
width = height + height + height + 2*self.props.spacing
63
elif len(reqs) > 4: # root, ellipsis, parent, current
64
pad = 3*self.props.spacing
65
width = height + reqs[1][0] + height + height + pad
67
requisition.width = width
68
requisition.height = height
70
def _req_sum(self, reqs):
71
pad = 0 if not reqs else (len(reqs)-1)*self.props.spacing
72
return pad+sum([req[0] for req in reqs])
74
def _condense(self, req, w):
75
# FIXME show and hide cause a fight in an infinite loop
77
# w.get_child().get_child().get_children()[1].hide()
78
# except (AttributeError, IndexError):
81
return (hr, hr) # XXX simplistic: set square size for now
83
def _uncondense(self, w):
85
# w.get_child().get_child().get_children()[1].show()
86
# except (AttributeError, IndexError):
89
def _truncate(self, req, amount):
91
return (wr-amount, hr) # XXX this can be less than hr, even <0
93
def do_size_allocate(self, allocation):
94
"""This gets called to layout our child widgets"""
95
x0, y0 = allocation.x, allocation.y
96
w0, h0 = allocation.width, allocation.height
98
crumbs = self.get_children()
104
self.ellipsis = crumbs.pop(1)
105
hidden = [self.ellipsis]
107
# Undo any earlier condensing
109
self._uncondense(crumbs[0])
111
self._uncondense(crumbs[-2])
113
reqs = [w.get_child_requisition() for w in crumbs]
115
# If necessary, condense the root crumb
116
if self._req_sum(reqs) > w0 and len(crumbs) > 2:
117
reqs[0] = self._condense(reqs[0], crumbs[0])
119
# If necessary, replace an increasing amount of the
120
# crumbs after the root with the ellipsis
121
while self._req_sum(reqs) > w0:
122
if self.ellipsis in hidden and len(crumbs) > 3:
123
hidden = [crumbs.pop(1)]
125
crumbs.insert(1, self.ellipsis)
126
req = self.ellipsis.get_child_requisition()
128
elif self.ellipsis in crumbs and len(crumbs) > 4:
129
hidden.append(crumbs.pop(2))
132
break # don't remove the parent
134
# If necessary, condense the parent crumb
135
if self._req_sum(reqs) > w0 and len(crumbs) > 1:
136
reqs[-2] = self._condense(reqs[-2], crumbs[-2])
138
# If necessary, truncate the current crumb
139
if self._req_sum(reqs) > w0:
140
reqs[-1] = self._truncate(reqs[-1],
141
self._req_sum(reqs)-w0)
142
# Now we are at minimum width
145
for w, req in zip(crumbs, reqs):
147
w.size_allocate(gtk.gdk.Rectangle(x0+x, y0, wr, h0))
149
x += wr + self.props.spacing
152
w.size_allocate(gtk.gdk.Rectangle(-1, -1, 0, 0))
155
# def do_forall(self, internal, callback, data):
156
# callback(self.ellipsis, data)
157
# for w in self.get_children():
160
# this file can be run as a simple test program:
161
if __name__ == '__main__':
163
crumbs = CrumbBox(spacing=2)
166
(gtk.STOCK_HARDDISK, "Filesystem"),
167
(None, None), # XXX for ellipsis
168
(gtk.STOCK_OPEN, "home"),
169
(gtk.STOCK_OPEN, "user"),
170
(gtk.STOCK_OPEN, "music"),
171
(gtk.STOCK_OPEN, "genre"),
172
(gtk.STOCK_OPEN, "artist"),
173
(gtk.STOCK_OPEN, "album"),
175
for stock, text in items:
177
image = gtk.image_new_from_stock(stock,
179
crumbs.pack_start(CrumbButton(image, gtk.Label(text)))
181
crumbs.pack_start(gtk.Label("..."))
184
w.connect('hide', gtk.main_quit)