/usr/share/pyshared/sonata/breadcrumbs.py is in sonata 1.6.2.1-6.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | #!/usr/bin/env python
import gtk
class CrumbButton(gtk.ToggleButton):
"""A ToggleButton tailored for use as a breadcrumb."""
def __init__(self, image, label):
gtk.ToggleButton.__init__(self)
self.set_properties(can_focus=False, relief=gtk.RELIEF_NONE)
# adapt gtk.Button internal layout code:
hbox = gtk.HBox(spacing=2)
align = gtk.Alignment(xalign=0.5, yalign=0.5)
hbox.pack_start(image, False, False)
hbox.pack_start(label, True, True)
align.add(hbox)
self.add(align)
class CrumbBox(gtk.Box):
"""A box layout similar to gtk.HBox, but specifically for breadcrumbs.
* Crumbs in the middle are replaced with an ellipsis if necessary.
* The root, parent, and current element are always kept visible.
* The root and parent are put in a condensed form if necessary.
* The current element is truncated if necessary.
"""
__gtype_name__ = 'CrumbBox'
def __init__(self, *args, **kwargs):
gtk.Box.__init__(self, *args, **kwargs)
# FIXME i can't get an internal child ellipsis to render...
# gtk.widget_push_composite_child()
# self.ellipsis = gtk.Label("...")
# self.ellipsis.props.visible = True
# gtk.widget_pop_composite_child()
# self.ellipsis.set_parent(self)
def do_size_request(self, requisition):
"""This gets called to determine the size we request"""
# ellipsis_req = self.ellipsis.size_request()
reqs = [w.size_request() for w in self]
# This would request "natural" size:
# pad = 0 if not reqs else (len(reqs)-1)*self.props.spacing
# requisition.width = sum( [r[0] for r in reqs]) + pad
# requisition.height = max([0]+[r[1] for r in reqs])
# return
# Request "minimum" size:
height = max([0]+[r[1] for r in reqs])
if len(reqs) == 0: # empty
width = 0
elif len(reqs) < 3: # current crumb
width = height
elif len(reqs) == 3: # parent and current
width = height + height + self.props.spacing
elif len(reqs) == 4: # root, parent and current
width = height + height + height + 2*self.props.spacing
elif len(reqs) > 4: # root, ellipsis, parent, current
pad = 3*self.props.spacing
width = height + reqs[1][0] + height + height + pad
requisition.width = width
requisition.height = height
def _req_sum(self, reqs):
pad = 0 if not reqs else (len(reqs)-1)*self.props.spacing
return pad+sum([req[0] for req in reqs])
def _condense(self, req, w):
# FIXME show and hide cause a fight in an infinite loop
# try:
# w.get_child().get_child().get_children()[1].hide()
# except (AttributeError, IndexError):
# pass
wr, hr = req
return (hr, hr) # XXX simplistic: set square size for now
def _uncondense(self, w):
# try:
# w.get_child().get_child().get_children()[1].show()
# except (AttributeError, IndexError):
pass
def _truncate(self, req, amount):
wr, hr = req
return (wr-amount, hr) # XXX this can be less than hr, even <0
def do_size_allocate(self, allocation):
"""This gets called to layout our child widgets"""
x0, y0 = allocation.x, allocation.y
w0, h0 = allocation.width, allocation.height
crumbs = self.get_children()
if len(crumbs) < 2:
return
# FIXME:
self.ellipsis = crumbs.pop(1)
hidden = [self.ellipsis]
# Undo any earlier condensing
if len(crumbs) > 0:
self._uncondense(crumbs[0])
if len(crumbs) > 1:
self._uncondense(crumbs[-2])
reqs = [w.get_child_requisition() for w in crumbs]
# If necessary, condense the root crumb
if self._req_sum(reqs) > w0 and len(crumbs) > 2:
reqs[0] = self._condense(reqs[0], crumbs[0])
# If necessary, replace an increasing amount of the
# crumbs after the root with the ellipsis
while self._req_sum(reqs) > w0:
if self.ellipsis in hidden and len(crumbs) > 3:
hidden = [crumbs.pop(1)]
reqs.pop(1)
crumbs.insert(1, self.ellipsis)
req = self.ellipsis.get_child_requisition()
reqs.insert(1, req)
elif self.ellipsis in crumbs and len(crumbs) > 4:
hidden.append(crumbs.pop(2))
reqs.pop(2)
else:
break # don't remove the parent
# If necessary, condense the parent crumb
if self._req_sum(reqs) > w0 and len(crumbs) > 1:
reqs[-2] = self._condense(reqs[-2], crumbs[-2])
# If necessary, truncate the current crumb
if self._req_sum(reqs) > w0:
reqs[-1] = self._truncate(reqs[-1],
self._req_sum(reqs)-w0)
# Now we are at minimum width
x = 0
for w, req in zip(crumbs, reqs):
wr, _hr = req
w.size_allocate(gtk.gdk.Rectangle(x0+x, y0, wr, h0))
w.show()
x += wr + self.props.spacing
for w in hidden:
w.size_allocate(gtk.gdk.Rectangle(-1, -1, 0, 0))
w.hide()
# def do_forall(self, internal, callback, data):
# callback(self.ellipsis, data)
# for w in self.get_children():
# callback(w, data)
# this file can be run as a simple test program:
if __name__ == '__main__':
w = gtk.Window()
crumbs = CrumbBox(spacing=2)
items = [
(gtk.STOCK_HARDDISK, "Filesystem"),
(None, None), # XXX for ellipsis
(gtk.STOCK_OPEN, "home"),
(gtk.STOCK_OPEN, "user"),
(gtk.STOCK_OPEN, "music"),
(gtk.STOCK_OPEN, "genre"),
(gtk.STOCK_OPEN, "artist"),
(gtk.STOCK_OPEN, "album"),
]
for stock, text in items:
if stock:
image = gtk.image_new_from_stock(stock,
gtk.ICON_SIZE_MENU)
crumbs.pack_start(CrumbButton(image, gtk.Label(text)))
else:
crumbs.pack_start(gtk.Label("..."))
w.add(crumbs)
w.connect('hide', gtk.main_quit)
w.show_all()
gtk.main()
|