home *** CD-ROM | disk | FTP | other *** search
- import sys
- import gtk, gobject
- import deskbar.Utils, deskbar
-
- # Most Handlers will be HANDLER_IS_HAPPY, and this the assumed default state.
- # Some Handlers will require configuration, or need to notify the user in some
- # way of some message. For example, the evolution-data-server backed Handler
- # should give an indication if none of the user's address books have auto-
- # complete enabled (thus rendering the Handler unable to function). This would
- # be represented by HANDLER_IS_CONFIGURABLE.
- # Other Handlers shouldn't be shown to the user at all. For example, if the
- # user's preferred web browser is Epiphany, then the Firefox handler should
- # be hidden: HANDLER_IS_NOT_APPLICABLE.
- HANDLER_IS_HAPPY = 0
- HANDLER_IS_CONFIGURABLE = 1
- HANDLER_HAS_REQUIREMENTS = 2
- HANDLER_IS_NOT_APPLICABLE = 3
-
- class Handler:
- def __init__(self, iconfile):
- """
- The constructor of the Handler should not block.
- Heavy duty tasks such as indexing should be done in the initialize() method.
- Under all circumstances, the constructor should not raise ANY exception
- """
- if type(iconfile) == tuple:
- for icon in iconfile:
- self._icon = deskbar.Utils.load_icon(icon)
- if self._icon != None:
- break
- else:
- self._icon = deskbar.Utils.load_icon(iconfile)
-
- self._priority = 0
-
- def set_priority(self, prio):
- self._priority = prio
-
- def get_priority(self):
- """
- Returns the global priority (against other handlers) of this handler as int
- """
- return self._priority
-
- def deserialize(self, class_name, serialized):
- try:
- match = getattr(sys.modules[self.__module__], class_name)(self, **serialized)
- if match.is_valid():
- return match
- except Exception, msg:
- print 'Warning:Error while deserializing match:', class_name, serialized, msg
-
- return None
-
- def get_icon(self):
- """
- Returns a GdkPixbuf hat represents this handler.
- Returns None if there is no associated icon.
- """
- return self._icon
-
- def initialize(self):
- """
- The initialize of the Handler should not block.
- Heavy duty tasks such as indexing should be done in this method, it
- will be called with a low priority in the mainloop.
-
- Handler.initialize() is guarantied to be called before the handler
- is queried.
-
- If an exception is thrown in this method, the module will be ignored and will
- not receive any query.
- """
- pass
-
- def stop(self):
- """
- If the handler needs any cleaning up before it is unloaded, do it here.
-
- Handler.stop() is guarantied to be called before the handler is
- unloaded.
- """
- pass
-
- def query(self, query):
- """
- Searches the handler for the given query string.
- Returns a list of
- (string, match object) tuple or
- match object, when the string is the same as the passed one.
- """
- raise NotImplementedError
-
- def on_key_press(self, query, shortcut):
- """
- Called when the user presses a special trigger combination, like alt-foo
- The query text and text press gtk event are passed.
-
- The handler must return None if it didn't handle the key press, or a Match instance if
- it handled the keypress, and the returned match will be executed with the query text
- """
- return None
-
- def is_async (self):
- """
- AsyncHandler overwrites this method and returns True.
- It is used to determine whether we should call some async specific methods/signals.
- """
- return False
-
- class SignallingHandler (Handler, gobject.GObject):
- """
- This handler is an asynchronous handler using natural glib libraries, like
- libebook, or galago, or twisted.
- """
- __gsignals__ = {
- "query-ready" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_STRING, gobject.TYPE_PYOBJECT])
- }
-
- def __init__ (self, iconfile=None):
- Handler.__init__ (self, iconfile)
- gobject.GObject.__init__ (self)
- self.__last_query = None
- self.__start_query_id = 0
- self.__delay = 0
-
- def set_delay (self, timeout):
- self.__delay = timeout
-
- def query_async (self, qstring):
- if self.__delay == 0:
- self.__query_async_real (qstring)
- return
- # Check if there is a query delayed, and remove it if so
- if self.__start_query_id != 0:
- gobject.source_remove(self.__start_query_id)
-
- self.__start_query_id = gobject.timeout_add(self.__delay, self.__query_async_real, qstring)
-
- def __query_async_real (self, qstring):
- """
- When we receive an async call, we first register the most current search string.
- Then we call with a little delay the actual query() method, implemented by the handler.
- """
- self.__last_query = qstring
- try:
- self.query (qstring)
- except TypeError:
- self.query (qstring, deskbar.DEFAULT_RESULTS_PER_HANDLER)
-
- def emit_query_ready (self, qstring, matches):
- if qstring == self.__last_query:
- self.emit ("query-ready", qstring, matches)
-
- def stop_query (self):
- if self.__start_query_id != 0:
- gobject.source_remove(self.__start_query_id)
- self.__last_query = None
-
- def is_async (self):
- return True
-
- if gtk.pygtk_version < (2,8,0):
- gobject.type_register(SignallingHandler)
-
- # Here begins the Nastyness
- from Queue import Queue
- from Queue import Empty
- from threading import Thread
-
- class NoArgs :
- pass
-
- class QueryStopped (Exception):
- pass
-
- class QueryChanged (Exception):
- def __init__ (self, new_query):
- self.new_query = new_query
-
- class AsyncHandler (Handler, gobject.GObject):
- """
- This class can do asynchronous queries. To implement an AsyncHandler just write it
- like you would 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. This method can handle clean up methods
- and timeouts/delays if you want to check for rapidly changing queries.
-
- 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_STRING, 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):
- """
- This method is the one to be called by the object wanting to start a new query.
- If there's an already running query that 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,)).start ()
- #print "AsyncHandler: Thread created for %s" % self.__class__ # DEBUG
- 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().
- """
- if self.is_running:
- self.__query_queue.put (QueryStopped)
-
- def emit_query_ready (self, qstring, 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, qstring, matches)
-
- def check_query_changed (self, clean_up=None, args=NoArgs, timeout=None):
- """
- 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 you pass a timeout argument this call will not return before the query
- has been unchanged for timeout seconds.
- """
- qstring = None
- try:
- qstring = self.__get_last_query (timeout)
- except QueryStopped:
- if clean_up:
- if args == NoArgs:
- clean_up ()
- else:
- clean_up (args)
- #print "AsyncHandler: Query stopped", self.__class__ # DEBUG
- raise QueryStopped()
- if qstring:
- # There's a query queued
- # cancel the current query.
- if clean_up:
- if args == NoArgs:
- clean_up ()
- else:
- clean_up (args)
- #print "AsyncHandler: Query changed", self.__class__ # DEBUG
- raise QueryChanged (qstring)
-
- def __emit_query_ready (self, qstring, matches):
- """Idle handler to emit a 'query-ready' signal to the main loop."""
- self.emit ("query-ready", qstring, matches)
- return False
-
- def __query_async (self, qstring):
- """
- The magic happens here.
- """
- try:
- try:
- res = self.query (qstring)
- except TypeError:
- res = self.query (qstring, deskbar.DEFAULT_RESULTS_PER_HANDLER)
-
- if (res and res != []):
- self.emit_query_ready (qstring, res)
- self.is_running = False
-
- except QueryChanged, query_change:
- try:
- self.__query_async (query_change.new_query)
- except QueryStopped:
- self.is_running = False
- #print "AsyncHandler: %s thread terminated." % str(self.__class__) # DEBUG
-
- except QueryStopped:
- self.is_running = False
- #print "AsyncHandler: %s thread terminated." % str(self.__class__) # DEBUG
-
- def __get_last_query (self, timeout=None):
- """
- 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()
-
- If timeout is passed then wait timeout seconds to see if new queries
- are put on the queue.
- """
- tmp = None
- last_query = None
- try:
- while True:
- # Get a query without blocking (or only block
- # timeout seconds).
- # The get() call raises an Empty exception
- # if there's no element to get()
- if timeout:
- tmp = self.__query_queue.get (True, timeout)
- else:
- 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
-
- if gtk.pygtk_version < (2,8,0):
- gobject.type_register(AsyncHandler)
-