As promised; the latest Cuemiac-stuff before I enter radio silence. Attached is cuemiac_handler_categories.txt - methods to stick in the Matches of the handlers as descibed in the file. cuemiac.py - put in deskbar/ run with "python cuemiac.py" hbar.png - put in deskbar/ Remember to set MYPATH in cuemiac.py. Cheers Mikkel On Mon, 2005-10-24 at 17:45 +0200, Raphael Slinckx wrote: > Mikkel Kamstrup Erlandsen wrote: > > > As my masters has entered a critical phase, I'll have to devote all my > > attention to wrapping up the much awaited "Differential forms on > > Delta-Complexes" v1.0 in the upcomming time (awaited by supervisor > > atleast ;-P). > > > > Good luck :) > I won't be able either to do much hacking this week, since i have a > partial report to hand back this friday, and it's far from ready.. > > > This means that I won't be able to hack on the Cuemiac (also refereed > > to as "the C" in the inner circles), or any deskbar-stuff for that > > matter, until sometime after the 4th. of november. > > > > I won't hang out on IRC, but I will check my mail, and reply if > > there's anything important (such as a Roadmap to 1.0 *hint* *hint*[1]) > > to discuss. > > > > I'll post the latest Cuemiac stuff to the list when I get home. > > > Before doing any CVS things, i suggest someone tags the current CVS as > DESKBAR_APPLET_0_8_0 or whatever the convention is, so we can have > snapshots of the deskbars over time, and they are always handy in case > of problems.. > > I'll try to see if i manage to get a hackish version running with > correct focus management. > > Concerning the Roadmap, i don't have a clue on what to put in it, beside > the usual TODO/Planed features stuff, i suppose we can't commit to a > timeframe yet, unless gnome takes us in, in which case i guess they 'll > ask about one. At that moment we will have to really think about what > can be done, in what timeframe. > _______________________________________________ > deskbar-applet-list mailing list > deskbar-applet-list gnome org > http://mail.gnome.org/mailman/listinfo/deskbar-applet-list >
# # Known issues # # - Expander knob should be indented or completely removed. # Note: Adding a column for the icons and putting # the expander in the second column looks bad. # # - Depends on a png-file to draw the underlines of # the category labels. Maybe use cairo to do this instead. # # - No line breaking for long titles. Do we want this anyway? # should we just ellipsize? # # - Should reduce window size on row collapse. Should this be done in the # cuemiac widget or by the window containing it? # Maybe CuemiacWIdget should emit a signal when it collapses a row... # # - "hover-expand" does not work when moving the cursor with the keyboard. # # SET MYPATH TO YOUR SOURCE DIR - WITH TRAILING / # MYPATH = "/home/mikkel/Projects/deskbar-applet-devel/deskbar/" 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 Nest : """ A class used to handle nested results in the CuemiacModel """ def __init__(self, category_name): self.__nest_msg = category_name self.__num_children = 0 def get_name (self, text=None): return {"count" : self.__num_children} def get_verb (self): return self.__nest_msg def set_num_children (self, num): self.__num_children = num def inc_num_children (self): self.__num_children = self.__num_children + 1 class Separator : """ Just a dummy to stick in the TreeStore when we have a category separator """ pass class UnknownCategory (Exception): def __init__ (self, category_name, match): print "Unknown Category '%s' requested by %s" % (category_name, match.__class__) # See CuemiacModel for a description of this beast CATEGORIES = { "Files" : { "name": "Files", "nest": "<b>%(count)s</b> <i>more files</i>", "threshold": 3 }, "Actions" : { "name": "Actions", "nest": "<b>%(count)s</b> <i>more actions</i>", "threshold": 1 }, "News" : { "name": "News", "nest": "<b>%(count)s</b> <i>more news items</i>", "threshold": 3 }, "Contacts" : { "name": "Contacts", "nest": "<b>%(count)s</b> <i>more contacts</i>", "threshold": 3 }, "Emails" : { "name": "Emails", "nest": "<b>%(count)s</b> <i>more emails</i>", "threshold": 3 }, "Notes" : { "name": "Notes", "nest": "<b>%(count)s</b> <i>more notes</i>", "threshold": 3 }, "Volumes" : { "name": "Volumes", "nest": "<b>%(count)s</b> <i>more volumes</i>", "threshold": 3 }, "Online" : { "name": "Online", "nest": "<b>%(count)s</b> <i>more online hits</i>", "threshold": 2 }, "Calendar" : { "name": "Calendar", "nest": "<b>%(count)s</b> <i>more calendar items</i>", "threshold": 1 }, "Conversation" : { "name": "Conversation", "nest": "<b>%(count)s</b> <i>more conversations</i>", "threshold": 1 } } 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: "name" : The *display name* of the category "nest" : what string to display for nested results (str) can refer to %(count)s to insert the number of childs. "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. IMPORTANT: The key values of the category_infos dict is NOT necesarily the same as category_infos[cat_name]["name"]. This is for internatianalization reasons. Basically the keys of the dict are the only thing uniquely determine a category. """ gtk.TreeStore.__init__ (self, gobject.TYPE_PYOBJECT) # Match object self.infos = category_infos self.categories = {} # a dict containing # {name : (display_name, gtk.TreeRowReference, gtk.TreeRowReference, count, threshold)) # where the tuple contains # (displayed_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 Nest to the list nest_entry = Nest (self.infos[cat_name]["nest"]) pos = self.__get_row_ref_position (first) iter = self.insert (None, pos+count, [nest_entry]) # 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]) nest_entry.inc_num_children () self.categories[cat_name] = (cat_name, first, nest, count + 1, threshold) else: # Append nested, and increase the number of children in the # nested match iter = self.get_iter ( nest.get_path() ) gtk.TreeStore.append (self, iter, [match]) self[iter][self.MATCHES].inc_num_children () # 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): """ Creates a new category with the given match as first entry. """ 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, [Separator()]) first = gtk.TreeRowReference (self, self.get_path(iter)) # Add the new category to our category list self.categories [cat_name] = (self.infos[cat_name]["name"], first, None, 1, self.infos[cat_name]["threshold"]) def clear (self): """ Clears this model of data. """ 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): display_name, first, nest, count, old_thres = self.categories[cat_name] self.categories[cat_name] = (display_name, first, nest, count, threshold) class CuemiacTreeView (gtk.TreeView): """ Shows a DeskbarCategoryModel. Used internally in the CuemiacWidget. """ __gsignals__ = { "match-selected" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_PYOBJECT]), } def __init__ (self, cuemiac_model): gtk.TreeView.__init__ (self, cuemiac_model) icon = gtk.CellRendererPixbuf () hit_title = gtk.CellRendererText () hits = gtk.TreeViewColumn ("Hits") hits.pack_start (icon) hits.pack_start (hit_title) hits.set_cell_data_func(hit_title, self.__get_match_title_for_cell) hits.set_cell_data_func(icon, self.__get_match_icon_for_cell) #hit_title.set_property ("width-chars", 40) self.append_column (hits) #self.set_row_separator_func( # lambda model, iter: (model[iter][model.MATCHES].__class__ == Separator)) self.connect ("cursor-changed", self.__on_cursor_changed) self.set_property ("headers-visible", False) self.set_property ("hover-selection", True) self.set_property ("hover-expand", True) self.set_reorderable(True) self.connect ("button-press-event", self.__on_click) def __on_cursor_changed (self, view): model, iter = self.get_selection().get_selected() if model[iter][model.MATCHES].__class__ == Separator: self.get_selection().unselect_iter (iter) def __get_match_icon_for_cell (self, column, cell, model, iter, data=None): match = model[iter][model.MATCHES] if match.__class__ == Separator or match.__class__ == Nest: cell.set_property ("pixbuf", None) else: icon = match.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] if match.__class__ == Separator: cell.set_property ("markup", "") cell.set_property ("height", 20) return else: cell.set_property ("height", -1) 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 () def __on_click (self, widget, event): model, iter = self.get_selection().get_selected() match = model[iter][model.MATCHES] # # We initially expanded on a click anywhere # within the nested row. This was a mess. # #if match.__class__ == Nest: # path = model.get_path(iter) # if self.row_expanded (path): # self.collapse_row (path) # else: # self.expand_row (path, True) #else: # self.emit ("match-selected", match) self.emit ("match-selected", match) class CuemiacWidget (gtk.HBox): """ The Cuemiac. Nothing more to say. This is what you want. The rest of this file is just code. """ __gsignals__ = { "match-selected" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_PYOBJECT]), } 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 = {} # keys are category names and values are widgets self.pack_start(self.view, expand=True) self.view.connect_after ("size-allocate", self.align_category_labels) self.view.connect ("match-selected", lambda sender, match: self.emit ("match-selected", match)) 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, display_name): """ Returns a widget representing the category title string. BEWARE: This need not be a gtk.Label. It's just a widget. """ # 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 (MYPATH+"hbar.png") label = gtk.Label () label.set_markup ("<b>"+display_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): """ Callback to postion the category "labels". """ if self.view.window is None: return column = self.view.get_column(0) for cat_name in self.model.categories.iterkeys (): display_name, first, nest, count, threshold = self.model.categories[cat_name] 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, display_name) # Take the vertical spacing between rows into account 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): """ Use this to clear the entire view and model structure of the Cuemiac. """ self.model.clear () for label in self.cat_labels.itervalues (): self.layout.remove (label) if __name__ == "__main__": gtk.threads_init () # Google-live needs this modules = [] def on_module_loaded (loader, ctx): loader.initialize_module (ctx) modules.append (ctx) if ctx.module.is_async (): ctx.module.connect ("query-ready", lambda sender, match: cuemiac.append(match)) from module_list import ModuleLoader, ModuleList mloader = ModuleLoader (None) mlist = ModuleList () mloader.connect ("module-loaded", on_module_loaded) mloader.load (MYPATH+"handlers/beagle-live.py") mloader.load (MYPATH+"handlers/google-live.py") mloader.load (MYPATH+"handlers/volumes.py") def on_entry_changed (entry): cuemiac.clear() for ctx in modules: if ctx.module.is_async (): ctx.module.query_async (entry.get_text().strip()) else: cuemiac.append ( ctx.module.query (entry.get_text().strip()) ) vbox = gtk.VBox () entry = gtk.Entry() entry.connect ("changed", on_entry_changed) cmodel = CuemiacModel (CATEGORIES) cview = CuemiacTreeView (cmodel) cuemiac = CuemiacWidget (cmodel, cview) cuemiac.connect ("match-selected", lambda sender, match: click_cb(match)) vbox.pack_start (entry, False) vbox.pack_start (cuemiac, True) window = gtk.Window() window.connect ("destroy", gtk.main_quit) window.add (vbox) window.show_all() cuemiac.show_all() gtk.threads_enter () gtk.main () gtk.threads_leave ()
Attachment:
hbar.png
Description: PNG image
# # beagle-live.py # def get_category (self): t = self.__result["type"] if t == "MailMessage" : return "Emails" elif t == "Contact": return "Contacts" elif t == "File": return "Files" elif t == "FeedItem": return "News" elif t == "Note": return "Notes" elif t == "IMLog": return "Conversation" elif t == "Calendar": return "Calendar" # # google-live.py # def get_category (self): return "Online" # # volumes.py # def get_category (self): return "Volumes"