More Cuemiac! * Apply the attached patch (beware it's a hack - but quite a small one) to clean cvs. * Put hbar.png and cuemiac.py in deskbar-applet/deskbar. * Edit MYPATH as noted in the header of cuemiac.py * Make sure that beagled is running * run "python cuemiac.py" * Be friggin' amazed! Screenie here: http://www.daimi.au.dk/~kamstrup/cuemiac-live.png Cheers Mikkel PS: Man I'm exited today! We get file monitoring and an almost-working cuemiac. PPS: Note how fast we get results from beagle... It's almost instantaneously. -- And I'm even on a slow computer right now. NIGEL: I'm using ubuntu-title (you can get it from the ubuntu repos)
Attachment:
hbar.png
Description: PNG image
diff -ruN ../deskbar-applet/deskbar/handlers/beagle-live.py deskbar/handlers/beagle-live.py
--- ../deskbar-applet/deskbar/handlers/beagle-live.py 2005-10-23 02:22:53.938601208 +0200
+++ deskbar/handlers/beagle-live.py 2005-10-23 02:27:34.519946360 +0200
@@ -4,7 +4,7 @@
from gettext import gettext as _
from os.path import exists
-MAX_RESULTS = 2 # per handler
+MAX_RESULTS = 8 # per handler
HANDLERS = {
"BeagleLiveHandler" : {
@@ -103,6 +103,16 @@
self.__result = result
+ 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 "Chat Logs"
+ elif t == "Calendar": return "Calendar"
+
def get_name (self, text=None):
# We use the result dict itself to look up words
return self.__result
diff -ruN ../deskbar-applet/deskbar/handlers/google-live.py deskbar/handlers/google-live.py
--- ../deskbar-applet/deskbar/handlers/google-live.py 2005-10-21 01:21:55.012619472 +0200
+++ deskbar/handlers/google-live.py 2005-10-23 02:27:34.518946512 +0200
@@ -53,6 +53,9 @@
Match.__init__ (self, handler, "Google: "+name, icon)
self.__url = url
+ def get_category (self):
+ return "Online"
+
def get_verb(self):
return "%(name)s"
diff -ruN ../deskbar-applet/deskbar/handlers/volumes.py deskbar/handlers/volumes.py
--- ../deskbar-applet/deskbar/handlers/volumes.py 2005-10-20 23:39:07.000000000 +0200
+++ deskbar/handlers/volumes.py 2005-10-23 02:27:34.519946360 +0200
@@ -23,6 +23,9 @@
def __init__(self, backend, drive, icon=None):
deskbar.handler.Match.__init__(self, backend, drive.get_display_name(), icon)
self.__drive = drive
+
+ def get_category (self):
+ return "Volumes"
def action(self, text=None):
self._priority = self._priority+1
#
# Known issues
#
# - Expander should be indented. Know this; putting
# icons in a separate tree column and setting expander
# column to 1 (the hit titles) is NOT pretty.
#
# - Depends on a png-file to draw the underlines of
# the category labels. Is this good or bad? It allows
# alternative funkier lines.
#
# - No line breaking for long titles. Do we want this anyway?
#
# - Underlining of "more *" disappears on mouse over. Setting
# the "underlined" property of the cellrenderer looks bad,
# but fixes this...
#
# - More spacing between categories is desirable
#
# - Should reduce window size on row collapse. Should this be done in the
# cuemiac widget or by the window containing it?
#
# 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</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
}
}
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.set_property ("headers-visible", False)
self.set_property ("hover-selection", True)
self.set_reorderable(True)
self.connect ("button-press-event", self.on_click)
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", "")
return
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):
# We want to expand no matter where you click on an
# expandable row, not just when the expander is clicked.
model, iter = self.get_selection().get_selected()
match = model[iter][model.MATCHES]
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)
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 ()