Re: [Deskbar] The eye of Sauron is moving



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"


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]