""" Pathbar implementation in Python. All mentions of 'path' in this assume a filereader.Path() object, not a string. They are using GTK_ARROW_LEFT and GTK_ARROW_RIGHT for the arrow 'slider' buttons in nautilus-pathbar.c """ import os, gtk, pango, gobject import fsutil, mime from filereader import Path from helpers import Property def ignore_clicks(f): def func(self, *args): if self._ignore_clicks: return self._ignore_clicks = True f(self, *args) self._ignore_clicks = False return func def get_true_label_size(label): """Given a label, returns the maximum size request (x, y) for that label were it to be bolded. The nautilus pathbar way is to take the max of the original pixel size and the bold one; I just take the bold one. It looks more like Thunar.""" layout = label.get_layout() path = label.get_text() layout.set_markup('%s' % path) return layout.get_pixel_size() class PathBar(gtk.HBox): def __init__(self): gtk.HBox.__init__(self) self.set_spacing(3) self.__path = None self._ignore_clicks = False self.show() @Property def path(): def fget(self): return self.__path def fset(self, path): self.__rebuild(path) @ignore_clicks def __pathButtonClicked(self, button): for child in self.get_children(): if isinstance(child, PathButton): child.set_active(False) button.set_active() self.emit('directory-changed', button.get_path()) def __rebuild(self, path): # remove all existing buttons self.foreach(lambda child: child.destroy()) icon_size = gtk.icon_size_lookup(gtk.ICON_SIZE_BUTTON)[0] align = gtk.Alignment(1.0, 1.0, 1.0, 1.0) align.set_property('width-request', 16) self.pack_start(align, False, False, 0) align.show() self.__path = path larrow = ArrowButton(gtk.ARROW_LEFT) rarrow = ArrowButton(gtk.ARROW_RIGHT) self.pack_start(larrow, False, False, 0) self.reorder_child(larrow, 0) larrow.show() for _path in path.parents(): button = PathButton(_path) button.connect('clicked', lambda button: self.__pathButtonClicked(button)) self.pack_start(button, False, False, 0) self.reorder_child(button, 0) if _path == path: button.set_active(True) button.show() self.pack_start(rarrow, False, False, 0) self.reorder_child(rarrow, 0) rarrow.show() class ArrowButton(gtk.Button): def __init__(self, direction, shadow=gtk.SHADOW_OUT): gtk.Button.__init__(self) self.set_focus_on_click(False) arrow = gtk.Arrow(direction, shadow) self.add(arrow) self.__arrow = arrow def show(self): self.__arrow.show() gtk.Button.show(self) class PathButton(gtk.ToggleButton): def __init__(self, path): gtk.ToggleButton.__init__(self) self.set_focus_on_click(False) self.__path = path icon, label = self.__icon_label() # create a horizontal box to hold the label and possibly an icon hbox = gtk.HBox(False, 2) self.add(hbox) hbox.pack_start(icon, False, False, 0) hbox.pack_start(label, True, True, 0) self.__label, self.__icon, self.__hbox = label, icon, hbox def show(self): """Show/unshow self and all components.""" self.__label.show() self.__icon.show() self.__hbox.show() gtk.ToggleButton.show(self) def hide(self): self.__label.hide() self.__icon.hide() self.__hbox.hide() gtk.ToggleButton.hide(self) def set_active(self, active=True): """Take care of bolding or unbolding the text in the button.""" # if we are not changing state, don't do anything # TODO: profile this optimisation to see if it's worth it if self.get_active() == active: return if active: self.__label.set_text("%s" % self.__label_text()) self.__label.set_use_markup(True) else: self.__label.set_text(self.__label_text()) gtk.ToggleButton.set_active(self, active) def get_path(self): return self.__path def __label_text(self): if self.__path.path != '/': return self.__path.escapedName() else: return '' def __icon_label(self): """Create the appropriate icon & label for this button. @returns: gtk.Image, gtk.Label""" path = self.__path if path.icon: icon = gtk.image_new_from_icon_name(path.icon, gtk.ICON_SIZE_MENU) else: icon = gtk.Image() text = path.escapedName() if text == '/': return icon, gtk.Label('') label = gtk.Label(text) label.set_size_request(*get_true_label_size(label)) label.set_use_markup(True) return icon, label class FileView(gtk.TreeView): def __init__(self, showHidden=False): gtk.TreeView.__init__(self) self.__options = {'showHidden' : showHidden, } self.__initFileView() self.__initColumns() self.__initStore() self.__initSignals() self.__fileReader = None self.show() @Property def fileReader(): def fget(self): return self.__fileReader def fset(self, fr): self.__rebuild(fr) def __initFileView(self): """Set some default options we want on the view.""" # Allow multiple selection self.get_selection().set_mode(gtk.SELECTION_MULTIPLE) # Allow rubber banding (Gtk 2.10+) (this is really crappy) #if hasattr(self, 'set_rubber_banding'): # self.set_rubber_banding(True) def __initColumns(self): n = gtk.TreeViewColumn('Name') s = gtk.TreeViewColumn('Size', gtk.CellRendererText(), text=2) n.pack_start(gtk.CellRendererPixbuf(), False) n.pack_start(gtk.CellRendererText(), True) n.set_attributes(n.get_cell_renderers()[0], pixbuf=0) n.set_attributes(n.get_cell_renderers()[1], text=1) n.set_expand(True) n.resizeable = True n.set_property('sort-indicator', True) # align this to the right s.get_cell_renderers()[0].set_property('xalign', 1.0) s.set_alignment(1.0) s.set_expand(False) s.resizeable = False self.append_column(n) self.append_column(s) def __initStore(self): s = gtk.ListStore(gtk.gdk.Pixbuf, str, str, gobject.TYPE_OBJECT) self.set_model(s) def __initSignals(self): self.connect('row_activated', self._on_row_activated) def __rebuild(self, reader): store = self.get_model() store.clear() self.__fileReader = reader for entry in reader.directories + reader.files: if not entry.isHidden or self.__options['showHidden']: store.append((entry.icon, entry.name, entry.humanSize(), entry.path)) self.columns_autosize() # signal handlers def _on_row_activated(self, *args): #args = [treeview (self), position, column] view, position, column = args[0], args[1], args[2] position = position[0] model = self.get_model() iter = model.get_iter(position) val = lambda x: model.get_value(iter, x) # str, str, Path name, size, path = val(1), val(2), val(3) print name, size, path if path.isdir(): self.emit('directory-changed', path) gobject.type_register(FileView) gobject.type_register(PathBar) gobject.type_register(PathButton) gobject.signal_new('directory-changed', FileView, \ gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, \ [Path]) gobject.signal_new('directory-changed', PathBar, \ gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, \ [Path])