class hyper_link(clickable_eventbox): gproperty('normal-markup', str, '<span color="#0000c0">%s</span>') gproperty('active-markup', str, '<span color="#c00000">%s</span>') gproperty('hover-markup', str, '<u><span color="#0000c0">%s</span></u>') def __init__(self, text=''): self.__gproperties = {} self.__label = gtk.Label() clickable_eventbox.__init__(self, self.__label) self.set_border_width(1) self.add_events(gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK) self.connect('enter-notify-event', self.on_hover_changed, True) self.connect('leave-notify-event', self.on_hover_changed, False) self.connect('active', self.on_active_changed, True) self.connect('unactive', self.on_active_changed, False) self.__text = None self.__is_active = False self.__is_hover = False self.set_text(text) def do_set_property(self, prop, value): self.__gproperties[prop.name] = value def do_get_property(self, prop): return self.__gproperties.setdefault(prop.name, prop.default_value) def set_text(self, text): self.__text = text self.__update_look() def on_hover_changed(self, eb, event, hover): self.__is_hover = hover self.__update_look() def on_active_changed(self, eb, active): self.__is_active = active self.__update_look() def __update_look(self): if self.__is_active: state = 'active' elif self.__is_hover: state = 'hover' else: state = 'normal' markup_string = self.get_property('%s-markup' % state) self.__label.set_markup(markup_string % escape(self.__text))
class Tree(gtk.VBox): """A treeview control with niceness.""" #The fields for the model. FIELDS = (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT, gobject.TYPE_STRING) #The columns for the view. COLUMNS = [[gtk.CellRendererText, 'markup', 2]] #The signals for the widget. __gsignals__ = { 'clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, )), 'double-clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, )), 'right-clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)), 'middle-clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, )), 'new-item': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'delete-item': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'edit-item': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) } gproperty('has-edit-box', bool, default=True) gproperty('headers-visible', bool, default=True) gproperty('icons-visible', bool, default=False) gproperty('markup-format-string', str, default='%(key)s') def do_get_property(self, prop): return self.__properties.setdefault(prop.name, prop.default_value) def do_set_property(self, prop, value): self.__properties[prop.name] = value EDIT_BUTTONS = False EDIT_BOX = False HEADERS_VISIBLE = False SORT_BY = None SORT_LIST = None SORT_AVAILABLE = None SORT_CONTROLS = False def __init__(self): self.__init_model() self.__init_view() self.__init_signals() self.__properties = {} def __init_view(self): """Load the widgets.""" gtk.VBox.__init__(self) self.__sw = gtk.ScrolledWindow() self.pack_start(self.__sw) self.__sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.__view = gtk.TreeView(self.__model) self.__view.set_rules_hint(True) self.__view.set_enable_search(False) self.__view.set_model(self.__model) self.__sw.add(self.__view) self.__view.set_headers_visible(False) for column in self.__init_renderers(): self.__view.append_column(column) if self.SORT_CONTROLS == True: sb = gtk.Expander() self.pack_start(sb, expand=False) l = gtk.Label('Sort') sb.set_label_widget(l) hb = gtk.HBox() sb.add(hb) self.__sortcombo = gtk.combo_box_new_text() hb.pack_start(self.__sortcombo) if self.SORT_AVAILABLE is not None: self.sort_available = dict(self.SORT_AVAILABLE) for attr, val in self.SORT_AVAILABLE: self.__sortcombo.append_text(attr) self.__sortdir = gtk.ToggleToolButton( stock_id=gtk.STOCK_SORT_DESCENDING) hb.pack_start(self.__sortdir, expand=False) self.__sortdir.set_active(True) self.__sortcombo.set_active(0) self.__sortdir.connect('toggled', self.cb_sortdir_toggled) self.__sortcombo.connect('changed', self.cb_sortcombo_changed) self.__user_initiated_sort() if self.EDIT_BOX == True: self.__editbox = QuestionBox() self.pack_start(self.__editbox, expand=False) if self.EDIT_BUTTONS == True: self.__toolbar = toolbar.Toolbar() self.__toolbar.add_button('new', 'new', 'new', True) self.__toolbar.add_button('delete', 'delete', 'delete', True) self.__toolbar.add_button('edit', 'edit', 'Edit this item.', True) self.__toolbar.connect('clicked', self.cb_toolbar_clicked) self.pack_start(self.__toolbar, expand=False) self.__init_signals() if self.SORT_BY is not None: self.sort_by([self.SORT_BY]) if self.SORT_LIST is not None: self.sort_by(self.SORT_LIST) self.show_all() def __init_model(self): """Initialise and return the model for the data.""" self.__model = gtk.TreeStore(*self.FIELDS) return self.__model def __init_signals(self): def cb_toolbar_clicked(toolbar, action): if action == 'new': self.emit('new-item') elif action == 'delete': self.emit('delete-item') elif action == 'edit': self.emit('edit-item') self.__toolbar.emit_stop_by_name('clicked') if self.EDIT_BUTTONS == True: self.__toolbar.connect('clicked', cb_toolbar_clicked) def cb_cursor_changed(view): self.emit('clicked', self.selected) self.__view.emit_stop_by_name('cursor-changed') self.__view.connect('cursor-changed', cb_cursor_changed) def cb_row_activated(view, path, column): self.emit('double-clicked', self.selected) self.__view.emit_stop_by_name('row-activated') self.__view.connect_after('row-activated', cb_row_activated) def cb_button_press_event(source, event): if event.button == 3: pathinf = self.__view.get_path_at_pos(int(event.x), int(event.y)) self.__view.emit_stop_by_name('button-press-event') if pathinf is not None: path, col, cellx, celly = pathinf self.__view.grab_focus() self.__view.set_cursor(path, None, 0) self.emit('right-clicked', self.selected, event) else: self.emit('right-clicked', None, event) return True if event.button == 2: pathinf = self.__view.get_path_at_pos(int(event.x), int(event.y)) if pathinf is not None: path, col, cellx, celly = pathinf self.__view.grab_focus() self.__view.set_cursor(path, None, 0) self.__view.emit_stop_by_name('button-press-event') self.emit('middle-clicked', self.selected) return True self.__view.add_events(gtk.gdk.BUTTON_PRESS_MASK) self.__view.connect('button-press-event', cb_button_press_event) def __init_renderers(self): """Initialise the renderers.""" for renderer_type, attribute, field in self.COLUMNS: renderer = renderer_type() renderer.set_property('ypad', 1) column = gtk.TreeViewColumn(attribute, renderer, **{attribute: field}) column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) yield column def add_item(self, item, key=None, parent=None): """Add an item to the tree.""" if key is None: key = item.key else: item.key = key titem = TreeItem(key, item) row = [key, titem, self.__get_markup(item)] niter = self.model.append(parent, row) def reset(): self.model.set_value(niter, 2, self.get_markup(item)) titem.reset_markup = reset item.reset_markup = reset return niter def __get_markup(self, item): unmangled = {} markup_fmt = self.get_property('markup-format-string') for k in dir(item): if k in markup_fmt: unmangled[k] = getattr(item, k) markup_string = markup_fmt % unmangled return markup_string get_markup = __get_markup def add_items(self, items): """Add items to the tree.""" for item in items: self.add_item(item) def clear(self): """Clear the tree.""" self.__model.clear() def del_item(self): """Removes the currently selected item from the tree.""" selected_path = self.__get_selected_path() if selected_path: self.__model.emit('row-deleted', selected_path) def set_items(self, items): """Set the items in the tree from the item list.""" self.clear() self.add_items(items) def question(self, callback, prompt): self.__editbox.question(callback, prompt) def sort_by(self, attrnames, sortcolid=0, columnid=1, direction=1): def comparemethod(model, iter1, iter2): v1 = model.get_value(iter1, columnid) v2 = model.get_value(iter2, columnid) def cmpvs(attrname, v1, v2): attr1 = attr2 = None if v1 is not None: attr1 = getattr(v1.value, attrname, None) if v2 is not None: attr2 = getattr(v2.value, attrname, None) if attr1 is None and attr2 is None: return 0 elif attr1 is None: return 0 elif attr2 is None: return 0 else: return cmp(attr2, attr1) for attrname in attrnames + ['key']: compd = cmpvs(attrname, v1, v2) if compd != 0: return direction * compd return 0 self.__model.set_sort_column_id(sortcolid, gtk.SORT_DESCENDING) self.__model.set_sort_func(sortcolid, comparemethod) #self.__view.set_model(self.__model) def __get_model(self): """Return the Tree Model.""" return self.__model model = property(__get_model) def __get_view(self): return self.__view view = property(__get_view) def __get_selected(self, column=1): """Get the selected item.""" ite = self.__get_selected_iter() if ite: return self.__get(ite, column) selected = property(__get_selected) def get_selected_key(self): return self.__get_selected(0) selected_key = property(get_selected_key) def set_selected(self, key): """Set the selected item to the first item matching the key.""" key = str(key) for row in self.model: if row[0] == key: self.__view.set_cursor(row.path) return True return False def __get_selected_iter(self): """Get the selected Tree Model Iter.""" path = self.__get_selected_path() if path: return self.__model.get_iter(path) selected_iter = property(__get_selected_iter) def __get_selected_path(self): """Get the selected Tree View Path.""" return self.__view.get_cursor()[0] selected_path = property(__get_selected_path) def __get(self, niter, column): """Get a cell's vlue from the Tree Model.""" return self.__model.get_value(niter, column) get = __get def __set(self, niter, column, value): self.__model.set_value(niter, column, value) set = __set def cb_toolbar_clicked(self, toolbar, action): if action == 'new': self.emit('new-item') elif action == 'edit': self.emit('edit-item') elif action == 'delete': self.emit('delete-item') toolbar.emit_stop_by_name('clicked') def cb_sortcombo_changed(self, combo): self.__user_initiated_sort() def cb_sortdir_toggled(self, toggle): self.__user_initiated_sort() def __user_initiated_sort(self): text = self.__sortcombo.get_active_text() if self.__sortdir.get_active(): direction = 1 #gtk.SORT_DESCENDING else: direction = -1 #gtk.SORT_ASCENDING self.sort_by([self.sort_available[text]], direction=direction)