X-Git-Url: http://git.maemo.org/git/?p=gc-dialer;a=blobdiff_plain;f=src%2Fgtk_toolbox.py;h=7112030ff81629b6e432a210097e5bf1e8e07818;hp=88ceb1fbd13aeb8988c4672c148968e11ea01c7d;hb=0320d6d2086ce89fe554d8f902531f5b00dbec94;hpb=c9c72064f07a4ab16374e4bb5abed0f640e515ef diff --git a/src/gtk_toolbox.py b/src/gtk_toolbox.py index 88ceb1f..7112030 100644 --- a/src/gtk_toolbox.py +++ b/src/gtk_toolbox.py @@ -2,11 +2,14 @@ from __future__ import with_statement +import os +import errno import sys -import traceback +import time +import itertools import functools import contextlib -import warnings +import logging import threading import Queue @@ -14,6 +17,61 @@ import gobject import gtk +_moduleLogger = logging.getLogger("gtk_toolbox") + + +def get_screen_orientation(): + width, height = gtk.gdk.get_default_root_window().get_size() + if width < height: + return gtk.ORIENTATION_VERTICAL + else: + return gtk.ORIENTATION_HORIZONTAL + + +def orientation_change_connect(handler, *args): + """ + @param handler(orientation, *args) -> None(?) + """ + initialScreenOrientation = get_screen_orientation() + orientationAndArgs = list(itertools.chain((initialScreenOrientation, ), args)) + + def _on_screen_size_changed(screen): + newScreenOrientation = get_screen_orientation() + if newScreenOrientation != orientationAndArgs[0]: + orientationAndArgs[0] = newScreenOrientation + handler(*orientationAndArgs) + + rootScreen = gtk.gdk.get_default_root_window() + return gtk.connect(rootScreen, "size-changed", _on_screen_size_changed) + + +@contextlib.contextmanager +def flock(path, timeout=-1): + WAIT_FOREVER = -1 + DELAY = 0.1 + timeSpent = 0 + + acquired = False + + while timeSpent <= timeout or timeout == WAIT_FOREVER: + try: + fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR) + acquired = True + break + except OSError, e: + if e.errno != errno.EEXIST: + raise + time.sleep(DELAY) + timeSpent += DELAY + + assert acquired, "Failed to grab file-lock %s within timeout %d" % (path, timeout) + + try: + yield fd + finally: + os.unlink(path) + + @contextlib.contextmanager def gtk_lock(): gtk.gdk.threads_enter() @@ -110,6 +168,30 @@ def autostart(func): @autostart +def printer_sink(format = "%s"): + """ + >>> pr = printer_sink("%r") + >>> pr.send("Hello") + 'Hello' + >>> pr.send("5") + '5' + >>> pr.send(5) + 5 + >>> p = printer_sink() + >>> p.send("Hello") + Hello + >>> p.send("World") + World + >>> # p.throw(RuntimeError, "Goodbye") + >>> # p.send("Meh") + >>> # p.close() + """ + while True: + item = yield + print format % (item, ) + + +@autostart def null_sink(): """ Good for uses like with cochain to pick up any slack @@ -123,24 +205,26 @@ def comap(function, target): """ >>> p = printer_sink() >>> cm = comap(lambda x: x+1, p) - >>> cm.send(0) + >>> cm.send((0, )) 1 - >>> cm.send(1.0) + >>> cm.send((1.0, )) 2.0 - >>> cm.send(-2) + >>> cm.send((-2, )) -1 - >>> # cm.throw(RuntimeError, "Goodbye") - >>> # cm.send(0) - >>> # cm.send(1.0) - >>> # cm.close() """ while True: try: item = yield mappedItem = function(*item) target.send(mappedItem) - except StandardError, e: - target.throw(e.__class__, e.message) + except Exception, e: + _moduleLogger.exception("Forwarding exception!") + target.throw(e.__class__, str(e)) + + +def _flush_queue(queue): + while not queue.empty(): + yield queue.get() @autostart @@ -160,8 +244,8 @@ def queue_sink(queue): try: item = yield queue.put((None, item)) - except StandardError, e: - queue.put((e.__class__, e.message)) + except Exception, e: + queue.put((e.__class__, str(e))) except GeneratorExit: queue.put((GeneratorExit, None)) raise @@ -180,17 +264,6 @@ def decode_item(item, target): def nonqueue_source(queue, target): - """ - >>> q = Queue.Queue() - >>> for i in [ - ... (None, 'Hello'), - ... (None, 'World'), - ... (GeneratorExit, None), - ... ]: - ... q.put(i) - >>> qs = queue_source(q, printer_sink()) - Hello - """ isDone = False while not isDone: item = queue.get() @@ -211,6 +284,22 @@ def threaded_stage(target, thread_factory = threading.Thread): return queue_sink(messages) +def log_exception(logger): + + def log_exception_decorator(func): + + @functools.wraps(func) + def wrapper(*args, **kwds): + try: + return func(*args, **kwds) + except Exception: + logger.exception(func.__name__) + + return wrapper + + return log_exception_decorator + + class LoginWindow(object): def __init__(self, widgetTree): @@ -230,13 +319,13 @@ class LoginWindow(object): self._serviceCombo.add_attribute(cell, 'text', 1) self._serviceCombo.set_active(0) - callbackMapping = { - "on_loginbutton_clicked": self._on_loginbutton_clicked, - "on_loginclose_clicked": self._on_loginclose_clicked, - } - widgetTree.signal_autoconnect(callbackMapping) + widgetTree.get_widget("loginbutton").connect("clicked", self._on_loginbutton_clicked) + widgetTree.get_widget("logins_close_button").connect("clicked", self._on_loginclose_clicked) - def request_credentials(self, parentWindow = None): + def request_credentials(self, + parentWindow = None, + defaultCredentials = ("", "") + ): """ @note UI Thread """ @@ -246,6 +335,9 @@ class LoginWindow(object): self._serviceCombo.hide() self._serviceList.clear() + self._usernameEntry.set_text(defaultCredentials[0]) + self._passwordEntry.set_text(defaultCredentials[1]) + try: self._dialog.set_transient_for(parentWindow) self._dialog.set_default_response(gtk.RESPONSE_OK) @@ -305,6 +397,21 @@ class LoginWindow(object): self._dialog.response(gtk.RESPONSE_CANCEL) +def safecall(f, errorDisplay=None, default=None, exception=Exception): + ''' + Returns modified f. When the modified f is called and throws an + exception, the default value is returned + ''' + def _safecall(*args, **argv): + try: + return f(*args,**argv) + except exception, e: + if errorDisplay is not None: + errorDisplay.push_exception(e) + return default + return _safecall + + class ErrorDisplay(object): def __init__(self, widgetTree): @@ -324,31 +431,25 @@ class ErrorDisplay(object): self.push_message(message) def push_message(self, message): - if 0 < len(self.__messages): - self.__messages.append(message) - else: + self.__messages.append(message) + if 1 == len(self.__messages): self.__show_message(message) - def push_exception_with_lock(self, exception = None): + def push_exception_with_lock(self): with gtk_lock(): - self.push_exception(exception) + self.push_exception() - def push_exception(self, exception = None): - if exception is None: - userMessage = str(sys.exc_value) - warningMessage = str(traceback.format_exc()) - else: - userMessage = str(exception) - warningMessage = str(exception) + def push_exception(self): + userMessage = str(sys.exc_info()[1]) self.push_message(userMessage) - warnings.warn(warningMessage, stacklevel=3) + _moduleLogger.exception(userMessage) def pop_message(self): - if 0 < len(self.__messages): - self.__show_message(self.__messages[0]) - del self.__messages[0] - else: + del self.__messages[0] + if 0 == len(self.__messages): self.__hide_message() + else: + self.__errorDescription.set_text(self.__messages[0]) def _on_close(self, *args): self.pop_message() @@ -380,11 +481,8 @@ class DummyErrorDisplay(object): self.__show_message(message) def push_exception(self, exception = None): - if exception is None: - warningMessage = traceback.format_exc() - else: - warningMessage = exception - warnings.warn(warningMessage, stacklevel=3) + userMessage = str(sys.exc_value) + _moduleLogger.exception(userMessage) def pop_message(self): if 0 < len(self.__messages): @@ -392,7 +490,7 @@ class DummyErrorDisplay(object): del self.__messages[0] def __show_message(self, message): - warnings.warn(message, stacklevel=2) + _moduleLogger.debug(message) class MessageBox(gtk.MessageDialog): @@ -465,8 +563,8 @@ class PopupCalendar(object): try: self._calendar.select_month(self._displayDate.month, self._displayDate.year) self._calendar.select_day(self._displayDate.day) - except StandardError, e: - warnings.warn(e.message) + except Exception, e: + _moduleLogger.exception(e) class QuickAddView(object): @@ -516,7 +614,7 @@ class QuickAddView(object): self._taskNameEntry.set_text("") self._signalSink.stage.send(("add", name)) - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() def _on_add_edit(self, *args): @@ -525,13 +623,13 @@ class QuickAddView(object): self._taskNameEntry.set_text("") self._signalSink.stage.send(("add-edit", name)) - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() def _on_add_pressed(self, widget): try: self._addToEditTimerId = gobject.timeout_add(1000, self._on_add_edit) - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() def _on_add_released(self, widget): @@ -539,7 +637,7 @@ class QuickAddView(object): if self._addToEditTimerId is not None: gobject.source_remove(self._addToEditTimerId) self._addToEditTimerId = None - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() def _on_paste(self, *args): @@ -549,16 +647,109 @@ class QuickAddView(object): if addedText: entry += addedText self._taskNameEntry.set_text(entry) - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() def _on_clear(self, *args): try: self._taskNameEntry.set_text("") - except StandardError, e: + except Exception, e: self._errorDisplay.push_exception() +class TapOrHold(object): + + def __init__(self, widget): + self._widget = widget + self._isTap = True + self._isPointerInside = True + self._holdTimeoutId = None + self._tapTimeoutId = None + self._taps = 0 + + self._bpeId = None + self._breId = None + self._eneId = None + self._lneId = None + + def enable(self): + self._bpeId = self._widget.connect("button-press-event", self._on_button_press) + self._breId = self._widget.connect("button-release-event", self._on_button_release) + self._eneId = self._widget.connect("enter-notify-event", self._on_enter) + self._lneId = self._widget.connect("leave-notify-event", self._on_leave) + + def disable(self): + self._widget.disconnect(self._bpeId) + self._widget.disconnect(self._breId) + self._widget.disconnect(self._eneId) + self._widget.disconnect(self._lneId) + + def on_tap(self, taps): + print "TAP", taps + + def on_hold(self, taps): + print "HOLD", taps + + def on_holding(self): + print "HOLDING" + + def on_cancel(self): + print "CANCEL" + + def _on_button_press(self, *args): + # Hack to handle weird notebook behavior + self._isPointerInside = True + self._isTap = True + + if self._tapTimeoutId is not None: + gobject.source_remove(self._tapTimeoutId) + self._tapTimeoutId = None + + # Handle double taps + if self._holdTimeoutId is None: + self._tapTimeoutId = None + + self._taps = 1 + self._holdTimeoutId = gobject.timeout_add(1000, self._on_hold_timeout) + else: + self._taps = 2 + + def _on_button_release(self, *args): + assert self._tapTimeoutId is None + # Handle release after timeout if user hasn't double-clicked + self._tapTimeoutId = gobject.timeout_add(100, self._on_tap_timeout) + + def _on_actual_press(self, *args): + if self._holdTimeoutId is not None: + gobject.source_remove(self._holdTimeoutId) + self._holdTimeoutId = None + + if self._isPointerInside: + if self._isTap: + self.on_tap(self._taps) + else: + self.on_hold(self._taps) + else: + self.on_cancel() + + def _on_tap_timeout(self, *args): + self._tapTimeoutId = None + self._on_actual_press() + return False + + def _on_hold_timeout(self, *args): + self._holdTimeoutId = None + self._isTap = False + self.on_holding() + return False + + def _on_enter(self, *args): + self._isPointerInside = True + + def _on_leave(self, *args): + self._isPointerInside = False + + if __name__ == "__main__": if False: import datetime