From: Ed Page Date: Sat, 12 Dec 2009 17:44:37 +0000 (-0600) Subject: Minor cleanup using reusable exception logger and moving constants out of main file X-Git-Url: http://git.maemo.org/git/?p=gonvert;a=commitdiff_plain;h=91909712ac1ea9c8c271bc72757f04f2eec34106 Minor cleanup using reusable exception logger and moving constants out of main file --- diff --git a/src/constants.py b/src/constants.py index 66036f8..675bf08 100644 --- a/src/constants.py +++ b/src/constants.py @@ -8,3 +8,6 @@ __app_magic__ = 0xdeadbeef _data_path_ = os.path.join(os.path.expanduser("~"), ".gonvert") _user_settings_ = "%s/settings.ini" % _data_path_ _user_logpath_ = "%s/gonvert.log" % _data_path_ + +PROFILE_STARTUP = False +FORCE_HILDON_LIKE = False diff --git a/src/gonvert_glade.py b/src/gonvert_glade.py index bc1c291..4882b9b 100755 --- a/src/gonvert_glade.py +++ b/src/gonvert_glade.py @@ -16,6 +16,7 @@ import gtk.gdk import constants import hildonize +import gtk_toolbox import unit_data try: @@ -28,8 +29,6 @@ else: _moduleLogger = logging.getLogger("gonvert_glade") -PROFILE_STARTUP = False -FORCE_HILDON_LIKE = False if gettext is not None: gettext.bindtextdomain('gonvert', '/usr/share/locale') @@ -144,7 +143,7 @@ class Gonvert(object): self._unitsNameRenderer = gtk.CellRendererText() self._unitsNameRenderer.set_property("scale", 0.75) - if FORCE_HILDON_LIKE: + if constants.FORCE_HILDON_LIKE: self._unitsNameRenderer.set_property("ellipsize", pango.ELLIPSIZE_END) self._unitsNameRenderer.set_property("width-chars", 5) self._unitNameColumn = gtk.TreeViewColumn(_('Name'), self._unitsNameRenderer) @@ -241,7 +240,7 @@ class Gonvert(object): assert scrollingWidget is not None, scrollingWidgetName hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget) - if hildonize.IS_HILDON_SUPPORTED or FORCE_HILDON_LIKE: + if hildonize.IS_HILDON_SUPPORTED or constants.FORCE_HILDON_LIKE: self._categoryView.get_parent().hide() self._unitsView.set_headers_visible(False) self._previousUnitName.get_parent().hide() @@ -476,7 +475,7 @@ class Gonvert(object): nameLength = max(nameLength, len(key)) self._sortedUnitModel.sort_column_changed() - if FORCE_HILDON_LIKE: + if constants.FORCE_HILDON_LIKE: maxCatCharWidth = int(nameLength * 0.75) maxCharWidth = int(len("nibble | hexit | quadbit") * 0.75) charWidth = min(maxCatCharWidth, maxCharWidth) @@ -542,248 +541,227 @@ class Gonvert(object): value = float(userEntry) return value + @gtk_toolbox.log_exception(_moduleLogger) def _on_key_press(self, widget, event, *args): """ @note Hildon specific """ RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter) - try: - if ( - event.keyval == gtk.keysyms.F6 or - event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK - ): - if self._isFullScreen: - self._mainWindow.unfullscreen() - else: - self._mainWindow.fullscreen() - elif event.keyval == gtk.keysyms.f and event.get_state() & gtk.gdk.CONTROL_MASK: - self._toggle_find() - elif event.keyval == gtk.keysyms.p and event.get_state() & gtk.gdk.CONTROL_MASK: - self._find_previous() - elif event.keyval == gtk.keysyms.n and event.get_state() & gtk.gdk.CONTROL_MASK: - self._find_next() - elif event.keyval == ord("l") and event.get_state() & gtk.gdk.CONTROL_MASK: - with open(constants._user_logpath_, "r") as f: - logLines = f.xreadlines() - log = "".join(logLines) - self._clipboard.set_text(str(log)) - except Exception, e: - _moduleLogger.exception("_on_key_press") + if ( + event.keyval == gtk.keysyms.F6 or + event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK + ): + if self._isFullScreen: + self._mainWindow.unfullscreen() + else: + self._mainWindow.fullscreen() + elif event.keyval == gtk.keysyms.f and event.get_state() & gtk.gdk.CONTROL_MASK: + self._toggle_find() + elif event.keyval == gtk.keysyms.p and event.get_state() & gtk.gdk.CONTROL_MASK: + self._find_previous() + elif event.keyval == gtk.keysyms.n and event.get_state() & gtk.gdk.CONTROL_MASK: + self._find_next() + elif event.keyval == ord("l") and event.get_state() & gtk.gdk.CONTROL_MASK: + with open(constants._user_logpath_, "r") as f: + logLines = f.xreadlines() + log = "".join(logLines) + self._clipboard.set_text(str(log)) + @gtk_toolbox.log_exception(_moduleLogger) def _on_browse_key_press(self, widget, event, *args): - try: - if event.keyval == gtk.keysyms.uparrow or event.keyval == gtk.keysyms.Up: - index, column = self._unitsView.get_cursor() - newIndex = max(index[0]-1, 0) - self._unitsView.set_cursor((newIndex, ), column, True) - return True # override default behavior - elif event.keyval == gtk.keysyms.downarrow or event.keyval == gtk.keysyms.Down: - index, column = self._unitsView.get_cursor() - newIndex = min(index[0]+1, len(self._unitModel)-1) - self._unitsView.set_cursor((newIndex, ), column, True) - return True # override default behavior - except Exception, e: - _moduleLogger.exception("_on_key_press") - + if event.keyval == gtk.keysyms.uparrow or event.keyval == gtk.keysyms.Up: + index, column = self._unitsView.get_cursor() + newIndex = max(index[0]-1, 0) + self._unitsView.set_cursor((newIndex, ), column, True) + return True # override default behavior + elif event.keyval == gtk.keysyms.downarrow or event.keyval == gtk.keysyms.Down: + index, column = self._unitsView.get_cursor() + newIndex = min(index[0]+1, len(self._unitModel)-1) + self._unitsView.set_cursor((newIndex, ), column, True) + return True # override default behavior + + @gtk_toolbox.log_exception(_moduleLogger) def _on_window_state_change(self, widget, event, *args): """ @note Hildon specific """ - try: - if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: - self._isFullScreen = True - else: - self._isFullScreen = False - except Exception, e: - _moduleLogger.exception("_on_window_state_change") + if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: + self._isFullScreen = True + else: + self._isFullScreen = False + @gtk_toolbox.log_exception(_moduleLogger) def _on_findEntry_changed(self, *args): """ Clear out find results since the user wants to look for something new """ - try: - self._clear_find() - except Exception: - _moduleLogger.exception("_on_findEntry_changed") + self._clear_find() + @gtk_toolbox.log_exception(_moduleLogger) def _on_find_activate(self, *args): - try: - self._find_next() - self._findButton.grab_focus() - except Exception: - _moduleLogger.exception("_on_find_activate") + self._find_next() + self._findButton.grab_focus() + @gtk_toolbox.log_exception(_moduleLogger) def _on_click_unit_column(self, col): """ Sort the contents of the col when the user clicks on the title. """ - try: - #Determine which column requires sorting - columns = self._get_column_sort_stuff() - for columnIndex, (maybeCol, directionName, col_cmp) in enumerate(columns): - if col is maybeCol: - direction = getattr(self, directionName) - gtkDirection = gtk.SORT_ASCENDING if direction else gtk.SORT_DESCENDING - - # cause a sort - self._sortedUnitModel.set_sort_column_id(columnIndex, gtkDirection) - - # set the visual for sorting - col.set_sort_indicator(True) - col.set_sort_order(not direction) - - setattr(self, directionName, not direction) - break - else: - maybeCol.set_sort_indicator(False) + #Determine which column requires sorting + columns = self._get_column_sort_stuff() + for columnIndex, (maybeCol, directionName, col_cmp) in enumerate(columns): + if col is maybeCol: + direction = getattr(self, directionName) + gtkDirection = gtk.SORT_ASCENDING if direction else gtk.SORT_DESCENDING + + # cause a sort + self._sortedUnitModel.set_sort_column_id(columnIndex, gtkDirection) + + # set the visual for sorting + col.set_sort_indicator(True) + col.set_sort_order(not direction) + + setattr(self, directionName, not direction) + break else: - assert False, "Unknown column: %s" % (col.get_title(), ) - except Exception: - _moduleLogger.exception("_on_click_unit_column") + maybeCol.set_sort_indicator(False) + else: + assert False, "Unknown column: %s" % (col.get_title(), ) + @gtk_toolbox.log_exception(_moduleLogger) def _on_category_selector_clicked(self, *args): - try: - currenntIndex = unit_data.UNIT_CATEGORIES.index(self._selectedCategoryName) - newIndex = hildonize.touch_selector( - self._mainWindow, - "Categories", - unit_data.UNIT_CATEGORIES, - currenntIndex, - ) + currenntIndex = unit_data.UNIT_CATEGORIES.index(self._selectedCategoryName) + newIndex = hildonize.touch_selector( + self._mainWindow, + "Categories", + unit_data.UNIT_CATEGORIES, + currenntIndex, + ) - selectedCategoryName = unit_data.UNIT_CATEGORIES[newIndex] - self._categorySelectionButton.get_child().set_markup("%s" % selectedCategoryName) - self._categoryView.set_cursor(newIndex, self._categoryColumn, False) - self._categoryView.grab_focus() - except Exception: - _moduleLogger.exception("_on_category_selector_clicked") + selectedCategoryName = unit_data.UNIT_CATEGORIES[newIndex] + self._categorySelectionButton.get_child().set_markup("%s" % selectedCategoryName) + self._categoryView.set_cursor(newIndex, self._categoryColumn, False) + self._categoryView.grab_focus() + @gtk_toolbox.log_exception(_moduleLogger) def _on_click_category(self, *args): - try: - selected, iter = self._categoryView.get_selection().get_selected() - if iter is None: - # User is typing in an invalid string, not selecting any category - return - selectedCategory = self._categoryModel.get_value(iter, 0) - self._switch_category(selectedCategory) - except Exception: - _moduleLogger.exception("_on_click_category") + selected, iter = self._categoryView.get_selection().get_selected() + if iter is None: + # User is typing in an invalid string, not selecting any category + return + selectedCategory = self._categoryModel.get_value(iter, 0) + self._switch_category(selectedCategory) + @gtk_toolbox.log_exception(_moduleLogger) def _on_click_unit(self, *args): - try: - selected, iter = self._unitsView.get_selection().get_selected() - selected_unit = selected.get_value(iter, self.UNITS_NAME_IDX) - unit_spec = self._unitDataInCategory[selected_unit] + selected, iter = self._unitsView.get_selection().get_selected() + selected_unit = selected.get_value(iter, self.UNITS_NAME_IDX) + unit_spec = self._unitDataInCategory[selected_unit] - showSymbol = False + showSymbol = False - if self._unitName.get_text() != selected_unit: - self._previousUnitName.set_text(self._unitName.get_text()) - self._previousUnitValue.set_text(self._unitValue.get_text()) - self._previousUnitSymbol.set_text(self._unitSymbol.get_text()) - if self._unitSymbol.get_text(): - showSymbol = True - - self._unitName.set_text(selected_unit) - self._unitValue.set_text(selected.get_value(iter, self.UNITS_VALUE_IDX)) - buffer = self._unitDescription.get_buffer() - buffer.set_text(unit_spec[2]) - self._unitSymbol.set_text(unit_spec[1]) # put units into label text - if unit_spec[1]: + if self._unitName.get_text() != selected_unit: + self._previousUnitName.set_text(self._unitName.get_text()) + self._previousUnitValue.set_text(self._unitValue.get_text()) + self._previousUnitSymbol.set_text(self._unitSymbol.get_text()) + if self._unitSymbol.get_text(): showSymbol = True - else: - showSymbol = False - if showSymbol: - self._unitSymbol.show() - self._previousUnitSymbol.show() + self._unitName.set_text(selected_unit) + self._unitValue.set_text(selected.get_value(iter, self.UNITS_VALUE_IDX)) + buffer = self._unitDescription.get_buffer() + buffer.set_text(unit_spec[2]) + self._unitSymbol.set_text(unit_spec[1]) # put units into label text + if unit_spec[1]: + showSymbol = True + else: + showSymbol = False + + if showSymbol: + self._unitSymbol.show() + self._previousUnitSymbol.show() + else: + self._unitSymbol.hide() + self._previousUnitSymbol.hide() + + if self._unitValue.get_text() == '': + if self._selectedCategoryName == "Computer Numbers": + self._unitValue.set_text("0") else: - self._unitSymbol.hide() - self._previousUnitSymbol.hide() - - if self._unitValue.get_text() == '': - if self._selectedCategoryName == "Computer Numbers": - self._unitValue.set_text("0") - else: - self._unitValue.set_text("0.0") - - self._defaultUnitForCategory[self._selectedCategoryName] = [ - self._unitName.get_text(), self._previousUnitName.get_text() - ] - - # select the text so user can start typing right away - self._unitValue.grab_focus() - self._unitValue.select_region(0, -1) - except Exception: - _moduleLogger.exception("_on_click_unit") + self._unitValue.set_text("0.0") + + self._defaultUnitForCategory[self._selectedCategoryName] = [ + self._unitName.get_text(), self._previousUnitName.get_text() + ] + + # select the text so user can start typing right away + self._unitValue.grab_focus() + self._unitValue.select_region(0, -1) + @gtk_toolbox.log_exception(_moduleLogger) def _on_unit_value_changed(self, *args): - try: - if self._unitName.get_text() == '': - return - if not self._unitValue.is_focus(): - return - - #retrieve the conversion function and value from the selected unit - value = self._sanitize_value(self._unitValue.get_text()) - func, arg = self._unitDataInCategory[self._unitName.get_text()][0] - base = func.to_base(value, arg) - - #point to the first row - for row in self._unitModel: - func, arg = self._unitDataInCategory[row[self.UNITS_NAME_IDX]][0] - newValue = func.from_base(base, arg) - - newValueDisplay = str(newValue) - integerDisplay, fractionalDisplay = split_number(newValue) - - row[self.UNITS_VALUE_IDX] = newValueDisplay - row[self.UNITS_INTEGER_IDX] = integerDisplay - row[self.UNITS_FRACTION_IDX] = fractionalDisplay - - # Update the secondary unit entry - if self._previousUnitName.get_text() != '': - func, arg = self._unitDataInCategory[self._previousUnitName.get_text()][0] - self._previousUnitValue.set_text(str(func.from_base(base, arg, ))) - - self._sortedUnitModel.sort_column_changed() - self._refresh_columns() - except Exception: - _moduleLogger.exception("_on_unit_value_changed") + if self._unitName.get_text() == '': + return + if not self._unitValue.is_focus(): + return - def _on_previous_unit_value_changed(self, *args): - try: - if self._previousUnitName.get_text() == '': - return - if not self._previousUnitValue.is_focus(): - return + #retrieve the conversion function and value from the selected unit + value = self._sanitize_value(self._unitValue.get_text()) + func, arg = self._unitDataInCategory[self._unitName.get_text()][0] + base = func.to_base(value, arg) - #retrieve the conversion function and value from the selected unit - value = self._sanitize_value(self._previousUnitValue.get_text()) + #point to the first row + for row in self._unitModel: + func, arg = self._unitDataInCategory[row[self.UNITS_NAME_IDX]][0] + newValue = func.from_base(base, arg) + + newValueDisplay = str(newValue) + integerDisplay, fractionalDisplay = split_number(newValue) + + row[self.UNITS_VALUE_IDX] = newValueDisplay + row[self.UNITS_INTEGER_IDX] = integerDisplay + row[self.UNITS_FRACTION_IDX] = fractionalDisplay + + # Update the secondary unit entry + if self._previousUnitName.get_text() != '': func, arg = self._unitDataInCategory[self._previousUnitName.get_text()][0] - base = func.to_base(value, arg) + self._previousUnitValue.set_text(str(func.from_base(base, arg, ))) + + self._sortedUnitModel.sort_column_changed() + self._refresh_columns() + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_previous_unit_value_changed(self, *args): + if self._previousUnitName.get_text() == '': + return + if not self._previousUnitValue.is_focus(): + return - #point to the first row - for row in self._unitModel: - func, arg = self._unitDataInCategory[row[self.UNITS_NAME_IDX]][0] - newValue = func.from_base(base, arg) + #retrieve the conversion function and value from the selected unit + value = self._sanitize_value(self._previousUnitValue.get_text()) + func, arg = self._unitDataInCategory[self._previousUnitName.get_text()][0] + base = func.to_base(value, arg) - newValueDisplay = str(newValue) - integerDisplay, fractionalDisplay = split_number(newValue) + #point to the first row + for row in self._unitModel: + func, arg = self._unitDataInCategory[row[self.UNITS_NAME_IDX]][0] + newValue = func.from_base(base, arg) - row[self.UNITS_VALUE_IDX] = newValueDisplay - row[self.UNITS_INTEGER_IDX] = integerDisplay - row[self.UNITS_FRACTION_IDX] = fractionalDisplay + newValueDisplay = str(newValue) + integerDisplay, fractionalDisplay = split_number(newValue) - # Update the primary unit entry - func, arg = self._unitDataInCategory[self._unitName.get_text()][0] - self._unitValue.set_text(str(func.from_base(base, arg, ))) + row[self.UNITS_VALUE_IDX] = newValueDisplay + row[self.UNITS_INTEGER_IDX] = integerDisplay + row[self.UNITS_FRACTION_IDX] = fractionalDisplay - self._sortedUnitModel.sort_column_changed() - self._refresh_columns() - except Exception: - _moduleLogger.exception("_on_previous_unit_value_changed") + # Update the primary unit entry + func, arg = self._unitDataInCategory[self._unitName.get_text()][0] + self._unitValue.set_text(str(func.from_base(base, arg, ))) + + self._sortedUnitModel.sort_column_changed() + self._refresh_columns() + @gtk_toolbox.log_exception(_moduleLogger) def _on_about_clicked(self, a): dlg = gtk.AboutDialog() dlg.set_name(constants.__pretty_app_name__) @@ -795,11 +773,12 @@ class Gonvert(object): dlg.run() dlg.destroy() + @gtk_toolbox.log_exception(_moduleLogger) def _on_user_exit(self, *args): try: self._save_settings() except Exception: - _moduleLogger.exception("_on_user_exit") + pass finally: gtk.main_quit() @@ -809,7 +788,7 @@ def run_gonvert(): if hildonize.IS_HILDON_SUPPORTED: gtk.set_application_name(constants.__pretty_app_name__) handle = Gonvert() - if not PROFILE_STARTUP: + if not constants.PROFILE_STARTUP: gtk.main() diff --git a/src/gtk_toolbox.py b/src/gtk_toolbox.py new file mode 100644 index 0000000..8c2947d --- /dev/null +++ b/src/gtk_toolbox.py @@ -0,0 +1,772 @@ +#!/usr/bin/python + +from __future__ import with_statement + +import os +import errno +import sys +import time +import itertools +import functools +import contextlib +import logging +import threading +import Queue + +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() + try: + yield + finally: + gtk.gdk.threads_leave() + + +def find_parent_window(widget): + while True: + parent = widget.get_parent() + if isinstance(parent, gtk.Window): + return parent + widget = parent + + +def make_idler(func): + """ + Decorator that makes a generator-function into a function that will continue execution on next call + """ + a = [] + + @functools.wraps(func) + def decorated_func(*args, **kwds): + if not a: + a.append(func(*args, **kwds)) + try: + a[0].next() + return True + except StopIteration: + del a[:] + return False + + return decorated_func + + +def asynchronous_gtk_message(original_func): + """ + @note Idea came from http://www.aclevername.com/articles/python-webgui/ + """ + + def execute(allArgs): + args, kwargs = allArgs + with gtk_lock(): + original_func(*args, **kwargs) + return False + + @functools.wraps(original_func) + def delayed_func(*args, **kwargs): + gobject.idle_add(execute, (args, kwargs)) + + return delayed_func + + +def synchronous_gtk_message(original_func): + """ + @note Idea came from http://www.aclevername.com/articles/python-webgui/ + """ + + @functools.wraps(original_func) + def immediate_func(*args, **kwargs): + with gtk_lock(): + return original_func(*args, **kwargs) + + return immediate_func + + +def autostart(func): + """ + >>> @autostart + ... def grep_sink(pattern): + ... print "Looking for %s" % pattern + ... while True: + ... line = yield + ... if pattern in line: + ... print line, + >>> g = grep_sink("python") + Looking for python + >>> g.send("Yeah but no but yeah but no") + >>> g.send("A series of tubes") + >>> g.send("python generators rock!") + python generators rock! + >>> g.close() + """ + + @functools.wraps(func) + def start(*args, **kwargs): + cr = func(*args, **kwargs) + cr.next() + return cr + + return start + + +@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 + """ + while True: + item = yield + + +@autostart +def comap(function, target): + """ + >>> p = printer_sink() + >>> cm = comap(lambda x: x+1, p) + >>> cm.send((0, )) + 1 + >>> cm.send((1.0, )) + 2.0 + >>> cm.send((-2, )) + -1 + """ + while True: + try: + item = yield + mappedItem = function(*item) + target.send(mappedItem) + 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 +def queue_sink(queue): + """ + >>> q = Queue.Queue() + >>> qs = queue_sink(q) + >>> qs.send("Hello") + >>> qs.send("World") + >>> qs.throw(RuntimeError, "Goodbye") + >>> qs.send("Meh") + >>> qs.close() + >>> print [i for i in _flush_queue(q)] + [(None, 'Hello'), (None, 'World'), (, 'Goodbye'), (None, 'Meh'), (, None)] + """ + while True: + try: + item = yield + queue.put((None, item)) + except Exception, e: + queue.put((e.__class__, str(e))) + except GeneratorExit: + queue.put((GeneratorExit, None)) + raise + + +def decode_item(item, target): + if item[0] is None: + target.send(item[1]) + return False + elif item[0] is GeneratorExit: + target.close() + return True + else: + target.throw(item[0], item[1]) + return False + + +def nonqueue_source(queue, target): + isDone = False + while not isDone: + item = queue.get() + isDone = decode_item(item, target) + while not queue.empty(): + queue.get_nowait() + + +def threaded_stage(target, thread_factory = threading.Thread): + messages = Queue.Queue() + + run_source = functools.partial(nonqueue_source, messages, target) + thread = thread_factory(target=run_source) + thread.setDaemon(True) + thread.start() + + # Sink running in current 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): + """ + @note Thread agnostic + """ + self._dialog = widgetTree.get_widget("loginDialog") + self._parentWindow = widgetTree.get_widget("mainWindow") + self._serviceCombo = widgetTree.get_widget("serviceCombo") + self._usernameEntry = widgetTree.get_widget("usernameentry") + self._passwordEntry = widgetTree.get_widget("passwordentry") + + self._serviceList = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING) + self._serviceCombo.set_model(self._serviceList) + cell = gtk.CellRendererText() + self._serviceCombo.pack_start(cell, True) + self._serviceCombo.add_attribute(cell, 'text', 1) + self._serviceCombo.set_active(0) + + 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, + defaultCredentials = ("", "") + ): + """ + @note UI Thread + """ + if parentWindow is None: + parentWindow = self._parentWindow + + 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) + response = self._dialog.run() + if response != gtk.RESPONSE_OK: + raise RuntimeError("Login Cancelled") + + username = self._usernameEntry.get_text() + password = self._passwordEntry.get_text() + self._passwordEntry.set_text("") + finally: + self._dialog.hide() + + return username, password + + def request_credentials_from(self, + services, + parentWindow = None, + defaultCredentials = ("", "") + ): + """ + @note UI Thread + """ + if parentWindow is None: + parentWindow = self._parentWindow + + self._serviceList.clear() + for serviceIdserviceName in services: + self._serviceList.append(serviceIdserviceName) + self._serviceCombo.set_active(0) + self._serviceCombo.show() + + 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) + response = self._dialog.run() + if response != gtk.RESPONSE_OK: + raise RuntimeError("Login Cancelled") + + username = self._usernameEntry.get_text() + password = self._passwordEntry.get_text() + finally: + self._dialog.hide() + + itr = self._serviceCombo.get_active_iter() + serviceId = int(self._serviceList.get_value(itr, 0)) + self._serviceList.clear() + return serviceId, username, password + + def _on_loginbutton_clicked(self, *args): + self._dialog.response(gtk.RESPONSE_OK) + + def _on_loginclose_clicked(self, *args): + 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): + super(ErrorDisplay, self).__init__() + self.__errorBox = widgetTree.get_widget("errorEventBox") + self.__errorDescription = widgetTree.get_widget("errorDescription") + self.__errorClose = widgetTree.get_widget("errorClose") + self.__parentBox = self.__errorBox.get_parent() + + self.__errorBox.connect("button_release_event", self._on_close) + + self.__messages = [] + self.__parentBox.remove(self.__errorBox) + + def push_message_with_lock(self, message): + with gtk_lock(): + self.push_message(message) + + def push_message(self, message): + self.__messages.append(message) + if 1 == len(self.__messages): + self.__show_message(message) + + def push_exception_with_lock(self): + with gtk_lock(): + self.push_exception() + + def push_exception(self): + userMessage = str(sys.exc_info()[1]) + self.push_message(userMessage) + _moduleLogger.exception(userMessage) + + def pop_message(self): + 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() + + def __show_message(self, message): + self.__errorDescription.set_text(message) + self.__parentBox.pack_start(self.__errorBox, False, False) + self.__parentBox.reorder_child(self.__errorBox, 1) + + def __hide_message(self): + self.__errorDescription.set_text("") + self.__parentBox.remove(self.__errorBox) + + +class DummyErrorDisplay(object): + + def __init__(self): + super(DummyErrorDisplay, self).__init__() + + self.__messages = [] + + def push_message_with_lock(self, message): + self.push_message(message) + + def push_message(self, message): + if 0 < len(self.__messages): + self.__messages.append(message) + else: + self.__show_message(message) + + def push_exception(self, exception = None): + userMessage = str(sys.exc_value) + _moduleLogger.exception(userMessage) + + def pop_message(self): + if 0 < len(self.__messages): + self.__show_message(self.__messages[0]) + del self.__messages[0] + + def __show_message(self, message): + _moduleLogger.debug(message) + + +class MessageBox(gtk.MessageDialog): + + def __init__(self, message): + parent = None + gtk.MessageDialog.__init__( + self, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK, + message, + ) + self.set_default_response(gtk.RESPONSE_OK) + self.connect('response', self._handle_clicked) + + def _handle_clicked(self, *args): + self.destroy() + + +class MessageBox2(gtk.MessageDialog): + + def __init__(self, message): + parent = None + gtk.MessageDialog.__init__( + self, + parent, + gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK, + message, + ) + self.set_default_response(gtk.RESPONSE_OK) + self.connect('response', self._handle_clicked) + + def _handle_clicked(self, *args): + self.destroy() + + +class PopupCalendar(object): + + def __init__(self, parent, displayDate, title = ""): + self._displayDate = displayDate + + self._calendar = gtk.Calendar() + self._calendar.select_month(self._displayDate.month, self._displayDate.year) + self._calendar.select_day(self._displayDate.day) + self._calendar.set_display_options( + gtk.CALENDAR_SHOW_HEADING | + gtk.CALENDAR_SHOW_DAY_NAMES | + gtk.CALENDAR_NO_MONTH_CHANGE | + 0 + ) + self._calendar.connect("day-selected", self._on_day_selected) + + self._popupWindow = gtk.Window() + self._popupWindow.set_title(title) + self._popupWindow.add(self._calendar) + self._popupWindow.set_transient_for(parent) + self._popupWindow.set_modal(True) + self._popupWindow.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + self._popupWindow.set_skip_pager_hint(True) + self._popupWindow.set_skip_taskbar_hint(True) + + def run(self): + self._popupWindow.show_all() + + def _on_day_selected(self, *args): + try: + self._calendar.select_month(self._displayDate.month, self._displayDate.year) + self._calendar.select_day(self._displayDate.day) + except Exception, e: + _moduleLogger.exception(e) + + +class QuickAddView(object): + + def __init__(self, widgetTree, errorDisplay, signalSink, prefix): + self._errorDisplay = errorDisplay + self._manager = None + self._signalSink = signalSink + + self._clipboard = gtk.clipboard_get() + + self._taskNameEntry = widgetTree.get_widget(prefix+"-nameEntry") + self._addTaskButton = widgetTree.get_widget(prefix+"-addButton") + self._pasteTaskNameButton = widgetTree.get_widget(prefix+"-pasteNameButton") + self._clearTaskNameButton = widgetTree.get_widget(prefix+"-clearNameButton") + self._onAddId = None + self._onAddClickedId = None + self._onAddReleasedId = None + self._addToEditTimerId = None + self._onClearId = None + self._onPasteId = None + + def enable(self, manager): + self._manager = manager + + self._onAddId = self._addTaskButton.connect("clicked", self._on_add) + self._onAddClickedId = self._addTaskButton.connect("pressed", self._on_add_pressed) + self._onAddReleasedId = self._addTaskButton.connect("released", self._on_add_released) + self._onPasteId = self._pasteTaskNameButton.connect("clicked", self._on_paste) + self._onClearId = self._clearTaskNameButton.connect("clicked", self._on_clear) + + def disable(self): + self._manager = None + + self._addTaskButton.disconnect(self._onAddId) + self._addTaskButton.disconnect(self._onAddClickedId) + self._addTaskButton.disconnect(self._onAddReleasedId) + self._pasteTaskNameButton.disconnect(self._onPasteId) + self._clearTaskNameButton.disconnect(self._onClearId) + + def set_addability(self, addability): + self._addTaskButton.set_sensitive(addability) + + def _on_add(self, *args): + try: + name = self._taskNameEntry.get_text() + self._taskNameEntry.set_text("") + + self._signalSink.stage.send(("add", name)) + except Exception, e: + self._errorDisplay.push_exception() + + def _on_add_edit(self, *args): + try: + name = self._taskNameEntry.get_text() + self._taskNameEntry.set_text("") + + self._signalSink.stage.send(("add-edit", name)) + 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 Exception, e: + self._errorDisplay.push_exception() + + def _on_add_released(self, widget): + try: + if self._addToEditTimerId is not None: + gobject.source_remove(self._addToEditTimerId) + self._addToEditTimerId = None + except Exception, e: + self._errorDisplay.push_exception() + + def _on_paste(self, *args): + try: + entry = self._taskNameEntry.get_text() + addedText = self._clipboard.wait_for_text() + if addedText: + entry += addedText + self._taskNameEntry.set_text(entry) + except Exception, e: + self._errorDisplay.push_exception() + + def _on_clear(self, *args): + try: + self._taskNameEntry.set_text("") + 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 True: + win = gtk.Window() + win.set_title("Tap'N'Hold") + eventBox = gtk.EventBox() + win.add(eventBox) + + context = ContextHandler(eventBox, coroutines.printer_sink()) + context.enable() + win.connect("destroy", lambda w: gtk.main_quit()) + + win.show_all() + + if False: + import datetime + cal = PopupCalendar(None, datetime.datetime.now()) + cal._popupWindow.connect("destroy", lambda w: gtk.main_quit()) + cal.run() + + gtk.main()