The Cuemiac is really comming together... I've attached the work so far. Please not the "Known issues" at the top of the file. Screenie is here: http://www.daimi.au.dk/~kamstrup/cuemiac-hsep.png Cheers Mikkel
# # Known issues # # - number of nested results not indicated # # - expander should expand on clicking anywhere in the row # # - expander should be indented # # - it's possible to highligt category separator rows # # - depends on a png-file to draw the underlines of # the category labels. # from os.path import join import cgi import sys import gtk, gobject import gnome import gnome.ui from gettext import gettext as _ icon_theme = gtk.icon_theme_get_default() factory = gnome.ui.ThumbnailFactory(16) def load_icon(icon_name): return icon_theme.load_icon(icon_name, 16, gtk.ICON_LOOKUP_USE_BUILTIN) class Match: def __init__(self, name, category, icon_name=None): self._priority = 0 self._name = name self._category = category if icon_name: self._icon = load_icon (icon_name) else: self._icon = None def get_category (self): return self._category def get_name(self, text=None): return {"name": self._name} def get_verb(self): return "%(name)s" def get_priority(self): return self._priority def get_icon(self): return self._icon def action(self, text=None): print "Activation:", self.get_verb() class NestedMatch (Match): def __init__(self, category_name): self.__nest_msg = _(category_name) Match.__init__ (self, None, category_name) def get_verb (self): return "<u><i>"+self.__nest_msg+"</i></u>" class SeparatorMatch (Match): def __init__(self): Match.__init__ (self, None, "") def get_verb (self): return "" class UnknownCategory (Exception): def __init__ (self, category_name, match): print "Unknown Category '%s' requested by %s" % (category_name, match.__class__) CATEGORIES = { "Files" : { "nest": "more files", "threshold": 3 }, "Actions" : { "nest": "more actions", "threshold": 1 }, "News" : { "nest": "more news", "threshold": 3 } } class CuemiacModel (gtk.TreeStore): # Column name MATCHES = 0 def __init__ (self, category_infos): """ category_infos is a dict containing (case-sensitive) category names, with corresponding category information dicts. The category information dicts must contain the follwing key-value pairs: "nest" : what string to display for nested results (str) "threshold" : how many results before we start nesting them (int) The categories supplied in this dict are the only ones that Cuemiac model will allow. Request for other categories raise a UnknownCategory exception. """ gtk.TreeStore.__init__ (self, gobject.TYPE_PYOBJECT) # Match object self.infos = category_infos self.categories = {} # a dict containing # {name : (name, gtk.TreeRowReference, gtk.TreeRowReference, count, threshold)) # where the tuple contains # (category_name, first_entry, nested_entry, total_num_hits, hits_before_hide) # for each category self.count = 0 def append (self, match): """ Appends the match to the category returned by match.get_category(). Automatically creates a new category for the match if necesary. """ if self.categories.has_key (match.get_category()): self.__append_to_category (match, self.categories [match.get_category()]) else: self.create_category_with_match (match) self.count = self.count + 1 def __append_to_category (self, match, category): cat_name, first, nest, count, threshold = category if count < threshold: pos = self.__get_row_ref_position (first) self.insert (None, pos+count, [match]) self.categories[cat_name] = (cat_name, first, nest, count + 1, threshold) elif count == threshold: # We need to nest the rest of the matches, # so append a NestedMatch to the list nmatch = NestedMatch (self.infos[cat_name]["nest"]) pos = self.__get_row_ref_position (first) iter = self.insert (None, pos+count, [nmatch]) # Create a ref to the nested match entry nest = gtk.TreeRowReference (self, self.get_path(iter)) # Now append the match nested in the NestedMatch gtk.TreeStore.append (self, iter, [match]) self.categories[cat_name] = (cat_name, first, nest, count + 1, threshold) else: # Append nested iter = self.get_iter ( nest.get_path() ) gtk.TreeStore.append (self, iter, [match]) # FIXME We should show a count in the nested match self.categories[cat_name] = (cat_name, first, nest, count + 1, threshold) def create_category_with_match (self, match): cat_name = match.get_category () if not self.infos.has_key (cat_name): raise UnknownCategory(cat_name, match) iter = gtk.TreeStore.append (self, None, [match]) gtk.TreeStore.append (self, None, [SeparatorMatch()]) first = gtk.TreeRowReference (self, self.get_path(iter)) # Add the new category to our category list self.categories [cat_name] = (cat_name, first, None, 1, self.infos[cat_name]["threshold"]) def clear (self): gtk.TreeStore.clear (self) self.categories = {} self.count = 0 def __get_row_ref_position (self, row_ref): return int (self.get_string_from_iter (self.get_iter(row_ref.get_path()))) def set_category_threshold (self, cat_name, threshold): cat_name, first, nest, count, old_thres = self.categories[cat_name] self.categories[cat_name] = (cat_name, first, nest, count, threshold) class CuemiacTreeView (gtk.TreeView): """ Shows a DeskbarCategoryModel. """ def __init__ (self, cuemiac_model): gtk.TreeView.__init__ (self, cuemiac_model) self.set_property ("headers-visible", False) # False self.set_property ("hover-selection", True) self.set_reorderable(True) hits = gtk.TreeViewColumn ("Hits") hit_icon = gtk.CellRendererPixbuf () hit_title = gtk.CellRendererText () hits.pack_start (hit_icon, False) hits.pack_start (hit_title) hits.set_cell_data_func(hit_icon, self.__get_match_icon_for_cell) hits.set_cell_data_func(hit_title, self.__get_match_title_for_cell) self.append_column (hits) def __get_match_icon_for_cell (self, column, cell, model, iter, data=None): icon = model[iter][model.MATCHES].get_icon() cell.set_property ("pixbuf", icon) def __get_match_title_for_cell (self, column, cell, model, iter, data=None): match = model[iter][model.MATCHES] t = "cuemiac" # Pass unescaped query to the matches verbs = {"text" : t} verbs.update(match.get_name(t)) # Escape the query now for display verbs["text"] = cgi.escape(verbs["text"]) cell.set_property ("markup", match.get_verb () % verbs) def clear (self): self.model.clear () class CuemiacWidget (gtk.HBox): """ The Cuemiac. Nothing more to say. """ def __init__ (self, cuemiac_model, cuemiac_treeview): gtk.HBox.__init__ (self) self.layout = gtk.Fixed () self.layout.set_border_width (6) self.pack_start(self.layout, expand=False) self.model = cuemiac_model self.view = cuemiac_treeview self.cat_labels = {} self.pack_start(self.view, expand=True) self.view.connect_after ("size-allocate", self.align_category_labels) def append (self, match): """ Automagically append a match or list of matches to correct category(s), or create a new one(s) if needed. """ if type (match) == list: for hit in match: self.__append (hit) else: self.__append (match) def __append (self, match): """ Appends a single match to the correct category, or creates a new category for it if needeed. """ self.model.append (match) def get_label_for_category (self, cat_name): # Check if we have a label for this category # if we have return it, otherwise create a new one if self.cat_labels.has_key (cat_name): return self.cat_labels [cat_name] else: vbox = gtk.VBox () hbox = gtk.HBox () sep = gtk.Image() sep.set_from_file ("hbar.png") label = gtk.Label () label.set_markup ("<b>"+cat_name+"</b>") hbox.pack_end (label, False) vbox.pack_start(hbox) vbox.pack_start (sep) self.cat_labels [cat_name] = vbox return vbox def align_category_labels (self, sender, allocation): if self.view.window is None: return column = self.view.get_column(0) for category in self.model.categories.itervalues (): cat_name, first, nest, count, threshold = category path = first.get_path() area = self.view.get_background_area(path, column) x,y = self.view.tree_to_widget_coords(area.x, area.y) label = self.get_label_for_category (cat_name) if label.parent: self.layout.move(label, 0, y - self.view.style_get_property("vertical-separator")) else: self.layout.put(label, 0, y - self.view.style_get_property("vertical-separator")) label.show_all() def clear (self): self.model.clear () if __name__ == "__main__": import random random.seed () print "Cuemiac test" m1 = Match ("Ground breaking application for the free desktop", "News", "stock_news") m2 = Match ("Steve Jobs put bounty on free software developers' heads", "News", "stock_news") m3 = Match ("cumemiac.py", "Files", "stock_new") m4 = Match ("Next Gen Usability.odt", "Files", "stock_new") m5 = Match ("Cuemiac-devel", "Files", "stock_new") m6 = Match ("Cuemiac.pyo", "Files", "stock_new") m7 = Match ("ChangeLog", "Files", "stock_new") m8 = Match ("Cuemiac Mockup.svg", "Files", "stock_new") m9 = Match ("Look up word: <i>cuemiac</i>", "Actions", "gdict") m10 = Match ("Search google for: <i>cuemiac</i>", "Actions", "gnome-globe") # Shufle the list a bit to check if we categories result properly... matches = [m5, m2, m3, m10, m1, m6, m7, m8, m9, m4] cmodel = CuemiacModel (CATEGORIES) cview = CuemiacTreeView (cmodel) cuemiac = CuemiacWidget (cmodel, cview) window = gtk.Window() window.connect ("destroy", gtk.main_quit) window.add (cuemiac) window.show_all() for m in matches: gobject.timeout_add (random.randint (0, 4000), lambda x: cuemiac.append (x), m) cuemiac.show_all() gtk.main ()
Attachment:
hbar.png
Description: PNG image