[Deskbar] async handler update
- From: Mikkel Kamstrup Erlandsen <kamstrup daimi au dk>
- To: "[deskbar]" <deskbar-applet-list gnome org>
- Subject: [Deskbar] async handler update
- Date: Sat, 15 Oct 2005 17:07:45 +0200
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]