From f7de6aaa94a6fa631543e099f4c8dab15f96944b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 6 Jul 2011 20:55:09 -0500 Subject: [PATCH] Updating from skeleton --- src/gonvert.py | 2 - src/gonvert_qt.py | 5 +- src/util/algorithms.py | 80 +++++ src/util/concurrent.py | 27 +- src/util/go_utils.py | 2 +- src/util/gtk_utils.py | 34 +++ src/util/hildonize.py | 766 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/io.py | 22 ++ src/util/misc.py | 29 ++ src/util/qore_utils.py | 82 +++--- src/util/qt_compat.py | 46 +++ src/util/qtpie.py | 25 +- src/util/qtpieboard.py | 3 +- src/util/qui_utils.py | 137 ++++++--- src/util/qwrappers.py | 97 ++++-- support/py2deb.py | 3 + 16 files changed, 1235 insertions(+), 125 deletions(-) create mode 100644 src/util/gtk_utils.py create mode 100644 src/util/hildonize.py create mode 100644 src/util/qt_compat.py diff --git a/src/gonvert.py b/src/gonvert.py index 1ee52af..10c789e 100755 --- a/src/gonvert.py +++ b/src/gonvert.py @@ -1,10 +1,8 @@ #!/usr/bin/python import sys -import logging -_moduleLogger = logging.getLogger(__name__) sys.path.append("/opt/gonvert/lib") diff --git a/src/gonvert_qt.py b/src/gonvert_qt.py index 481d9ea..58ddf58 100755 --- a/src/gonvert_qt.py +++ b/src/gonvert_qt.py @@ -12,8 +12,9 @@ import simplejson import logging import logging.handlers -from PyQt4 import QtGui -from PyQt4 import QtCore +import util.qt_compat as qt_compat +QtCore = qt_compat.QtCore +QtGui = qt_compat.import_module("QtGui") import constants from util import qui_utils diff --git a/src/util/algorithms.py b/src/util/algorithms.py index 7052b98..e94fb61 100644 --- a/src/util/algorithms.py +++ b/src/util/algorithms.py @@ -8,6 +8,8 @@ import itertools import functools import datetime import types +import array +import random def ordered_itr(collection): @@ -41,6 +43,17 @@ def itercat(*iterators): yield x +def product(*args, **kwds): + # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy + # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111 + pools = map(tuple, args) * kwds.get('repeat', 1) + result = [[]] + for pool in pools: + result = [x+[y] for x in result for y in pool] + for prod in result: + yield tuple(prod) + + def iterwhile(func, iterator): """ Iterate for as long as func(value) returns true. @@ -579,6 +592,73 @@ def itr_available(queue, initiallyBlock = False): yield queue.get_nowait() +class BloomFilter(object): + """ + http://en.wikipedia.org/wiki/Bloom_filter + Sources: + http://code.activestate.com/recipes/577684-bloom-filter/ + http://code.activestate.com/recipes/577686-bloom-filter/ + + >>> from random import sample + >>> from string import ascii_letters + >>> states = '''Alabama Alaska Arizona Arkansas California Colorado Connecticut + ... Delaware Florida Georgia Hawaii Idaho Illinois Indiana Iowa Kansas + ... Kentucky Louisiana Maine Maryland Massachusetts Michigan Minnesota + ... Mississippi Missouri Montana Nebraska Nevada NewHampshire NewJersey + ... NewMexico NewYork NorthCarolina NorthDakota Ohio Oklahoma Oregon + ... Pennsylvania RhodeIsland SouthCarolina SouthDakota Tennessee Texas Utah + ... Vermont Virginia Washington WestVirginia Wisconsin Wyoming'''.split() + >>> bf = BloomFilter(num_bits=1000, num_probes=14) + >>> for state in states: + ... bf.add(state) + >>> numStatesFound = sum(state in bf for state in states) + >>> numStatesFound, len(states) + (50, 50) + >>> trials = 100 + >>> numGarbageFound = sum(''.join(sample(ascii_letters, 5)) in bf for i in range(trials)) + >>> numGarbageFound, trials + (0, 100) + """ + + def __init__(self, num_bits, num_probes): + num_words = (num_bits + 31) // 32 + self._arr = array.array('B', [0]) * num_words + self._num_probes = num_probes + + def add(self, key): + for i, mask in self._get_probes(key): + self._arr[i] |= mask + + def union(self, bfilter): + if self._match_template(bfilter): + for i, b in enumerate(bfilter._arr): + self._arr[i] |= b + else: + # Union b/w two unrelated bloom filter raises this + raise ValueError("Mismatched bloom filters") + + def intersection(self, bfilter): + if self._match_template(bfilter): + for i, b in enumerate(bfilter._arr): + self._arr[i] &= b + else: + # Intersection b/w two unrelated bloom filter raises this + raise ValueError("Mismatched bloom filters") + + def __contains__(self, key): + return all(self._arr[i] & mask for i, mask in self._get_probes(key)) + + def _match_template(self, bfilter): + return self.num_bits == bfilter.num_bits and self.num_probes == bfilter.num_probes + + def _get_probes(self, key): + hasher = random.Random(key).randrange + for _ in range(self._num_probes): + array_index = hasher(len(self._arr)) + bit_index = hasher(32) + yield array_index, 1 << bit_index + + if __name__ == "__main__": import doctest print doctest.testmod() diff --git a/src/util/concurrent.py b/src/util/concurrent.py index a6499fe..f5f6e1d 100644 --- a/src/util/concurrent.py +++ b/src/util/concurrent.py @@ -15,12 +15,33 @@ import misc _moduleLogger = logging.getLogger(__name__) -class AsyncLinearExecution(object): +class AsyncTaskQueue(object): + + def __init__(self, taskPool): + self._asyncs = [] + self._taskPool = taskPool + + def add_async(self, func): + self.flush() + a = AsyncGeneratorTask(self._taskPool, func) + self._asyncs.append(a) + return a + + def flush(self): + self._asyncs = [a for a in self._asyncs if not a.isDone] + + +class AsyncGeneratorTask(object): def __init__(self, pool, func): self._pool = pool self._func = func self._run = None + self._isDone = False + + @property + def isDone(self): + return self._isDone def start(self, *args, **kwds): assert self._run is None, "Task already started" @@ -40,7 +61,7 @@ class AsyncLinearExecution(object): try: trampoline, args, kwds = self._run.send(result) except StopIteration, e: - pass + self._isDone = True else: self._pool.add_task( trampoline, @@ -56,7 +77,7 @@ class AsyncLinearExecution(object): try: trampoline, args, kwds = self._run.throw(error) except StopIteration, e: - pass + self._isDone = True else: self._pool.add_task( trampoline, diff --git a/src/util/go_utils.py b/src/util/go_utils.py index eaa2fe1..61e731d 100644 --- a/src/util/go_utils.py +++ b/src/util/go_utils.py @@ -138,7 +138,7 @@ class Timeout(object): _QUEUE_EMPTY = object() -class AsyncPool(object): +class FutureThread(object): def __init__(self): self.__workQueue = Queue.Queue() diff --git a/src/util/gtk_utils.py b/src/util/gtk_utils.py new file mode 100644 index 0000000..342feae --- /dev/null +++ b/src/util/gtk_utils.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +from __future__ import with_statement +from __future__ import division + +import contextlib +import logging + +import gtk + + +_moduleLogger = logging.getLogger(__name__) + + +@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 + + +if __name__ == "__main__": + pass + diff --git a/src/util/hildonize.py b/src/util/hildonize.py new file mode 100644 index 0000000..339eb2a --- /dev/null +++ b/src/util/hildonize.py @@ -0,0 +1,766 @@ +#!/usr/bin/env python + +""" +Open Issues + @bug not all of a message is shown + @bug Buttons are too small +""" + + +import gobject +import gtk +import dbus + + +class _NullHildonModule(object): + pass + + +try: + import hildon as _hildon + hildon = _hildon # Dumb but gets around pyflakiness +except (ImportError, OSError): + hildon = _NullHildonModule + + +IS_HILDON_SUPPORTED = hildon is not _NullHildonModule + + +class _NullHildonProgram(object): + + def add_window(self, window): + pass + + +def _hildon_get_app_class(): + return hildon.Program + + +def _null_get_app_class(): + return _NullHildonProgram + + +try: + hildon.Program + get_app_class = _hildon_get_app_class +except AttributeError: + get_app_class = _null_get_app_class + + +def _hildon_set_application_name(name): + gtk.set_application_name(name) + + +def _null_set_application_name(name): + pass + + +try: + gtk.set_application_name + set_application_name = _hildon_set_application_name +except AttributeError: + set_application_name = _null_set_application_name + + +def _fremantle_hildonize_window(app, window): + oldWindow = window + newWindow = hildon.StackableWindow() + if oldWindow.get_child() is not None: + oldWindow.get_child().reparent(newWindow) + app.add_window(newWindow) + return newWindow + + +def _hildon_hildonize_window(app, window): + oldWindow = window + newWindow = hildon.Window() + if oldWindow.get_child() is not None: + oldWindow.get_child().reparent(newWindow) + app.add_window(newWindow) + return newWindow + + +def _null_hildonize_window(app, window): + return window + + +try: + hildon.StackableWindow + hildonize_window = _fremantle_hildonize_window +except AttributeError: + try: + hildon.Window + hildonize_window = _hildon_hildonize_window + except AttributeError: + hildonize_window = _null_hildonize_window + + +def _fremantle_hildonize_menu(window, gtkMenu): + appMenu = hildon.AppMenu() + window.set_app_menu(appMenu) + gtkMenu.get_parent().remove(gtkMenu) + return appMenu + + +def _hildon_hildonize_menu(window, gtkMenu): + hildonMenu = gtk.Menu() + for child in gtkMenu.get_children(): + child.reparent(hildonMenu) + window.set_menu(hildonMenu) + gtkMenu.destroy() + return hildonMenu + + +def _null_hildonize_menu(window, gtkMenu): + return gtkMenu + + +try: + hildon.AppMenu + GTK_MENU_USED = False + IS_FREMANTLE_SUPPORTED = True + hildonize_menu = _fremantle_hildonize_menu +except AttributeError: + GTK_MENU_USED = True + IS_FREMANTLE_SUPPORTED = False + if IS_HILDON_SUPPORTED: + hildonize_menu = _hildon_hildonize_menu + else: + hildonize_menu = _null_hildonize_menu + + +def _hildon_set_button_auto_selectable(button): + button.set_theme_size(hildon.HILDON_SIZE_AUTO_HEIGHT) + + +def _null_set_button_auto_selectable(button): + pass + + +try: + hildon.HILDON_SIZE_AUTO_HEIGHT + gtk.Button.set_theme_size + set_button_auto_selectable = _hildon_set_button_auto_selectable +except AttributeError: + set_button_auto_selectable = _null_set_button_auto_selectable + + +def _hildon_set_button_finger_selectable(button): + button.set_theme_size(hildon.HILDON_SIZE_FINGER_HEIGHT) + + +def _null_set_button_finger_selectable(button): + pass + + +try: + hildon.HILDON_SIZE_FINGER_HEIGHT + gtk.Button.set_theme_size + set_button_finger_selectable = _hildon_set_button_finger_selectable +except AttributeError: + set_button_finger_selectable = _null_set_button_finger_selectable + + +def _hildon_set_button_thumb_selectable(button): + button.set_theme_size(hildon.HILDON_SIZE_THUMB_HEIGHT) + + +def _null_set_button_thumb_selectable(button): + pass + + +try: + hildon.HILDON_SIZE_THUMB_HEIGHT + gtk.Button.set_theme_size + set_button_thumb_selectable = _hildon_set_button_thumb_selectable +except AttributeError: + set_button_thumb_selectable = _null_set_button_thumb_selectable + + +def _hildon_set_cell_thumb_selectable(renderer): + renderer.set_property("scale", 1.5) + + +def _null_set_cell_thumb_selectable(renderer): + pass + + +if IS_HILDON_SUPPORTED: + set_cell_thumb_selectable = _hildon_set_cell_thumb_selectable +else: + set_cell_thumb_selectable = _null_set_cell_thumb_selectable + + +def _hildon_set_pix_cell_thumb_selectable(renderer): + renderer.set_property("stock-size", 48) + + +def _null_set_pix_cell_thumb_selectable(renderer): + pass + + +if IS_HILDON_SUPPORTED: + set_pix_cell_thumb_selectable = _hildon_set_pix_cell_thumb_selectable +else: + set_pix_cell_thumb_selectable = _null_set_pix_cell_thumb_selectable + + +def _fremantle_show_information_banner(parent, message): + hildon.hildon_banner_show_information(parent, "", message) + + +def _hildon_show_information_banner(parent, message): + hildon.hildon_banner_show_information(parent, None, message) + + +def _null_show_information_banner(parent, message): + pass + + +if IS_FREMANTLE_SUPPORTED: + show_information_banner = _fremantle_show_information_banner +else: + try: + hildon.hildon_banner_show_information + show_information_banner = _hildon_show_information_banner + except AttributeError: + show_information_banner = _null_show_information_banner + + +def _fremantle_show_busy_banner_start(parent, message): + hildon.hildon_gtk_window_set_progress_indicator(parent, True) + return parent + + +def _fremantle_show_busy_banner_end(banner): + hildon.hildon_gtk_window_set_progress_indicator(banner, False) + + +def _hildon_show_busy_banner_start(parent, message): + return hildon.hildon_banner_show_animation(parent, None, message) + + +def _hildon_show_busy_banner_end(banner): + banner.destroy() + + +def _null_show_busy_banner_start(parent, message): + return None + + +def _null_show_busy_banner_end(banner): + assert banner is None + + +try: + hildon.hildon_gtk_window_set_progress_indicator + show_busy_banner_start = _fremantle_show_busy_banner_start + show_busy_banner_end = _fremantle_show_busy_banner_end +except AttributeError: + try: + hildon.hildon_banner_show_animation + show_busy_banner_start = _hildon_show_busy_banner_start + show_busy_banner_end = _hildon_show_busy_banner_end + except AttributeError: + show_busy_banner_start = _null_show_busy_banner_start + show_busy_banner_end = _null_show_busy_banner_end + + +def _hildon_hildonize_text_entry(textEntry): + textEntry.set_property('hildon-input-mode', 7) + + +def _null_hildonize_text_entry(textEntry): + pass + + +if IS_HILDON_SUPPORTED: + hildonize_text_entry = _hildon_hildonize_text_entry +else: + hildonize_text_entry = _null_hildonize_text_entry + + +def _hildon_window_to_portrait(window): + # gtk documentation is unclear whether this does a "=" or a "|=" + flags = hildon.PORTRAIT_MODE_SUPPORT | hildon.PORTRAIT_MODE_REQUEST + hildon.hildon_gtk_window_set_portrait_flags(window, flags) + + +def _hildon_window_to_landscape(window): + # gtk documentation is unclear whether this does a "=" or a "&= ~" + flags = hildon.PORTRAIT_MODE_SUPPORT + hildon.hildon_gtk_window_set_portrait_flags(window, flags) + + +def _null_window_to_portrait(window): + pass + + +def _null_window_to_landscape(window): + pass + + +try: + hildon.PORTRAIT_MODE_SUPPORT + hildon.PORTRAIT_MODE_REQUEST + hildon.hildon_gtk_window_set_portrait_flags + + window_to_portrait = _hildon_window_to_portrait + window_to_landscape = _hildon_window_to_landscape +except AttributeError: + window_to_portrait = _null_window_to_portrait + window_to_landscape = _null_window_to_landscape + + +def get_device_orientation(): + bus = dbus.SystemBus() + try: + rawMceRequest = bus.get_object("com.nokia.mce", "/com/nokia/mce/request") + mceRequest = dbus.Interface(rawMceRequest, dbus_interface="com.nokia.mce.request") + orientation, standState, faceState, xAxis, yAxis, zAxis = mceRequest.get_device_orientation() + except dbus.exception.DBusException: + # catching for documentation purposes that when a system doesn't + # support this, this is what to expect + raise + + if orientation == "": + return gtk.ORIENTATION_HORIZONTAL + elif orientation == "": + return gtk.ORIENTATION_VERTICAL + else: + raise RuntimeError("Unknown orientation: %s" % orientation) + + +def _hildon_hildonize_password_entry(textEntry): + textEntry.set_property('hildon-input-mode', 7 | (1 << 29)) + + +def _null_hildonize_password_entry(textEntry): + pass + + +if IS_HILDON_SUPPORTED: + hildonize_password_entry = _hildon_hildonize_password_entry +else: + hildonize_password_entry = _null_hildonize_password_entry + + +def _hildon_hildonize_combo_entry(comboEntry): + comboEntry.set_property('hildon-input-mode', 1 << 4) + + +def _null_hildonize_combo_entry(textEntry): + pass + + +if IS_HILDON_SUPPORTED: + hildonize_combo_entry = _hildon_hildonize_combo_entry +else: + hildonize_combo_entry = _null_hildonize_combo_entry + + +def _null_create_seekbar(): + adjustment = gtk.Adjustment(0, 0, 101, 1, 5, 1) + seek = gtk.HScale(adjustment) + seek.set_draw_value(False) + return seek + + +def _fremantle_create_seekbar(): + seek = hildon.Seekbar() + seek.set_range(0.0, 100) + seek.set_draw_value(False) + seek.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + return seek + + +try: + hildon.Seekbar + create_seekbar = _fremantle_create_seekbar +except AttributeError: + create_seekbar = _null_create_seekbar + + +def _fremantle_hildonize_scrollwindow(scrolledWindow): + pannableWindow = hildon.PannableArea() + + child = scrolledWindow.get_child() + scrolledWindow.remove(child) + pannableWindow.add(child) + + parent = scrolledWindow.get_parent() + if parent is not None: + parent.remove(scrolledWindow) + parent.add(pannableWindow) + + return pannableWindow + + +def _hildon_hildonize_scrollwindow(scrolledWindow): + hildon.hildon_helper_set_thumb_scrollbar(scrolledWindow, True) + return scrolledWindow + + +def _null_hildonize_scrollwindow(scrolledWindow): + return scrolledWindow + + +try: + hildon.PannableArea + hildonize_scrollwindow = _fremantle_hildonize_scrollwindow + hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow +except AttributeError: + try: + hildon.hildon_helper_set_thumb_scrollbar + hildonize_scrollwindow = _hildon_hildonize_scrollwindow + hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow + except AttributeError: + hildonize_scrollwindow = _null_hildonize_scrollwindow + hildonize_scrollwindow_with_viewport = _null_hildonize_scrollwindow + + +def _hildon_request_number(parent, title, range, default): + spinner = hildon.NumberEditor(*range) + spinner.set_value(default) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(spinner) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + return spinner.get_value() + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +def _null_request_number(parent, title, range, default): + adjustment = gtk.Adjustment(default, range[0], range[1], 1, 5, 0) + spinner = gtk.SpinButton(adjustment, 0, 0) + spinner.set_wrap(False) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(spinner) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + return spinner.get_value_as_int() + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +try: + hildon.NumberEditor # TODO deprecated in fremantle + request_number = _hildon_request_number +except AttributeError: + request_number = _null_request_number + + +def _hildon_touch_selector(parent, title, items, defaultIndex): + model = gtk.ListStore(gobject.TYPE_STRING) + for item in items: + model.append((item, )) + + selector = hildon.TouchSelector() + selector.append_text_column(model, True) + selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE) + selector.set_active(0, defaultIndex) + + dialog = hildon.PickerDialog(parent) + dialog.set_selector(selector) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + return selector.get_active(0) + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +def _on_null_touch_selector_activated(treeView, path, column, dialog, pathData): + dialog.response(gtk.RESPONSE_OK) + pathData[0] = path + + +def _null_touch_selector(parent, title, items, defaultIndex = -1): + parentSize = parent.get_size() + + model = gtk.ListStore(gobject.TYPE_STRING) + for item in items: + model.append((item, )) + + cell = gtk.CellRendererText() + set_cell_thumb_selectable(cell) + column = gtk.TreeViewColumn(title) + column.pack_start(cell, expand=True) + column.add_attribute(cell, "text", 0) + + treeView = gtk.TreeView() + treeView.set_model(model) + treeView.append_column(column) + selection = treeView.get_selection() + selection.set_mode(gtk.SELECTION_SINGLE) + if 0 < defaultIndex: + selection.select_path((defaultIndex, )) + + scrolledWin = gtk.ScrolledWindow() + scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolledWin.add(treeView) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(scrolledWin) + dialog.resize(parentSize[0], max(parentSize[1]-100, 100)) + + scrolledWin = hildonize_scrollwindow(scrolledWin) + pathData = [None] + treeView.connect("row-activated", _on_null_touch_selector_activated, dialog, pathData) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + if pathData[0] is None: + raise RuntimeError("No selection made") + return pathData[0][0] + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +try: + hildon.PickerDialog + hildon.TouchSelector + touch_selector = _hildon_touch_selector +except AttributeError: + touch_selector = _null_touch_selector + + +def _hildon_touch_selector_entry(parent, title, items, defaultItem): + # Got a segfault when using append_text_column with TouchSelectorEntry, so using this way + try: + selector = hildon.TouchSelectorEntry(text=True) + except TypeError: + selector = hildon.hildon_touch_selector_entry_new_text() + defaultIndex = -1 + for i, item in enumerate(items): + selector.append_text(item) + if item == defaultItem: + defaultIndex = i + + dialog = hildon.PickerDialog(parent) + dialog.set_selector(selector) + + if 0 < defaultIndex: + selector.set_active(0, defaultIndex) + else: + selector.get_entry().set_text(defaultItem) + + try: + dialog.show_all() + response = dialog.run() + finally: + dialog.hide() + + if response == gtk.RESPONSE_OK: + return selector.get_entry().get_text() + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + + +def _on_null_touch_selector_entry_entry_changed(entry, result, selection, defaultIndex): + custom = entry.get_text().strip() + if custom: + result[0] = custom + selection.unselect_all() + else: + result[0] = None + selection.select_path((defaultIndex, )) + + +def _on_null_touch_selector_entry_entry_activated(customEntry, dialog, result): + dialog.response(gtk.RESPONSE_OK) + result[0] = customEntry.get_text() + + +def _on_null_touch_selector_entry_tree_activated(treeView, path, column, dialog, result): + dialog.response(gtk.RESPONSE_OK) + model = treeView.get_model() + itr = model.get_iter(path) + if itr is not None: + result[0] = model.get_value(itr, 0) + + +def _null_touch_selector_entry(parent, title, items, defaultItem): + parentSize = parent.get_size() + + model = gtk.ListStore(gobject.TYPE_STRING) + defaultIndex = -1 + for i, item in enumerate(items): + model.append((item, )) + if item == defaultItem: + defaultIndex = i + + cell = gtk.CellRendererText() + set_cell_thumb_selectable(cell) + column = gtk.TreeViewColumn(title) + column.pack_start(cell, expand=True) + column.add_attribute(cell, "text", 0) + + treeView = gtk.TreeView() + treeView.set_model(model) + treeView.append_column(column) + selection = treeView.get_selection() + selection.set_mode(gtk.SELECTION_SINGLE) + + scrolledWin = gtk.ScrolledWindow() + scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolledWin.add(treeView) + + customEntry = gtk.Entry() + + layout = gtk.VBox() + layout.pack_start(customEntry, expand=False) + layout.pack_start(scrolledWin) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(layout) + dialog.resize(parentSize[0], max(parentSize[1]-100, 100)) + + scrolledWin = hildonize_scrollwindow(scrolledWin) + + result = [None] + if 0 < defaultIndex: + selection.select_path((defaultIndex, )) + result[0] = defaultItem + else: + customEntry.set_text(defaultItem) + + customEntry.connect("activate", _on_null_touch_selector_entry_entry_activated, dialog, result) + customEntry.connect("changed", _on_null_touch_selector_entry_entry_changed, result, selection, defaultIndex) + treeView.connect("row-activated", _on_null_touch_selector_entry_tree_activated, dialog, result) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + _, itr = selection.get_selected() + if itr is not None: + return model.get_value(itr, 0) + else: + enteredText = customEntry.get_text().strip() + if enteredText: + return enteredText + elif result[0] is not None: + return result[0] + else: + raise RuntimeError("No selection made") + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +try: + hildon.PickerDialog + hildon.TouchSelectorEntry + touch_selector_entry = _hildon_touch_selector_entry +except AttributeError: + touch_selector_entry = _null_touch_selector_entry + + +if __name__ == "__main__": + app = get_app_class()() + + label = gtk.Label("Hello World from a Label!") + + win = gtk.Window() + win.add(label) + win = hildonize_window(app, win) + if False and IS_FREMANTLE_SUPPORTED: + appMenu = hildon.AppMenu() + for i in xrange(5): + b = gtk.Button(str(i)) + appMenu.append(b) + win.set_app_menu(appMenu) + win.show_all() + appMenu.show_all() + gtk.main() + elif False: + print touch_selector(win, "Test", ["A", "B", "C", "D"], 2) + elif False: + print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C") + print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah") + elif False: + import pprint + name, value = "", "" + goodLocals = [ + (name, value) for (name, value) in locals().iteritems() + if not name.startswith("_") + ] + pprint.pprint(goodLocals) + elif False: + import time + show_information_banner(win, "Hello World") + time.sleep(5) + elif False: + import time + banner = show_busy_banner_start(win, "Hello World") + time.sleep(5) + show_busy_banner_end(banner) diff --git a/src/util/io.py b/src/util/io.py index aac896d..4198f4b 100644 --- a/src/util/io.py +++ b/src/util/io.py @@ -8,6 +8,7 @@ import pickle import contextlib import itertools import codecs +from xml.sax import saxutils import csv try: import cStringIO as StringIO @@ -207,3 +208,24 @@ def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs): def utf_8_encoder(unicode_csv_data): for line in unicode_csv_data: yield line.encode('utf-8') + + +_UNESCAPE_ENTITIES = { + """: '"', + " ": " ", + "'": "'", +} + + +_ESCAPE_ENTITIES = dict((v, k) for (v, k) in zip(_UNESCAPE_ENTITIES.itervalues(), _UNESCAPE_ENTITIES.iterkeys())) +del _ESCAPE_ENTITIES[" "] + + +def unescape(text): + plain = saxutils.unescape(text, _UNESCAPE_ENTITIES) + return plain + + +def escape(text): + fancy = saxutils.escape(text, _ESCAPE_ENTITIES) + return fancy diff --git a/src/util/misc.py b/src/util/misc.py index c0a70a9..9b8d88c 100644 --- a/src/util/misc.py +++ b/src/util/misc.py @@ -649,6 +649,35 @@ def call_trace(f): @contextlib.contextmanager +def nested_break(): + """ + >>> with nested_break() as mylabel: + ... for i in xrange(3): + ... print "Outer", i + ... for j in xrange(3): + ... if i == 2: raise mylabel + ... if j == 2: break + ... print "Inner", j + ... print "more processing" + Outer 0 + Inner 0 + Inner 1 + Outer 1 + Inner 0 + Inner 1 + Outer 2 + """ + + class NestedBreakException(Exception): + pass + + try: + yield NestedBreakException + except NestedBreakException: + pass + + +@contextlib.contextmanager def lexical_scope(*args): """ @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/520586 diff --git a/src/util/qore_utils.py b/src/util/qore_utils.py index 491c96d..153558d 100644 --- a/src/util/qore_utils.py +++ b/src/util/qore_utils.py @@ -1,6 +1,7 @@ import logging -from PyQt4 import QtCore +import qt_compat +QtCore = qt_compat.QtCore import misc @@ -22,40 +23,23 @@ class QThread44(QtCore.QThread): self.exec_() -class _ParentThread(QtCore.QObject): - - def __init__(self, pool): - QtCore.QObject.__init__(self) - self._pool = pool - - @QtCore.pyqtSlot(object) - @misc.log_exception(_moduleLogger) - def _on_task_complete(self, taskResult): - on_success, on_error, isError, result = taskResult - if not self._pool._isRunning: - if isError: - _moduleLogger.error("Masking: %s" % (result, )) - isError = True - result = StopIteration("Cancelling all callbacks") - callback = on_success if not isError else on_error - try: - callback(result) - except Exception: - _moduleLogger.exception("Callback errored") - - class _WorkerThread(QtCore.QObject): - taskComplete = QtCore.pyqtSignal(object) + _taskComplete = qt_compat.Signal(object) - def __init__(self, pool): + def __init__(self, futureThread): QtCore.QObject.__init__(self) - self._pool = pool + self._futureThread = futureThread + self._futureThread._addTask.connect(self._on_task_added) + self._taskComplete.connect(self._futureThread._on_task_complete) - @QtCore.pyqtSlot(object) - @misc.log_exception(_moduleLogger) + @qt_compat.Slot(object) def _on_task_added(self, task): - if not self._pool._isRunning: + self.__on_task_added(task) + + @misc.log_exception(_moduleLogger) + def __on_task_added(self, task): + if not self._futureThread._isRunning: _moduleLogger.error("Dropping task") func, args, kwds, on_success, on_error = task @@ -69,39 +53,47 @@ class _WorkerThread(QtCore.QObject): isError = True taskResult = on_success, on_error, isError, result - self.taskComplete.emit(taskResult) - - @QtCore.pyqtSlot() - @misc.log_exception(_moduleLogger) - def _on_stop_requested(self): - self._pool._thread.quit() + self._taskComplete.emit(taskResult) -class AsyncPool(QtCore.QObject): +class FutureThread(QtCore.QObject): - _addTask = QtCore.pyqtSignal(object) - _stopPool = QtCore.pyqtSignal() + _addTask = qt_compat.Signal(object) def __init__(self): QtCore.QObject.__init__(self) self._thread = QThread44() - self._isRunning = True - self._parent = _ParentThread(self) + self._isRunning = False self._worker = _WorkerThread(self) self._worker.moveToThread(self._thread) - self._addTask.connect(self._worker._on_task_added) - self._worker.taskComplete.connect(self._parent._on_task_complete) - self._stopPool.connect(self._worker._on_stop_requested) - def start(self): self._thread.start() + self._isRunning = True def stop(self): self._isRunning = False - self._stopPool.emit() + self._thread.quit() def add_task(self, func, args, kwds, on_success, on_error): assert self._isRunning, "Task queue not started" task = func, args, kwds, on_success, on_error self._addTask.emit(task) + + @qt_compat.Slot(object) + def _on_task_complete(self, taskResult): + self.__on_task_complete(taskResult) + + @misc.log_exception(_moduleLogger) + def __on_task_complete(self, taskResult): + on_success, on_error, isError, result = taskResult + if not self._isRunning: + if isError: + _moduleLogger.error("Masking: %s" % (result, )) + isError = True + result = StopIteration("Cancelling all callbacks") + callback = on_success if not isError else on_error + try: + callback(result) + except Exception: + _moduleLogger.exception("Callback errored") diff --git a/src/util/qt_compat.py b/src/util/qt_compat.py new file mode 100644 index 0000000..2ab7fa4 --- /dev/null +++ b/src/util/qt_compat.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +from __future__ import with_statement +from __future__ import division + +#try: +# import PySide.QtCore as _QtCore +# QtCore = _QtCore +# USES_PYSIDE = True +#except ImportError: +if True: + import sip + sip.setapi('QString', 2) + sip.setapi('QVariant', 2) + import PyQt4.QtCore as _QtCore + QtCore = _QtCore + USES_PYSIDE = False + + +def _pyside_import_module(moduleName): + pyside = __import__('PySide', globals(), locals(), [moduleName], -1) + return getattr(pyside, moduleName) + + +def _pyqt4_import_module(moduleName): + pyside = __import__('PyQt4', globals(), locals(), [moduleName], -1) + return getattr(pyside, moduleName) + + +if USES_PYSIDE: + import_module = _pyside_import_module + + Signal = QtCore.Signal + Slot = QtCore.Slot + Property = QtCore.Property +else: + import_module = _pyqt4_import_module + + Signal = QtCore.pyqtSignal + Slot = QtCore.pyqtSlot + Property = QtCore.pyqtProperty + + +if __name__ == "__main__": + pass + diff --git a/src/util/qtpie.py b/src/util/qtpie.py index c23e512..6b77d5d 100755 --- a/src/util/qtpie.py +++ b/src/util/qtpie.py @@ -3,8 +3,9 @@ import math import logging -from PyQt4 import QtGui -from PyQt4 import QtCore +import qt_compat +QtCore = qt_compat.QtCore +QtGui = qt_compat.import_module("QtGui") import misc as misc_utils @@ -496,11 +497,11 @@ class QPieDisplay(QtGui.QWidget): class QPieButton(QtGui.QWidget): - activated = QtCore.pyqtSignal(int) - highlighted = QtCore.pyqtSignal(int) - canceled = QtCore.pyqtSignal() - aboutToShow = QtCore.pyqtSignal() - aboutToHide = QtCore.pyqtSignal() + activated = qt_compat.Signal(int) + highlighted = qt_compat.Signal(int) + canceled = qt_compat.Signal() + aboutToShow = qt_compat.Signal() + aboutToHide = qt_compat.Signal() BUTTON_RADIUS = 24 DELAY = 250 @@ -778,11 +779,11 @@ class QPieButton(QtGui.QWidget): class QPieMenu(QtGui.QWidget): - activated = QtCore.pyqtSignal(int) - highlighted = QtCore.pyqtSignal(int) - canceled = QtCore.pyqtSignal() - aboutToShow = QtCore.pyqtSignal() - aboutToHide = QtCore.pyqtSignal() + activated = qt_compat.Signal(int) + highlighted = qt_compat.Signal(int) + canceled = qt_compat.Signal() + aboutToShow = qt_compat.Signal() + aboutToHide = qt_compat.Signal() def __init__(self, parent = None): QtGui.QWidget.__init__(self, parent) diff --git a/src/util/qtpieboard.py b/src/util/qtpieboard.py index c7094f4..50ae9ae 100755 --- a/src/util/qtpieboard.py +++ b/src/util/qtpieboard.py @@ -6,7 +6,8 @@ from __future__ import division import os import warnings -from PyQt4 import QtGui +import qt_compat +QtGui = qt_compat.import_module("QtGui") import qtpie diff --git a/src/util/qui_utils.py b/src/util/qui_utils.py index 56a3408..11b3453 100644 --- a/src/util/qui_utils.py +++ b/src/util/qui_utils.py @@ -3,8 +3,9 @@ import contextlib import datetime import logging -from PyQt4 import QtCore -from PyQt4 import QtGui +import qt_compat +QtCore = qt_compat.QtCore +QtGui = qt_compat.import_module("QtGui") import misc @@ -54,8 +55,8 @@ class ErrorMessage(object): class QErrorLog(QtCore.QObject): - messagePushed = QtCore.pyqtSignal() - messagePopped = QtCore.pyqtSignal() + messagePushed = qt_compat.Signal() + messagePopped = qt_compat.Signal() def __init__(self): QtCore.QObject.__init__(self) @@ -113,21 +114,7 @@ class ErrorDisplay(object): self._errorLog.messagePushed.connect(self._on_message_pushed) self._errorLog.messagePopped.connect(self._on_message_popped) - self._icons = { - ErrorMessage.LEVEL_BUSY: - get_theme_icon( - #("process-working", "view-refresh", "general_refresh", "gtk-refresh") - ("view-refresh", "general_refresh", "gtk-refresh", ) - ).pixmap(32, 32), - ErrorMessage.LEVEL_INFO: - get_theme_icon( - ("dialog-information", "general_notes", "gtk-info") - ).pixmap(32, 32), - ErrorMessage.LEVEL_ERROR: - get_theme_icon( - ("dialog-error", "app_install_error", "gtk-dialog-error") - ).pixmap(32, 32), - } + self._icons = None self._severityLabel = QtGui.QLabel() self._severityLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) @@ -136,17 +123,11 @@ class ErrorDisplay(object): self._message.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) self._message.setWordWrap(True) - closeIcon = get_theme_icon(("window-close", "general_close", "gtk-close"), self._SENTINEL_ICON) - if closeIcon is not self._SENTINEL_ICON: - self._closeLabel = QtGui.QPushButton(closeIcon, "") - else: - self._closeLabel = QtGui.QPushButton("X") - self._closeLabel.clicked.connect(self._on_close) + self._closeLabel = None self._controlLayout = QtGui.QHBoxLayout() self._controlLayout.addWidget(self._severityLabel, 1, QtCore.Qt.AlignCenter) self._controlLayout.addWidget(self._message, 1000) - self._controlLayout.addWidget(self._closeLabel, 1, QtCore.Qt.AlignCenter) self._widget = QtGui.QWidget() self._widget.setLayout(self._controlLayout) @@ -157,23 +138,47 @@ class ErrorDisplay(object): return self._widget def _show_error(self): + if self._icons is None: + self._icons = { + ErrorMessage.LEVEL_BUSY: + get_theme_icon( + #("process-working", "view-refresh", "general_refresh", "gtk-refresh") + ("view-refresh", "general_refresh", "gtk-refresh", ) + ).pixmap(32, 32), + ErrorMessage.LEVEL_INFO: + get_theme_icon( + ("dialog-information", "general_notes", "gtk-info") + ).pixmap(32, 32), + ErrorMessage.LEVEL_ERROR: + get_theme_icon( + ("dialog-error", "app_install_error", "gtk-dialog-error") + ).pixmap(32, 32), + } + if self._closeLabel is None: + closeIcon = get_theme_icon(("window-close", "general_close", "gtk-close"), self._SENTINEL_ICON) + if closeIcon is not self._SENTINEL_ICON: + self._closeLabel = QtGui.QPushButton(closeIcon, "") + else: + self._closeLabel = QtGui.QPushButton("X") + self._closeLabel.clicked.connect(self._on_close) + self._controlLayout.addWidget(self._closeLabel, 1, QtCore.Qt.AlignCenter) error = self._errorLog.peek_message() self._message.setText(error.message) self._severityLabel.setPixmap(self._icons[error.level]) self._widget.show() - @QtCore.pyqtSlot() - @QtCore.pyqtSlot(bool) + @qt_compat.Slot() + @qt_compat.Slot(bool) @misc.log_exception(_moduleLogger) def _on_close(self, checked = False): self._errorLog.pop() - @QtCore.pyqtSlot() + @qt_compat.Slot() @misc.log_exception(_moduleLogger) def _on_message_pushed(self): self._show_error() - @QtCore.pyqtSlot() + @qt_compat.Slot() @misc.log_exception(_moduleLogger) def _on_message_popped(self): if len(self._errorLog) == 0: @@ -231,9 +236,17 @@ class QHtmlDelegate(QtGui.QStyledItemDelegate): doc.documentLayout().draw(painter, ctx) painter.restore() - def setWidth(self, width): - # @bug we need to be emitting sizeHintChanged but it requires an index + def setWidth(self, width, model): + if self._width == width: + return self._width = width + for c in xrange(model.rowCount()): + cItem = model.item(c, 0) + for r in xrange(model.rowCount()): + rItem = cItem.child(r, 0) + rIndex = model.indexFromItem(rItem) + self.sizeHintChanged.emit(rIndex) + return def sizeHint(self, option, index): newOption = QtGui.QStyleOptionViewItemV4(option) @@ -250,6 +263,45 @@ class QHtmlDelegate(QtGui.QStyledItemDelegate): return size +class QSignalingMainWindow(QtGui.QMainWindow): + + closed = qt_compat.Signal() + hidden = qt_compat.Signal() + shown = qt_compat.Signal() + resized = qt_compat.Signal() + + def __init__(self, *args, **kwd): + QtGui.QMainWindow.__init__(*((self, )+args), **kwd) + + def closeEvent(self, event): + val = QtGui.QMainWindow.closeEvent(self, event) + self.closed.emit() + return val + + def hideEvent(self, event): + val = QtGui.QMainWindow.hideEvent(self, event) + self.hidden.emit() + return val + + def showEvent(self, event): + val = QtGui.QMainWindow.showEvent(self, event) + self.shown.emit() + return val + + def resizeEvent(self, event): + val = QtGui.QMainWindow.resizeEvent(self, event) + self.resized.emit() + return val + +def set_current_index(selector, itemText, default = 0): + for i in xrange(selector.count()): + if selector.itemText(i) == itemText: + selector.setCurrentIndex(i) + break + else: + itemText.setCurrentIndex(default) + + def _null_set_stackable(window, isStackable): pass @@ -265,12 +317,12 @@ except AttributeError: set_stackable = _null_set_stackable -def _null_set_autorient(window, isStackable): +def _null_set_autorient(window, doAutoOrient): pass -def _maemo_set_autorient(window, isStackable): - window.setAttribute(QtCore.Qt.WA_Maemo5AutoOrientation, isStackable) +def _maemo_set_autorient(window, doAutoOrient): + window.setAttribute(QtCore.Qt.WA_Maemo5AutoOrientation, doAutoOrient) try: @@ -294,13 +346,16 @@ def _null_set_window_orientation(window, orientation): def _maemo_set_window_orientation(window, orientation): if orientation == QtCore.Qt.Vertical: - oldHint = QtCore.Qt.WA_Maemo5LandscapeOrientation - newHint = QtCore.Qt.WA_Maemo5PortraitOrientation + window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, False) + window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, True) elif orientation == QtCore.Qt.Horizontal: - oldHint = QtCore.Qt.WA_Maemo5PortraitOrientation - newHint = QtCore.Qt.WA_Maemo5LandscapeOrientation - window.setAttribute(oldHint, False) - window.setAttribute(newHint, True) + window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, True) + window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, False) + elif orientation is None: + window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, False) + window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, False) + else: + raise RuntimeError("Unknown orientation: %r" % orientation) try: diff --git a/src/util/qwrappers.py b/src/util/qwrappers.py index b84e8ff..2c50c8a 100644 --- a/src/util/qwrappers.py +++ b/src/util/qwrappers.py @@ -5,8 +5,9 @@ from __future__ import division import logging -from PyQt4 import QtGui -from PyQt4 import QtCore +import qt_compat +QtCore = qt_compat.QtCore +QtGui = qt_compat.import_module("QtGui") from util import qui_utils from util import misc as misc_utils @@ -17,6 +18,11 @@ _moduleLogger = logging.getLogger(__name__) class ApplicationWrapper(object): + DEFAULT_ORIENTATION = "Default" + AUTO_ORIENTATION = "Auto" + LANDSCAPE_ORIENTATION = "Landscape" + PORTRAIT_ORIENTATION = "Portrait" + def __init__(self, qapp, constants): self._constants = constants self._qapp = qapp @@ -31,11 +37,12 @@ class ApplicationWrapper(object): self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter")) self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen) + self._orientation = self.DEFAULT_ORIENTATION self._orientationAction = QtGui.QAction(None) - self._orientationAction.setText("Orientation") + self._orientationAction.setText("Next Orientation") self._orientationAction.setCheckable(True) self._orientationAction.setShortcut(QtGui.QKeySequence("CTRL+o")) - self._orientationAction.toggled.connect(self._on_toggle_orientation) + self._orientationAction.triggered.connect(self._on_next_orientation) self._logAction = QtGui.QAction(None) self._logAction.setText("Log") @@ -61,7 +68,7 @@ class ApplicationWrapper(object): self._idleDelay = QtCore.QTimer() self._idleDelay.setSingleShot(True) self._idleDelay.setInterval(0) - self._idleDelay.timeout.connect(lambda: self._mainWindow.start()) + self._idleDelay.timeout.connect(self._on_delayed_start) self._idleDelay.start() def load_settings(self): @@ -94,6 +101,10 @@ class ApplicationWrapper(object): return self._orientationAction @property + def orientation(self): + return self._orientation + + @property def logAction(self): return self._logAction @@ -105,6 +116,19 @@ class ApplicationWrapper(object): def quitAction(self): return self._quitAction + def set_orientation(self, orientation): + self._orientation = orientation + self._mainWindow.update_orientation(self._orientation) + + @classmethod + def _next_orientation(cls, current): + return { + cls.DEFAULT_ORIENTATION: cls.AUTO_ORIENTATION, + cls.AUTO_ORIENTATION: cls.LANDSCAPE_ORIENTATION, + cls.LANDSCAPE_ORIENTATION: cls.PORTRAIT_ORIENTATION, + cls.PORTRAIT_ORIENTATION: cls.DEFAULT_ORIENTATION, + }[current] + def _close_windows(self): if self._mainWindow is not None: self.save_settings() @@ -113,6 +137,10 @@ class ApplicationWrapper(object): self._mainWindow = None @misc_utils.log_exception(_moduleLogger) + def _on_delayed_start(self): + self._mainWindow.start() + + @misc_utils.log_exception(_moduleLogger) def _on_app_quit(self, checked = False): if self._mainWindow is not None: self.save_settings() @@ -130,9 +158,9 @@ class ApplicationWrapper(object): self._mainWindow.set_fullscreen(checked) @misc_utils.log_exception(_moduleLogger) - def _on_toggle_orientation(self, checked = False): + def _on_next_orientation(self, checked = False): with qui_utils.notify_error(self._errorLog): - self._mainWindow.set_orientation(checked) + self.set_orientation(self._next_orientation(self._orientation)) @misc_utils.log_exception(_moduleLogger) def _on_about(self, checked = True): @@ -171,7 +199,7 @@ class WindowWrapper(object): centralWidget.setLayout(self._superLayout) centralWidget.setContentsMargins(0, 0, 0, 0) - self._window = QtGui.QMainWindow(parent) + self._window = qui_utils.QSignalingMainWindow(parent) self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) qui_utils.set_stackable(self._window, True) self._window.setCentralWidget(centralWidget) @@ -191,6 +219,28 @@ class WindowWrapper(object): def window(self): return self._window + @property + def windowOrientation(self): + geom = self._window.size() + if geom.width() <= geom.height(): + return QtCore.Qt.Vertical + else: + return QtCore.Qt.Horizontal + + @property + def idealWindowOrientation(self): + if self._app.orientation == self._app.AUTO_ORIENTATION: + windowOrientation = self.windowOrientation + elif self._app.orientation == self._app.DEFAULT_ORIENTATION: + windowOrientation = qui_utils.screen_orientation() + elif self._app.orientation == self._app.LANDSCAPE_ORIENTATION: + windowOrientation = QtCore.Qt.Horizontal + elif self._app.orientation == self._app.PORTRAIT_ORIENTATION: + windowOrientation = QtCore.Qt.Vertical + else: + raise RuntimeError("Bad! No %r for you" % self._app.orientation) + return windowOrientation + def walk_children(self): return () @@ -207,10 +257,10 @@ class WindowWrapper(object): pass def show(self): - self.set_fullscreen(self._app.fullscreenAction.isChecked()) self._window.show() for child in self.walk_children(): child.show() + self.set_fullscreen(self._app.fullscreenAction.isChecked()) def hide(self): for child in self.walk_children(): @@ -218,23 +268,34 @@ class WindowWrapper(object): self._window.hide() def set_fullscreen(self, isFullscreen): - if isFullscreen: - self._window.showFullScreen() - else: - self._window.showNormal() + if self._window.isVisible(): + if isFullscreen: + self._window.showFullScreen() + else: + self._window.showNormal() for child in self.walk_children(): child.set_fullscreen(isFullscreen) - def set_orientation(self, isPortrait): - if isPortrait: + def update_orientation(self, orientation): + if orientation == self._app.DEFAULT_ORIENTATION: + qui_utils.set_autorient(self.window, False) + qui_utils.set_window_orientation(self.window, None) + elif orientation == self._app.AUTO_ORIENTATION: + qui_utils.set_autorient(self.window, True) + qui_utils.set_window_orientation(self.window, None) + elif orientation == self._app.LANDSCAPE_ORIENTATION: + qui_utils.set_autorient(self.window, False) + qui_utils.set_window_orientation(self.window, QtCore.Qt.Horizontal) + elif orientation == self._app.PORTRAIT_ORIENTATION: + qui_utils.set_autorient(self.window, False) qui_utils.set_window_orientation(self.window, QtCore.Qt.Vertical) else: - qui_utils.set_window_orientation(self.window, QtCore.Qt.Horizontal) + raise RuntimeError("Unknown orientation: %r" % orientation) for child in self.walk_children(): - child.set_orientation(isPortrait) + child.update_orientation(orientation) @misc_utils.log_exception(_moduleLogger) - def _on_child_close(self): + def _on_child_close(self, obj = None): raise NotImplementedError("Booh") @misc_utils.log_exception(_moduleLogger) diff --git a/support/py2deb.py b/support/py2deb.py index 4a47513..0518480 100644 --- a/support/py2deb.py +++ b/support/py2deb.py @@ -473,6 +473,9 @@ class Py2deb(object): self.__files[path]=nfiles + def __getitem__(self, k): + return self.__files[k] + def __delitem__(self, k): del self.__files[k] -- 1.7.9.5