All pastes #28910 Raw Edit

Python

public python v1 · immutable
#28910 ·published 2005-11-16 14:47 UTC
rendered paste body
## 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.#   Possible Fix: Create a custom cellrenderer that renders#   icon+text and optionally separator lines, this way#   the expander knob should align up with the icons...## - The entry loose focus sometimes when the CuemiacWindow appears,#   really annoying!## - 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#   CuemiacWidget or by the CuemiacWindow 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.## - No Multiscreen logic yet.## - ** Generally focus issues all over the place! **from os.path import *MYPATH = abspath(dirname(__file__))import cgiimport sysimport gtk, gobjectimport gnomeimport gnome.ui, gnomeappletfrom gettext import gettext as _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 + 1class Separator : 	"""	Just a dummy to stick in the TreeStore when we have a category separator	"""	passclass 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 beastCATEGORIES = {	"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			},	"Google Search"	: {				"name": "Google Search",				"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			},	"Web Browser" : {		"name":"Web Browser",		"nest":"<b>%(count)s</b> <i>more items</i>",		"threshold":5,	},	"Programs" : {		"name":"Programs",		"nest":"<b>%(count)s</b> <i>more programs</i>",		"threshold":3,	},	#"" : {	#	"name":"",	#	"nest":"",	#	"threshold":5,	#},}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 internationalization reasons.		Basically the keys of the dict are the only thing that uniquely determines 		a category.		"""				gtk.TreeStore.__init__ (self, 					gobject.TYPE_PYOBJECT)	# Match object							self.infos = category_infos  # dict containing category descriptions						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):			print 'Unknown category:',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 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)	def __get_row_ref_position (self, row_ref):		return int (self.get_string_from_iter (self.get_iter(row_ref.get_path())))	def __calc_max_category_width (self):		max_width = 0		for cat_name in self.infos.iterkeys ():			cat_width = len(self.infos[cat_name]["name"])			if cat_width > max_width:				max_width = cat_width		self.max_category_width = max_width					def get_max_category_width (self):		return self.max_category_widthclass 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)
		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.Layout () # Place to draw category names on		self.layout.set_border_width (6)		self.layout.set_property ("width", 100)		self.pack_start(self.layout, expand=True)				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))		self.layout.connect ("expose-event", self.layout_underline_categories)			self.__init_category_labels ()		self.max_category_label_width = self.__calc_max_category_label_width ()		def __init_category_labels (self):		"""		Creates gtk.Labels for all categories in allowed by the model.		Each label is realize()'ed so that we can calculate diemsnions later.		Called by the constructor.		"""		for cat_name in self.model.infos.iterkeys():			label = gtk.Label ()			label.set_markup ("<b>"+self.model.infos[cat_name]["name"]+"</b>")			label.realize ()			self.cat_labels [cat_name] = label				def __calc_max_category_label_width (self):		"""		Returns the max width in pixels of the category labels.		"""		max_width = 0		for cat_label in self.cat_labels.itervalues ():			w, h = cat_label.get_layout().get_pixel_size()			if w > max_width:				max_width = w		return max_width		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 gtk.Label representing the category title string.				HINT: You can obtain the dimensions of a realized label (like these)		with			w, h = label.get_layout().get_pixel_size()					The normal get_allocation() and/or get_size_request() will not necesarily		work unless the label has actually benn drawn on screen.		"""		return self.cat_labels [cat_name]				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)		pad = self.layout.get_border_width ()		width = self.layout.get_allocation().width - pad				rect = self.view.get_allocation ()		self.layout.set_size_request (self.max_category_label_width + 4*pad, rect.height)				self.layout.bin_window.clear ()		cairo = self.layout.bin_window.cairo_create ()		cairo.set_source_rgb (0,0,0)		cairo.set_line_width (0.5)				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)						if not label.parent:				self.layout.put(label, 0,						y + self.view.style_get_property("vertical-separator"))				label.show ()			# Calculate dimensions from Pango Layout.			# Since we might not be drawn on screen yet,			# normal gtk ops might not work...			w, h = label.get_layout().get_pixel_size()			self.layout.move(label, 				width - w,				y + self.view.style_get_property("vertical-separator"))						y = y + area.height - 2*self.view.style_get_property("vertical-separator")			cairo.move_to (pad, y)			cairo.line_to (width, y)				cairo.stroke ()		def layout_underline_categories (self, view, event):		# We have to do this in response to an expose event.		# Doing it in align_category_labels does *not* work		# since it triggers an expose event on self.layout,		# which will overwrite any drawing.		width = self.layout.get_allocation().width		pad = self.layout.get_border_width ()		column = self.view.get_column(0)		self.layout.bin_window.clear ()		cairo = self.layout.bin_window.cairo_create ()		cairo.set_source_rgb (0,0,0)		cairo.set_line_width (0.5) # FIXME This does not seems to have any effect				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)			y = y + area.height - 2*self.view.style_get_property("vertical-separator")			cairo.move_to (pad, y)			cairo.line_to (width, y)		cairo.stroke ()		return False					def clear (self):		"""		Use this to clear the entire view and model structure of the Cuemiac.		"""		self.model.clear ()		if self.layout.window:			for label in self.cat_labels.itervalues ():				self.layout.remove (label)			self.layout.bin_window.clear ()class CuemiacWindow (gtk.Window):	"""	Borderless window aligning itself to a given widget	"""	def __init__(self, widgetToAlignWith, alignment):		"""		alignment should be one of			gnomeapplet.ORIENT_{DOWN,UP,LEFT,RIGHT}				The window will be placed accordingly relative to the given widget		upon invoking .show().		"""		gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)		self.set_decorated(False)				self.widgetToAlignWith = widgetToAlignWith		self.alignment = alignment		self.__is_shown = False	def show (self):		"""		Calculates the position and shows the window		"""		# Get our own dimensions & position		self.realize()		gtk.gdk.flush()		window_width  = (self.window.get_geometry())[2]	   	window_height = (self.window.get_geometry())[3]		# Skip the taskbar, and the pager, stick and stay on top		self.stick()		self.set_keep_above(True)		self.set_skip_pager_hint(True)		self.set_skip_taskbar_hint(True)		self.set_type_hint (gtk.gdk.WINDOW_TYPE_HINT_DOCK)		# Get the dimensions/position of the widgetToAlignWith		self.widgetToAlignWith.realize()		(x, y) = self.widgetToAlignWith.window.get_origin()		(w, h) = self.get_size()		(w, h) = self.size_request()		target_w = self.widgetToAlignWith.allocation.width		target_h = self.widgetToAlignWith.allocation.height		screen = self.get_screen()		found_monitor = False		n = screen.get_n_monitors()		for i in range(0, n):				monitor = screen.get_monitor_geometry(i)				if (x >= monitor.x and x <= monitor.x + monitor.width and \					y >= monitor.y and y <= monitor.y + monitor.height):						found_monitor = True						break				if not found_monitor:				monitor = gtk.gdk.Rectangle(0, 0, screen.get_width(), screen.get_width())				self.alignment		if self.alignment == gnomeapplet.ORIENT_RIGHT:				x += target_w				if ((y + h) > monitor.y + monitor.height):						y -= (y + h) - (monitor.y + monitor.height)								if ((y + h) > (monitor.height / 2)):						gravity = gtk.gdk.GRAVITY_SOUTH_WEST					else:						gravity = gtk.gdk.GRAVITY_NORTH_WEST		elif self.alignment == gnomeapplet.ORIENT_LEFT:				x -= w				if ((y + h) > monitor.y + monitor.height):						y -= (y + h) - (monitor.y + monitor.height)								if ((y + h) > (monitor.height / 2)):						gravity = gtk.gdk.GRAVITY_SOUTH_EAST				else:						gravity = gtk.gdk.GRAVITY_NORTH_EAST		elif self.alignment == gnomeapplet.ORIENT_DOWN:				y += target_h				if ((x + w) > monitor.x + monitor.width):						x -= (x + w) - (monitor.x + monitor.width)				gravity = gtk.gdk.GRAVITY_NORTH_WEST		elif self.alignment == gnomeapplet.ORIENT_UP:				y -= h				if ((x + w) > monitor.x + monitor.width):						x -= (x + w) - (monitor.x + monitor.width)				gravity = gtk.gdk.GRAVITY_SOUTH_WEST				# -"Coordinates locked in captain."		# -"Engage."		self.move(x, y)		#print "Move win to "+x+","+y		self.set_gravity(gravity)		gtk.Window.show (self)		self.__is_shown = True	def show_all (self):		self.show ()		gtk.Window.show_all (self)			def hide (self):		self.__is_shown = False		gtk.Window.hide (self)		def is_shown (self):		return self.__is_shown# --------------------BEGIN CUT-N-PASTE FROM DESKBAR-APPLET ---------------import gtkgtk.threads_init()import gnome.ui, gnomeappletimport getopt, sysfrom os.path import *# Allow to use uninstalleddef _check(path):	return exists(path) and isdir(path) and isfile(path+"/AUTHORS")name = join(dirname(__file__), "..")if _check(name):	print 'Running uninstalled deskbar, modifying PYTHONPATH'	sys.path.insert(0, abspath(name))else:	print "Running installed deskbar, using normal PYTHONPATH"# Now the path is set, import our appletimport deskbar, deskbar.applet, deskbar.defsimport gettext, localegettext.bindtextdomain('deskbar-applet', abspath(join(deskbar.defs.DATA_DIR, "locale")))gettext.textdomain('deskbar-applet')locale.bindtextdomain('deskbar-applet', abspath(join(deskbar.defs.DATA_DIR, "locale")))locale.textdomain('deskbar-applet')# --------------------END CUT-N-PASTE FROM DESKBAR-APPLET ---------------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, ModuleListmloader = ModuleLoader (deskbar.MODULES_DIRS)mlist = ModuleList ()mloader.connect ("module-loaded", on_module_loaded)mloader.load_all()#mloader.load (join(MYPATH, "handlers/beagle-live.py"))#mloader.load (join(MYPATH, "handlers/google-live.py"))#mloader.load (join(MYPATH, "handlers/volumes.py"))def on_entry_changed (entry):	cuemiac.clear()	text = entry.get_text().strip()		if text == "":		cwindow.hide ()		return		for ctx in modules:		if not ctx.enabled:			continue					if ctx.module.is_async ():			ctx.module.query_async (text)		else:			cuemiac.append (ctx.module.query (text))		if not cwindow.is_shown ():		cwindow.show_all ()def on_entry_key_press (entry, event):	if event.keyval == gtk.keysyms.Escape:			# bind Escape to clear the GtkEntry			if not entry.get_text().strip() == "":				# If we cleared some text, tell async handlers to stop.				for ctx in modules:					if ctx.module.is_async ():						ctx.module.stop_query ()			entry.set_text("")vbox = gtk.VBox ()entry = gtk.Entry()entry.connect ("changed", on_entry_changed)entry.connect ("key-press-event", on_entry_key_press)cmodel = CuemiacModel (CATEGORIES)cview = CuemiacTreeView (cmodel)cuemiac = CuemiacWidget (cmodel, cview)cwindow = CuemiacWindow (entry, gnomeapplet.ORIENT_DOWN)cwindow.add (cuemiac)cwindow.connect ("focus-out-event", lambda widget,event: cwindow.hide())def do_action(sender, match):	if hasattr(match, 'action'):		match.action(entry.get_text())	cuemiac.connect ("match-selected", do_action)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.main ()