[Deskbar] async handler update



I updated the async handler stuff to apply against cvs.

There's also a SignallingHandler now. There's two new debuggung handlers
attached. One for each new class.

Cheers
Mikkel
diff -ruN ../deskbar-applet/deskbar/applet.py deskbar/applet.py
--- ../deskbar-applet/deskbar/applet.py	2005-10-14 22:04:57.000000000 +0200
+++ deskbar/applet.py	2005-10-15 17:03:03.675894896 +0200
@@ -23,6 +23,7 @@
 		self.entry = deskbar.deskbarentry.DeskbarEntry(self.module_list)
 		self.entry.get_evbox().connect("button-press-event", self.on_icon_button_press)
 		self.entry.get_entry().connect("button-press-event", self.on_entry_button_press)
+		self.loader.connect ("module-initialized", self.entry._connect_if_async)
 
 		self.keybinder = deskbar.applet_keybinder.AppletKeybinder(self)
 
diff -ruN ../deskbar-applet/deskbar/deskbarentry.py deskbar/deskbarentry.py
--- ../deskbar-applet/deskbar/deskbarentry.py	2005-10-14 21:25:26.000000000 +0200
+++ deskbar/deskbarentry.py	2005-10-15 17:03:03.674895048 +0200
@@ -5,6 +5,8 @@
 import deskbar.iconentry
 from deskbar.module_list import ModuleList
 
+from handler import *
+
 import gtk, gobject
 
 # The liststore columns
@@ -42,6 +44,7 @@
 		entry.connect("activate", self._on_entry_activate)
 		self._on_entry_changed_id = entry.connect("changed", self._on_entry_changed)
 		entry.connect("key-press-event", self._on_entry_key_press)
+		entry.connect("destroy", self._stop_async_handlers)
 		
 		# The image showing the matches' icon
 		self._image = gtk.Image()
@@ -84,7 +87,7 @@
 		completion.connect("match-selected", self._on_completion_selected)
 		entry.set_completion(completion)
 		
-		# Pain it accordingly		
+		# Paint  it accordingly		
 		renderer = gtk.CellRendererPixbuf()
 		completion.pack_start(renderer)
 		completion.add_attribute(renderer, "pixbuf", ICON_COL)
@@ -93,6 +96,19 @@
 		completion.pack_start(renderer)
 		completion.add_attribute(renderer, "markup", ACTION_COL)
 	
+	def _stop_async_handlers (self, sender=None):
+		for modctx in self._handlers:
+			if modctx.module.is_async ():
+				modctx.module.stop_query ()
+				print "deskbarentry.py : Stopping async handler: %s" % modctx.module
+	
+	def _connect_if_async (self, sender, context):
+		if context.module.is_async ():
+			context.module.connect ('query-ready', self._append_matches_cb)
+		
+	def _append_matches_cb (self, sender, matches):
+		self._append_matches (matches)
+	
 	def get_evbox(self):
 		return self._evbox
 	
@@ -211,9 +227,9 @@
 		if matches == None:
 			# We have a regular changed event, fill the new model, reset history
 			self._history.reset()
-
-		t = widget.get_text().strip()
-		if t == "":
+		
+		qstring = widget.get_text().strip()
+		if  qstring == "":
 			#Reset default icon
 			self._update_icon(icon=self._default_pixbuf)
 			return
@@ -222,26 +238,37 @@
 		result = []
 		if matches == None:
 			for modctx in self._handlers:
-				matches = modctx.module.query(t, MAX_RESULTS_PER_HANDLER)
-				for match in matches:
-					result.append(match)
+				if modctx.module.is_async ():
+					modctx.module.query_async (qstring, MAX_RESULTS_PER_HANDLER)
+				else:
+					matches = modctx.module.query(qstring, MAX_RESULTS_PER_HANDLER)
+					for match in matches:
+						result.append(match)
+			self._append_matches (result)
 		else:
-			result = matches
-				
-		for res in result:
-			handler = res.get_handler()
-			if res.get_icon() != None:
-				icon = res.get_icon()
+			self._append_matches (matches)
+	
+	def _append_matches (self, matches):
+		"""
+		Appends the list of Match objects to the list of query matches
+		"""
+		
+		t = self.get_entry().get_text().strip()
+		
+		for match in matches:
+			handler = match.get_handler()
+			if match.get_icon() != None:
+				icon = match.get_icon()
 			else:
 				icon = handler.get_icon()
 			
 			# Pass unescaped query to the matches
 			verbs = {"text" : t}
-			verbs.update(res.get_name(t))
+			verbs.update(match.get_name(t))
 			# Escape the query now for display
 			verbs["text"] = cgi.escape(verbs["text"])
 			
-			self._completion_model.append([handler.get_priority(), res.get_priority(), res.get_verb() % verbs, icon, res])
+			self._completion_model.append([handler.get_priority(), match.get_priority(), match.get_verb() % verbs, icon, match])
 		
 		#Set the entry icon accoring to the first match in the completion list
 		self._update_icon(iter=self._completion_model.get_iter_first())
diff -ruN ../deskbar-applet/deskbar/handler.py deskbar/handler.py
--- ../deskbar-applet/deskbar/handler.py	2005-10-14 15:43:06.000000000 +0200
+++ deskbar/handler.py	2005-10-15 17:03:03.675894896 +0200
@@ -126,3 +126,185 @@
 		"max".
 		"""
 		raise NotImplementedError
+
+	def is_async (self):
+		"""
+		AsyncHandler overwrites this method and returns True.
+		It is used to determine whether we should call query or query_async,
+		when querying this handler.
+		"""
+		return False
+
+
+
+from Queue import Queue
+from Queue import Empty
+from threading import Thread
+
+class NoArgs :
+	pass
+
+class QueryStopped (Exception):
+	pass	
+
+class QueryChanged (Exception):
+	pass
+				
+class AsyncHandler (Handler, gobject.GObject):
+	"""
+	This class can do asynchronous queries. To implement an AsyncHandler just write
+	would do for an ordinary (sync) Handler. Ie. you main concern is to implement a
+	query() method.
+	
+	In doing this you should regularly call check_query_changed() which will restart
+	the query if the query string has changed.
+	
+	To return a list of Matches either just return it normally from query(), or use
+	emit_query_ready(matches) to emit partial results.
+	
+	There will at all times only be at maximum one thread per AsyncHandler.
+	"""
+
+	__gsignals__ = {
+		"query-ready" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_PYOBJECT]),
+	}
+
+	QUERY_PRIORITY = gobject.PRIORITY_DEFAULT_IDLE
+
+	def __init__ (self, iconfile=None):
+		Handler.__init__ (self, iconfile)
+		gobject.GObject.__init__ (self)
+		self.__query_queue = Queue ()
+		self.is_running = False
+	
+	def query_async (self, qstring, max=5):
+		"""
+		This method is the one to be called by the object wanting to start a new query.
+		If there's an already running query taht one will be cancelled if possible.
+		
+		Each time there is matches ready there will be a "query-ready" signal emitted
+		which will be handled in the main thread. A list of Match objects will be passed
+		argument to this signal.
+		
+		Note: An AsyncHandler may signal on partial results. The thread need not have
+		exited because there's a 'query-ready' signal emitted. Read: Don't assume that the
+		handler only return Matches one time.
+		"""
+		if not self.is_running:
+			self.is_running = True
+			Thread (None, self.__query_async, args=(qstring, max)).start ()
+			print "AsyncHandler: Thread created for %s" % str(self.__class__)
+		else:
+			self.__query_queue.put (qstring, False)
+	
+	def stop_query (self):
+		"""
+		Instructs the handler to stop the query the next time it does check_query_changed().
+		"""
+		self.__query_queue.put (QueryStopped)
+	
+	def emit_query_ready (self, matches):
+		"""
+		Use this method to emit partial results. matches should be a list of Match objects.
+		
+		Note: returning a list of Match objects from the query() method automatically
+		emits a 'query-ready' signal for this list. 
+		"""
+		gobject.idle_add (self.__emit_query_ready, matches)
+		
+	def check_query_changed (self, clean_up=None, args=NoArgs):
+		"""
+		Checks if the query has changed. If it has it will execute clean_up(args)
+		and raise a QueryChanged exception. DO NOT catch this exception. This should
+		only be done by __async_query()
+		"""
+		if not self.__query_queue.empty():
+			# There's a query queued
+			# cancel the current query.
+			if clean_up:
+				if args == NoArgs:
+					clean_up ()
+				else:
+					clean_up (args)
+			raise QueryChanged ()
+		
+	def __emit_query_ready (self, matches):
+		"""Idle handler to emit a 'query-ready' signal to the main loop."""
+		self.emit ("query-ready", matches)
+		return False
+	
+	def __query_async (self, qstring, max=5):
+		"""
+		The magic happens here.
+		"""
+		try:
+			print "%s querying for '%s'" % (self.__class__, qstring)
+			res = self.query (qstring, max)
+			if (res and res != []):
+				self.emit_query_ready (res)
+			self.is_running = False
+			
+		except QueryChanged:
+			try:
+				qstring = self.__get_last_query ()
+				self.__query_async (qstring, max)
+			except QueryStopped:
+				self.is_running = False
+				print "AsyncHandler: %s thread terminated." % str(self.__class__)
+				
+		except QueryStopped:
+			self.is_running = False
+			print "AsyncHandler: %s thread terminated." % str(self.__class__)
+
+	def __get_last_query (self):
+		"""
+		Returns the query to be put on the query queue. We don't wan't to
+		do all the intermediate ones... They're obsolete.
+		
+		If there's a QueryStopped class somewhere in the queue 
+		(put there by stop_query()) raise a QueryStopped exeption.
+		This exception will be caught by __query_async()
+		"""
+		tmp = None
+		last_query = None
+		try:
+			while True:
+				# Get a query without blocking
+				# This call raises an Empty exception
+				# if there's no element to get()
+				tmp = self.__query_queue.get (False)
+				last_query = tmp
+				if last_query == QueryStopped:
+					raise QueryStopped ()
+		except Empty:
+			return last_query
+
+	def is_async (self):
+		"""Well what do you think?"""
+		return True
+
+
+class SignallingHandler (Handler, gobject.GObject):
+	#FIXME: Document SignallingHandler
+	__gsignals__ = {
+		"query-ready" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_PYOBJECT])
+	}
+
+	def __init__ (self, iconfile=None):
+                Handler.__init__ (self, iconfile)
+		gobject.GObject.__init__ (self)
+		self.__last_query = ""
+
+	def query_async (self, qstring, max=5):
+		self.__last_query = qstring
+		self.query (qstring, max)
+
+	def emit_query_ready (self, matches, qstring):
+		if qstring == self.__last_query:
+			self.emit ("query-ready", matches)
+
+	def stop_query (self):
+		pass
+		
+	def is_async (self):
+		return True
diff -ruN ../deskbar-applet/deskbar/module_list.py deskbar/module_list.py
--- ../deskbar-applet/deskbar/module_list.py	2005-10-14 22:04:57.000000000 +0200
+++ deskbar/module_list.py	2005-10-15 17:03:03.674895048 +0200
@@ -140,7 +140,7 @@
 		self.set_value(iter, self.MODULE_COL, context.module)
 		self.set_value(iter, self.SETTINGS_COL, context.settings)
 		self.set_value(iter, self.FILENAME_COL, context.filename)
-		self.set_value(iter, self.NAME_COL, "<b>%s</b>\n%s" % context.name)
+		self.set_value(iter, self.NAME_COL, context.name)
 		self.set_value(iter, self.EXP_CLASS_COL, context.exported_class)
 		
 	def update_row_cb (self, sender, context, iter=None):
from handler import *
from time import sleep

NAME = "Async Debug Module"
EXPORTED_CLASS = "AsyncDebugHandler"

# Change these values to for your debugging pleasures
QUERY_TIME = 0.3
NUM_QUERIES = 6
PARTIAL_RESULTS_TIME = 3

class AsyncDebugMatch (Match):
	def __init__(self, handler, name, icon=None):
		Match.__init__ (self, handler, name)
	
	def get_handler(self):
		return self._handler
	
	def get_name (self, text=None):
		return {"name" : "Async Debug, " + self._name}
	
	def get_verb(self):
		return "%(name)s - %(text)s"
		
	def action(self, text=None):
		print str(self.__class__) + " : action triggered"

class AsyncDebugHandler (AsyncHandler): 

	def __init__ (self):
		AsyncHandler.__init__ (self, None)
		
	def query (self, qstring, max=5):
		
		for i in range (NUM_QUERIES):
			sleep (QUERY_TIME)
			print "Querying: " + (i+1)*"."
			if i == PARTIAL_RESULTS_TIME:
				# emit partial results
				self.emit_query_ready ([AsyncDebugMatch(self, "partial results - %s"%qstring)])
				
			# This call will exit this method if there's a new query pending
			# or we have been instructed to stop:
			self.check_query_changed (self.clean_me, [qstring])
		
		# it is also allowed to return matches like this:
		return [AsyncDebugMatch(self, "returned results - %s"%qstring)]
				
	def clean_me (self, args):
		print str(self.__class__) + " : Clean up for query: " + str(args)
		
	def stop (self):
		print str(self.__class__) + " : stop() called" + str(args)
		
	def get_priority(self):	
		return 300 

from handler import *
from time import sleep

NAME = "Signalling Debug Module"
EXPORTED_CLASS = "SignallingDebugHandler"


SIGNAL_DELAY = 1000 # 1 sec.

class SignallingDebugMatch (Match):
	def __init__(self, handler, name, icon=None):
		Match.__init__ (self, handler, name)
	
	def get_handler(self):
		return self._handler
	
	def get_name (self, text=None):
		return {"name" : "Signal Debug, " + self._name}
	
	def get_verb(self):
		return "%(name)s - %(text)s"
		
	def action(self, text=None):
		print str(self.__class__) + " : action triggered"


class SignallingDebugHandler(SignallingHandler):
	def query(self, qstring, max):
		# gobject.timeout_add represents an async lib call
		gobject.timeout_add(SIGNAL_DELAY, lambda : self.__callback(qstring))

	def __callback(self, qstring):
		match = SignallingDebugMatch(self, qstring)
		self.emit_query_ready([match], qstring)
        
	def get_priority(self):	
		return 300 


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