Updating from Maemo skeleton
authorEd Page <eopage@byu.net>
Thu, 13 Jan 2011 04:01:52 +0000 (22:01 -0600)
committerEd Page <eopage@byu.net>
Thu, 13 Jan 2011 04:01:52 +0000 (22:01 -0600)
12 files changed:
src/gonvert_qt.py
src/maeqt.py [deleted file]
src/util/concurrent.py
src/util/go_utils.py
src/util/misc.py
src/util/qore_utils.py [new file with mode: 0644]
src/util/qtpie.py
src/util/qtpieboard.py
src/util/qui_utils.py [new file with mode: 0644]
src/util/qwrappers.py [new file with mode: 0644]
src/util/tp_utils.py
src/windows.py

index 36ed518..ab1824c 100755 (executable)
@@ -15,7 +15,7 @@ from PyQt4 import QtGui
 from PyQt4 import QtCore
 
 import constants
-import maeqt
+from util import qui_utils
 from util import misc as misc_utils
 import unit_data
 
@@ -424,12 +424,12 @@ class QuickConvert(object):
                self._favoritesWindow = None
 
                self._inputUnitValue = QtGui.QLineEdit()
-               maeqt.mark_numbers_preferred(self._inputUnitValue)
+               qui_utils.mark_numbers_preferred(self._inputUnitValue)
                self._inputUnitValue.textEdited.connect(self._on_value_edited)
                self._inputUnitSymbol = QtGui.QLabel()
 
                self._outputUnitValue = QtGui.QLineEdit()
-               maeqt.mark_numbers_preferred(self._outputUnitValue)
+               qui_utils.mark_numbers_preferred(self._outputUnitValue)
                self._outputUnitValue.textEdited.connect(self._on_output_value_edited)
                self._outputUnitSymbol = QtGui.QLabel()
 
@@ -492,8 +492,7 @@ class QuickConvert(object):
 
                self._window = QtGui.QMainWindow(parent)
                self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
-               maeqt.set_autorient(self._window, True)
-               maeqt.set_stackable(self._window, True)
+               qui_utils.set_stackable(self._window, True)
                self._window.setWindowTitle("%s - Quick Convert" % (constants.__pretty_app_name__, ))
                self._window.setWindowIcon(QtGui.QIcon(app.appIconPath))
                self._window.setCentralWidget(centralWidget)
@@ -865,8 +864,7 @@ class CategoryWindow(object):
 
                self._window = QtGui.QMainWindow(parent)
                self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
-               maeqt.set_autorient(self._window, True)
-               maeqt.set_stackable(self._window, True)
+               qui_utils.set_stackable(self._window, True)
                self._window.setWindowTitle("%s - Categories" % constants.__pretty_app_name__)
                self._window.setWindowIcon(QtGui.QIcon(self._app.appIconPath))
                self._window.setCentralWidget(centralWidget)
@@ -1251,7 +1249,7 @@ class UnitWindow(object):
                self._selectedUnitName = QtGui.QLabel()
                self._selectedUnitValue = QtGui.QLineEdit()
                self._selectedUnitValue.textEdited.connect(self._on_value_edited)
-               maeqt.mark_numbers_preferred(self._selectedUnitValue)
+               qui_utils.mark_numbers_preferred(self._selectedUnitValue)
                self._selectedUnitSymbol = QtGui.QLabel()
                self._updateDelayTimer = QtCore.QTimer()
                self._updateDelayTimer.setInterval(100)
@@ -1304,8 +1302,7 @@ class UnitWindow(object):
 
                self._window = QtGui.QMainWindow(parent)
                self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
-               maeqt.set_autorient(self._window, True)
-               maeqt.set_stackable(self._window, True)
+               qui_utils.set_stackable(self._window, True)
                self._window.setWindowTitle("%s - %s" % (constants.__pretty_app_name__, category))
                self._window.setWindowIcon(QtGui.QIcon(app.appIconPath))
                self._window.setCentralWidget(centralWidget)
diff --git a/src/maeqt.py b/src/maeqt.py
deleted file mode 100644 (file)
index d5eb18b..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-from PyQt4 import QtCore
-from PyQt4 import QtGui
-
-
-def _null_set_stackable(window, isStackable):
-       pass
-
-
-def _maemo_set_stackable(window, isStackable):
-       window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
-
-
-try:
-       QtCore.Qt.WA_Maemo5StackedWindow
-       set_stackable = _maemo_set_stackable
-except AttributeError:
-       set_stackable = _null_set_stackable
-
-
-def _null_set_autorient(window, isStackable):
-       pass
-
-
-def _maemo_set_autorient(window, isStackable):
-       window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
-
-
-try:
-       QtCore.Qt.WA_Maemo5AutoOrientation
-       set_autorient = _maemo_set_autorient
-except AttributeError:
-       set_autorient = _null_set_autorient
-
-
-def _null_set_landscape(window, isStackable):
-       pass
-
-
-def _maemo_set_landscape(window, isStackable):
-       window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
-
-
-try:
-       QtCore.Qt.WA_Maemo5LandscapeOrientation
-       set_landscape = _maemo_set_landscape
-except AttributeError:
-       set_landscape = _null_set_landscape
-
-
-def _null_set_portrait(window, isStackable):
-       pass
-
-
-def _maemo_set_portrait(window, isStackable):
-       window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
-
-
-try:
-       QtCore.Qt.WA_Maemo5PortraitOrientation
-       set_portrait = _maemo_set_portrait
-except AttributeError:
-       set_portrait = _null_set_portrait
-
-
-def _null_show_progress_indicator(window, isStackable):
-       pass
-
-
-def _maemo_show_progress_indicator(window, isStackable):
-       window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
-
-
-try:
-       QtCore.Qt.WA_Maemo5ShowProgressIndicator
-       show_progress_indicator = _maemo_show_progress_indicator
-except AttributeError:
-       show_progress_indicator = _null_show_progress_indicator
-
-
-def _null_mark_numbers_preferred(widget):
-       pass
-
-
-def _newqt_mark_numbers_preferred(widget):
-       widget.setInputMethodHints(QtCore.Qt.ImhPreferNumbers)
-
-
-try:
-       QtCore.Qt.ImhPreferNumbers
-       mark_numbers_preferred = _newqt_mark_numbers_preferred
-except AttributeError:
-       mark_numbers_preferred = _null_mark_numbers_preferred
-
-
-def screen_orientation():
-       geom = QtGui.QApplication.desktop().screenGeometry()
-       if geom.width() <= geom.height():
-               return QtCore.Qt.Vertical
-       else:
-               return QtCore.Qt.Horizontal
-
-
-def _null_get_theme_icon(iconNames, fallback = None):
-       icon = fallback if fallback is not None else QtGui.QIcon()
-       return icon
-
-
-def _newqt_get_theme_icon(iconNames, fallback = None):
-       for iconName in iconNames:
-               if QtGui.QIcon.hasThemeIcon(iconName):
-                       icon = QtGui.QIcon.fromTheme(iconName)
-                       break
-       else:
-               icon = fallback if fallback is not None else QtGui.QIcon()
-       return icon
-
-
-try:
-       QtGui.QIcon.fromTheme
-       get_theme_icon = _newqt_get_theme_icon
-except AttributeError:
-       get_theme_icon = _null_get_theme_icon
index 503a1b4..a6499fe 100644 (file)
@@ -7,6 +7,76 @@ import errno
 import time
 import functools
 import contextlib
+import logging
+
+import misc
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class AsyncLinearExecution(object):
+
+       def __init__(self, pool, func):
+               self._pool = pool
+               self._func = func
+               self._run = None
+
+       def start(self, *args, **kwds):
+               assert self._run is None, "Task already started"
+               self._run = self._func(*args, **kwds)
+               trampoline, args, kwds = self._run.send(None) # priming the function
+               self._pool.add_task(
+                       trampoline,
+                       args,
+                       kwds,
+                       self.on_success,
+                       self.on_error,
+               )
+
+       @misc.log_exception(_moduleLogger)
+       def on_success(self, result):
+               _moduleLogger.debug("Processing success for: %r", self._func)
+               try:
+                       trampoline, args, kwds = self._run.send(result)
+               except StopIteration, e:
+                       pass
+               else:
+                       self._pool.add_task(
+                               trampoline,
+                               args,
+                               kwds,
+                               self.on_success,
+                               self.on_error,
+                       )
+
+       @misc.log_exception(_moduleLogger)
+       def on_error(self, error):
+               _moduleLogger.debug("Processing error for: %r", self._func)
+               try:
+                       trampoline, args, kwds = self._run.throw(error)
+               except StopIteration, e:
+                       pass
+               else:
+                       self._pool.add_task(
+                               trampoline,
+                               args,
+                               kwds,
+                               self.on_success,
+                               self.on_error,
+                       )
+
+       def __repr__(self):
+               return "<async %s at 0x%x>" % (self._func.__name__, id(self))
+
+       def __hash__(self):
+               return hash(self._func)
+
+       def __eq__(self, other):
+               return self._func == other._func
+
+       def __ne__(self, other):
+               return self._func != other._func
 
 
 def synchronized(lock):
index 97d671c..eaa2fe1 100644 (file)
@@ -191,7 +191,7 @@ class AsyncPool(object):
                                result = func(*args, **kwds)
                                isError = False
                        except Exception, e:
-                               _moduleLogger.exception("Error, passing it back to the main thread")
+                               _moduleLogger.error("Error, passing it back to the main thread")
                                result = e
                                isError = True
                        self.__workQueue.task_done()
@@ -200,58 +200,6 @@ class AsyncPool(object):
                _moduleLogger.debug("Shutting down worker thread")
 
 
-class AsyncLinearExecution(object):
-
-       def __init__(self, pool, func):
-               self._pool = pool
-               self._func = func
-               self._run = None
-
-       def start(self, *args, **kwds):
-               assert self._run is None
-               self._run = self._func(*args, **kwds)
-               trampoline, args, kwds = self._run.send(None) # priming the function
-               self._pool.add_task(
-                       trampoline,
-                       args,
-                       kwds,
-                       self.on_success,
-                       self.on_error,
-               )
-
-       @misc.log_exception(_moduleLogger)
-       def on_success(self, result):
-               #_moduleLogger.debug("Processing success for: %r", self._func)
-               try:
-                       trampoline, args, kwds = self._run.send(result)
-               except StopIteration, e:
-                       pass
-               else:
-                       self._pool.add_task(
-                               trampoline,
-                               args,
-                               kwds,
-                               self.on_success,
-                               self.on_error,
-                       )
-
-       @misc.log_exception(_moduleLogger)
-       def on_error(self, error):
-               #_moduleLogger.debug("Processing error for: %r", self._func)
-               try:
-                       trampoline, args, kwds = self._run.throw(error)
-               except StopIteration, e:
-                       pass
-               else:
-                       self._pool.add_task(
-                               trampoline,
-                               args,
-                               kwds,
-                               self.on_success,
-                               self.on_error,
-                       )
-
-
 class AutoSignal(object):
 
        def __init__(self, toplevel):
index cf5c22a..c0a70a9 100644 (file)
@@ -16,6 +16,11 @@ import warnings
 import string
 
 
+class AnyData(object):
+
+       pass
+
+
 _indentationLevel = [0]
 
 
@@ -697,16 +702,14 @@ def normalize_number(prettynumber):
        uglynumber = re.sub('[^0-9+]', '', prettynumber)
        if uglynumber.startswith("+"):
                pass
-       elif uglynumber.startswith("1") and len(uglynumber) == 11:
+       elif uglynumber.startswith("1"):
                uglynumber = "+"+uglynumber
-       elif len(uglynumber) == 10:
+       elif 10 <= len(uglynumber):
+               assert uglynumber[0] not in ("+", "1"), "Number format confusing"
                uglynumber = "+1"+uglynumber
        else:
                pass
 
-       #validateRe = re.compile("^\+?[0-9]{10,}$")
-       #assert validateRe.match(uglynumber) is not None
-
        return uglynumber
 
 
@@ -720,6 +723,117 @@ def is_valid_number(number):
        return _VALIDATE_RE.match(number) is not None
 
 
+def make_ugly(prettynumber):
+       """
+       function to take a phone number and strip out all non-numeric
+       characters
+
+       >>> make_ugly("+012-(345)-678-90")
+       '+01234567890'
+       """
+       return normalize_number(prettynumber)
+
+
+def _make_pretty_with_areacode(phonenumber):
+       prettynumber = "(%s)" % (phonenumber[0:3], )
+       if 3 < len(phonenumber):
+               prettynumber += " %s" % (phonenumber[3:6], )
+               if 6 < len(phonenumber):
+                       prettynumber += "-%s" % (phonenumber[6:], )
+       return prettynumber
+
+
+def _make_pretty_local(phonenumber):
+       prettynumber = "%s" % (phonenumber[0:3], )
+       if 3 < len(phonenumber):
+               prettynumber += "-%s" % (phonenumber[3:], )
+       return prettynumber
+
+
+def _make_pretty_international(phonenumber):
+       prettynumber = phonenumber
+       if phonenumber.startswith("1"):
+               prettynumber = "1 "
+               prettynumber += _make_pretty_with_areacode(phonenumber[1:])
+       return prettynumber
+
+
+def make_pretty(phonenumber):
+       """
+       Function to take a phone number and return the pretty version
+       pretty numbers:
+               if phonenumber begins with 0:
+                       ...-(...)-...-....
+               if phonenumber begins with 1: ( for gizmo callback numbers )
+                       1 (...)-...-....
+               if phonenumber is 13 digits:
+                       (...)-...-....
+               if phonenumber is 10 digits:
+                       ...-....
+       >>> make_pretty("12")
+       '12'
+       >>> make_pretty("1234567")
+       '123-4567'
+       >>> make_pretty("2345678901")
+       '+1 (234) 567-8901'
+       >>> make_pretty("12345678901")
+       '+1 (234) 567-8901'
+       >>> make_pretty("01234567890")
+       '+012 (345) 678-90'
+       >>> make_pretty("+01234567890")
+       '+012 (345) 678-90'
+       >>> make_pretty("+12")
+       '+1 (2)'
+       >>> make_pretty("+123")
+       '+1 (23)'
+       >>> make_pretty("+1234")
+       '+1 (234)'
+       """
+       if phonenumber is None or phonenumber == "":
+               return ""
+
+       phonenumber = normalize_number(phonenumber)
+
+       if phonenumber == "":
+               return ""
+       elif phonenumber[0] == "+":
+               prettynumber = _make_pretty_international(phonenumber[1:])
+               if not prettynumber.startswith("+"):
+                       prettynumber = "+"+prettynumber
+       elif 8 < len(phonenumber) and phonenumber[0] in ("1", ):
+               prettynumber = _make_pretty_international(phonenumber)
+       elif 7 < len(phonenumber):
+               prettynumber = _make_pretty_with_areacode(phonenumber)
+       elif 3 < len(phonenumber):
+               prettynumber = _make_pretty_local(phonenumber)
+       else:
+               prettynumber = phonenumber
+       return prettynumber.strip()
+
+
+def similar_ugly_numbers(lhs, rhs):
+       return (
+               lhs == rhs or
+               lhs[1:] == rhs and lhs.startswith("1") or
+               lhs[2:] == rhs and lhs.startswith("+1") or
+               lhs == rhs[1:] and rhs.startswith("1") or
+               lhs == rhs[2:] and rhs.startswith("+1")
+       )
+
+
+def abbrev_relative_date(date):
+       """
+       >>> abbrev_relative_date("42 hours ago")
+       '42 h'
+       >>> abbrev_relative_date("2 days ago")
+       '2 d'
+       >>> abbrev_relative_date("4 weeks ago")
+       '4 w'
+       """
+       parts = date.split(" ")
+       return "%s %s" % (parts[0], parts[1][0])
+
+
 def parse_version(versionText):
        """
        >>> parse_version("0.5.2")
diff --git a/src/util/qore_utils.py b/src/util/qore_utils.py
new file mode 100644 (file)
index 0000000..491c96d
--- /dev/null
@@ -0,0 +1,107 @@
+import logging
+
+from PyQt4 import QtCore
+
+import misc
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class QThread44(QtCore.QThread):
+       """
+       This is to imitate QThread in Qt 4.4+ for when running on older version
+       See http://labs.trolltech.com/blogs/2010/06/17/youre-doing-it-wrong
+       (On Lucid I have Qt 4.7 and this is still an issue)
+       """
+
+       def __init__(self, parent = None):
+               QtCore.QThread.__init__(self, parent)
+
+       def run(self):
+               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)
+
+       def __init__(self, pool):
+               QtCore.QObject.__init__(self)
+               self._pool = pool
+
+       @QtCore.pyqtSlot(object)
+       @misc.log_exception(_moduleLogger)
+       def _on_task_added(self, task):
+               if not self._pool._isRunning:
+                       _moduleLogger.error("Dropping task")
+
+               func, args, kwds, on_success, on_error = task
+
+               try:
+                       result = func(*args, **kwds)
+                       isError = False
+               except Exception, e:
+                       _moduleLogger.error("Error, passing it back to the main thread")
+                       result = e
+                       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()
+
+
+class AsyncPool(QtCore.QObject):
+
+       _addTask = QtCore.pyqtSignal(object)
+       _stopPool = QtCore.pyqtSignal()
+
+       def __init__(self):
+               QtCore.QObject.__init__(self)
+               self._thread = QThread44()
+               self._isRunning = True
+               self._parent = _ParentThread(self)
+               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()
+
+       def stop(self):
+               self._isRunning = False
+               self._stopPool.emit()
+
+       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)
index 005050f..c23e512 100755 (executable)
@@ -6,17 +6,7 @@ import logging
 from PyQt4 import QtGui
 from PyQt4 import QtCore
 
-try:
-       from util import misc as misc_utils
-except ImportError:
-       class misc_utils(object):
-
-               @staticmethod
-               def log_exception(logger):
-
-                       def wrapper(func):
-                               return func
-                       return wrapper
+import misc as misc_utils
 
 
 _moduleLogger = logging.getLogger(__name__)
@@ -25,45 +15,6 @@ _moduleLogger = logging.getLogger(__name__)
 _TWOPI = 2 * math.pi
 
 
-class EIGHT_SLICE_PIE(object):
-
-       SLICE_CENTER = -1
-       SLICE_NORTH = 0
-       SLICE_NORTH_WEST = 1
-       SLICE_WEST = 2
-       SLICE_SOUTH_WEST = 3
-       SLICE_SOUTH = 4
-       SLICE_SOUTH_EAST = 5
-       SLICE_EAST = 6
-       SLICE_NORTH_EAST = 7
-
-       MAX_ANGULAR_SLICES = 8
-
-       SLICE_DIRECTIONS = [
-               SLICE_CENTER,
-               SLICE_NORTH,
-               SLICE_NORTH_WEST,
-               SLICE_WEST,
-               SLICE_SOUTH_WEST,
-               SLICE_SOUTH,
-               SLICE_SOUTH_EAST,
-               SLICE_EAST,
-               SLICE_NORTH_EAST,
-       ]
-
-       SLICE_DIRECTION_NAMES = [
-               "CENTER",
-               "NORTH",
-               "NORTH_WEST",
-               "WEST",
-               "SOUTH_WEST",
-               "SOUTH",
-               "SOUTH_EAST",
-               "EAST",
-               "NORTH_EAST",
-       ]
-
-
 def _radius_at(center, pos):
        delta = pos - center
        xDelta = delta.x()
@@ -237,7 +188,10 @@ class PieArtist(object):
        SHAPE_SQUARE = "square"
        DEFAULT_SHAPE = SHAPE_SQUARE
 
-       def __init__(self, filing):
+       BACKGROUND_FILL = "fill"
+       BACKGROUND_NOFILL = "no fill"
+
+       def __init__(self, filing, background = BACKGROUND_FILL):
                self._filing = filing
 
                self._cachedOuterRadius = self._filing.outerRadius()
@@ -245,6 +199,7 @@ class PieArtist(object):
                canvasSize = self._cachedOuterRadius * 2 + 1
                self._canvas = QtGui.QPixmap(canvasSize, canvasSize)
                self._mask = None
+               self._backgroundState = background
                self.palette = None
 
        def pieSize(self):
@@ -292,37 +247,27 @@ class PieArtist(object):
                painter = QtGui.QPainter(self._canvas)
                painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
 
-               adjustmentRect = self._canvas.rect().adjusted(0, 0, -1, -1)
+               self.paintPainter(selectionIndex, painter)
+
+               return self._canvas
+
+       def paintPainter(self, selectionIndex, painter):
+               adjustmentRect = painter.viewport().adjusted(0, 0, -1, -1)
 
                numChildren = len(self._filing)
                if numChildren == 0:
-                       if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
-                               painter.setBrush(self.palette.highlight())
-                       else:
-                               painter.setBrush(self.palette.window())
-                       painter.setPen(self.palette.mid().color())
-
-                       painter.drawRect(self._canvas.rect())
-                       self._paint_center_foreground(painter, selectionIndex)
+                       self._paint_center_background(painter, adjustmentRect, selectionIndex)
+                       self._paint_center_foreground(painter, adjustmentRect, selectionIndex)
                        return self._canvas
-               elif numChildren == 1:
-                       if selectionIndex == 0 and self._filing[0].isEnabled():
-                               painter.setBrush(self.palette.highlight())
-                       else:
-                               painter.setBrush(self.palette.window())
-
-                       painter.fillRect(self._canvas.rect(), painter.brush())
                else:
                        for i in xrange(len(self._filing)):
                                self._paint_slice_background(painter, adjustmentRect, i, selectionIndex)
 
                self._paint_center_background(painter, adjustmentRect, selectionIndex)
-               self._paint_center_foreground(painter, selectionIndex)
+               self._paint_center_foreground(painter, adjustmentRect, selectionIndex)
 
                for i in xrange(len(self._filing)):
-                       self._paint_slice_foreground(painter, i, selectionIndex)
-
-               return self._canvas
+                       self._paint_slice_foreground(painter, adjustmentRect, i, selectionIndex)
 
        def _generate_mask(self, mask):
                """
@@ -349,11 +294,16 @@ class PieArtist(object):
                else:
                        raise NotImplementedError(self.DEFAULT_SHAPE)
 
-               if i == selectionIndex and self._filing[i].isEnabled():
-                       painter.setBrush(self.palette.highlight())
+               if self._backgroundState == self.BACKGROUND_NOFILL:
+                       painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
+                       painter.setPen(self.palette.highlight().color())
                else:
-                       painter.setBrush(self.palette.window())
-               painter.setPen(self.palette.mid().color())
+                       if i == selectionIndex and self._filing[i].isEnabled():
+                               painter.setBrush(self.palette.highlight())
+                               painter.setPen(self.palette.highlight().color())
+                       else:
+                               painter.setBrush(self.palette.window())
+                               painter.setPen(self.palette.window().color())
 
                a = self._filing._index_to_angle(i, True)
                b = self._filing._index_to_angle(i + 1, True)
@@ -367,7 +317,7 @@ class PieArtist(object):
                sizeInDeg = (size * 360 * 16) / _TWOPI
                painter.drawPie(adjustmentRect, int(startAngleInDeg), int(sizeInDeg))
 
-       def _paint_slice_foreground(self, painter, i, selectionIndex):
+       def _paint_slice_foreground(self, painter, adjustmentRect, i, selectionIndex):
                child = self._filing[i]
 
                a = self._filing._index_to_angle(i, True)
@@ -380,7 +330,7 @@ class PieArtist(object):
                sliceX = averageRadius * math.cos(middleAngle)
                sliceY = - averageRadius * math.sin(middleAngle)
 
-               piePos = self._canvas.rect().center()
+               piePos = adjustmentRect.center()
                pieX = piePos.x()
                pieY = piePos.y()
                self._paint_label(
@@ -437,41 +387,54 @@ class PieArtist(object):
                        painter.drawText(leftX, topY, text)
 
        def _paint_center_background(self, painter, adjustmentRect, selectionIndex):
-               dark = self.palette.mid().color()
-               light = self.palette.light().color()
-               if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
-                       background = self.palette.highlight().color()
-               else:
-                       background = self.palette.window().color()
-
-               innerRadius = self._cachedInnerRadius
-               adjustmentCenterPos = adjustmentRect.center()
-               innerRect = QtCore.QRect(
-                       adjustmentCenterPos.x() - innerRadius,
-                       adjustmentCenterPos.y() - innerRadius,
-                       innerRadius * 2 + 1,
-                       innerRadius * 2 + 1,
-               )
+               if self._backgroundState == self.BACKGROUND_NOFILL:
+                       return
+               if len(self._filing) == 0:
+                       if self._backgroundState == self.BACKGROUND_NOFILL:
+                               painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
+                       else:
+                               if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
+                                       painter.setBrush(self.palette.highlight())
+                               else:
+                                       painter.setBrush(self.palette.window())
+                       painter.setPen(self.palette.mid().color())
 
-               painter.setPen(QtCore.Qt.NoPen)
-               painter.setBrush(background)
-               painter.drawPie(innerRect, 0, 360 * 16)
+                       painter.drawRect(adjustmentRect)
+               else:
+                       dark = self.palette.mid().color()
+                       light = self.palette.light().color()
+                       if self._backgroundState == self.BACKGROUND_NOFILL:
+                               background = QtGui.QBrush(QtCore.Qt.transparent)
+                       else:
+                               if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
+                                       background = self.palette.highlight().color()
+                               else:
+                                       background = self.palette.window().color()
+
+                       innerRadius = self._cachedInnerRadius
+                       adjustmentCenterPos = adjustmentRect.center()
+                       innerRect = QtCore.QRect(
+                               adjustmentCenterPos.x() - innerRadius,
+                               adjustmentCenterPos.y() - innerRadius,
+                               innerRadius * 2 + 1,
+                               innerRadius * 2 + 1,
+                       )
 
-               painter.setPen(QtGui.QPen(dark, 1))
-               painter.setBrush(QtCore.Qt.NoBrush)
-               painter.drawEllipse(innerRect)
+                       painter.setPen(QtCore.Qt.NoPen)
+                       painter.setBrush(background)
+                       painter.drawPie(innerRect, 0, 360 * 16)
 
-               if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
-                       pass
-               elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
-                       painter.setPen(QtGui.QPen(dark, 1))
-                       painter.setBrush(QtCore.Qt.NoBrush)
-                       painter.drawEllipse(adjustmentRect)
-               else:
-                       raise NotImplementedError(self.DEFAULT_SHAPE)
+                       if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
+                               pass
+                       elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
+                               painter.setPen(QtGui.QPen(dark, 1))
+                               painter.setBrush(QtCore.Qt.NoBrush)
+                               painter.drawEllipse(adjustmentRect)
+                       else:
+                               raise NotImplementedError(self.DEFAULT_SHAPE)
 
-       def _paint_center_foreground(self, painter, selectionIndex):
-               centerPos = self._canvas.rect().center()
+       def _paint_center_foreground(self, painter, adjustmentRect, selectionIndex):
+               centerPos = adjustmentRect.center()
                pieX = centerPos.x()
                pieY = centerPos.y()
 
@@ -517,9 +480,10 @@ class QPieDisplay(QtGui.QWidget):
        @misc_utils.log_exception(_moduleLogger)
        def paintEvent(self, paintEvent):
                canvas = self._artist.paint(self._selectionIndex)
+               offset = (self.size() - canvas.size()) / 2
 
                screen = QtGui.QPainter(self)
-               screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
+               screen.drawPixmap(QtCore.QPoint(offset.width(), offset.height()), canvas)
 
                QtGui.QWidget.paintEvent(self, paintEvent)
 
@@ -541,7 +505,7 @@ class QPieButton(QtGui.QWidget):
        BUTTON_RADIUS = 24
        DELAY = 250
 
-       def __init__(self, buttonSlice, parent = None):
+       def __init__(self, buttonSlice, parent = None, buttonSlices = None):
                # @bug Artifacts on Maemo 5 due to window 3D effects, find way to disable them for just these?
                # @bug The pie's are being pushed back on screen on Maemo, leading to coordinate issues
                QtGui.QWidget.__init__(self, parent)
@@ -553,9 +517,13 @@ class QPieButton(QtGui.QWidget):
 
                self._buttonFiling = PieFiling()
                self._buttonFiling.set_center(buttonSlice)
+               if buttonSlices is not None:
+                       for slice in buttonSlices:
+                               self._buttonFiling.insertItem(slice)
                self._buttonFiling.setOuterRadius(self.BUTTON_RADIUS)
-               self._buttonArtist = PieArtist(self._buttonFiling)
+               self._buttonArtist = PieArtist(self._buttonFiling, PieArtist.BACKGROUND_NOFILL)
                self._poppedUp = False
+               self._pressed = False
 
                self._delayPopupTimer = QtCore.QTimer()
                self._delayPopupTimer.setInterval(self.DELAY)
@@ -565,6 +533,12 @@ class QPieButton(QtGui.QWidget):
 
                self._mousePosition = None
                self.setFocusPolicy(QtCore.Qt.StrongFocus)
+               self.setSizePolicy(
+                       QtGui.QSizePolicy(
+                               QtGui.QSizePolicy.MinimumExpanding,
+                               QtGui.QSizePolicy.MinimumExpanding,
+                       )
+               )
 
        def insertItem(self, item, index = -1):
                self._filing.insertItem(item, index)
@@ -604,6 +578,7 @@ class QPieButton(QtGui.QWidget):
 
        def setButtonRadius(self, radius):
                self._buttonFiling.setOuterRadius(radius)
+               self._buttonFiling.setInnerRadius(radius / 2)
                self._buttonArtist.show(self.palette())
 
        def minimumSizeHint(self):
@@ -620,12 +595,14 @@ class QPieButton(QtGui.QWidget):
                self.highlighted.emit(self._selectionIndex)
 
                self._display.selectAt(self._selectionIndex)
+               self._pressed = True
+               self.update()
                self._popupLocation = mouseEvent.globalPos()
                self._delayPopupTimer.start()
 
        @misc_utils.log_exception(_moduleLogger)
        def _on_delayed_popup(self):
-               assert self._popupLocation is not None
+               assert self._popupLocation is not None, "Widget location abuse"
                self._popup_child(self._popupLocation)
 
        @misc_utils.log_exception(_moduleLogger)
@@ -670,6 +647,8 @@ class QPieButton(QtGui.QWidget):
                self._mousePosition = None
 
                self._activate_at(self._selectionIndex)
+               self._pressed = False
+               self.update()
                self._hide_child()
 
        @misc_utils.log_exception(_moduleLogger)
@@ -729,12 +708,18 @@ class QPieButton(QtGui.QWidget):
        def paintEvent(self, paintEvent):
                self.setButtonRadius(min(self.rect().width(), self.rect().height()) / 2 - 1)
                if self._poppedUp:
-                       canvas = self._buttonArtist.paint(PieFiling.SELECTION_CENTER)
+                       selectionIndex = PieFiling.SELECTION_CENTER
                else:
-                       canvas = self._buttonArtist.paint(PieFiling.SELECTION_NONE)
+                       selectionIndex = PieFiling.SELECTION_NONE
 
-               screen = QtGui.QPainter(self)
-               screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
+               screen = QtGui.QStylePainter(self)
+               screen.setRenderHint(QtGui.QPainter.Antialiasing, True)
+               option = QtGui.QStyleOptionButton()
+               option.initFrom(self)
+               option.state = QtGui.QStyle.State_Sunken if self._pressed else QtGui.QStyle.State_Raised
+
+               screen.drawControl(QtGui.QStyle.CE_PushButton, option)
+               self._buttonArtist.paintPainter(selectionIndex, screen)
 
                QtGui.QWidget.paintEvent(self, paintEvent)
 
index 7d79c60..c7094f4 100755 (executable)
@@ -7,28 +7,47 @@ import os
 import warnings
 
 from PyQt4 import QtGui
-from PyQt4 import QtCore
 
 import qtpie
 
 
 class PieKeyboard(object):
 
-       SLICE_CENTER = qtpie.EIGHT_SLICE_PIE.SLICE_CENTER
-       SLICE_NORTH = qtpie.EIGHT_SLICE_PIE.SLICE_NORTH
-       SLICE_NORTH_WEST = qtpie.EIGHT_SLICE_PIE.SLICE_NORTH_WEST
-       SLICE_WEST = qtpie.EIGHT_SLICE_PIE.SLICE_WEST
-       SLICE_SOUTH_WEST = qtpie.EIGHT_SLICE_PIE.SLICE_SOUTH_WEST
-       SLICE_SOUTH = qtpie.EIGHT_SLICE_PIE.SLICE_SOUTH
-       SLICE_SOUTH_EAST = qtpie.EIGHT_SLICE_PIE.SLICE_SOUTH_EAST
-       SLICE_EAST = qtpie.EIGHT_SLICE_PIE.SLICE_EAST
-       SLICE_NORTH_EAST = qtpie.EIGHT_SLICE_PIE.SLICE_NORTH_EAST
-
-       MAX_ANGULAR_SLICES = qtpie.EIGHT_SLICE_PIE.MAX_ANGULAR_SLICES
-
-       SLICE_DIRECTIONS = qtpie.EIGHT_SLICE_PIE.SLICE_DIRECTIONS
-
-       SLICE_DIRECTION_NAMES = qtpie.EIGHT_SLICE_PIE.SLICE_DIRECTION_NAMES
+       SLICE_CENTER = -1
+       SLICE_NORTH = 0
+       SLICE_NORTH_WEST = 1
+       SLICE_WEST = 2
+       SLICE_SOUTH_WEST = 3
+       SLICE_SOUTH = 4
+       SLICE_SOUTH_EAST = 5
+       SLICE_EAST = 6
+       SLICE_NORTH_EAST = 7
+
+       MAX_ANGULAR_SLICES = 8
+
+       SLICE_DIRECTIONS = [
+               SLICE_CENTER,
+               SLICE_NORTH,
+               SLICE_NORTH_WEST,
+               SLICE_WEST,
+               SLICE_SOUTH_WEST,
+               SLICE_SOUTH,
+               SLICE_SOUTH_EAST,
+               SLICE_EAST,
+               SLICE_NORTH_EAST,
+       ]
+
+       SLICE_DIRECTION_NAMES = [
+               "CENTER",
+               "NORTH",
+               "NORTH_WEST",
+               "WEST",
+               "SOUTH_WEST",
+               "SOUTH",
+               "SOUTH_EAST",
+               "EAST",
+               "NORTH_EAST",
+       ]
 
        def __init__(self):
                self._layout = QtGui.QGridLayout()
@@ -43,7 +62,7 @@ class PieKeyboard(object):
 
        def add_pie(self, row, column, pieButton):
                assert len(pieButton) == 8
-               self._layout.addWidget(pieButton, row, column, QtCore.Qt.AlignCenter)
+               self._layout.addWidget(pieButton, row, column)
                self.__cells[(row, column)] = pieButton
 
        def get_pie(self, row, column):
diff --git a/src/util/qui_utils.py b/src/util/qui_utils.py
new file mode 100644 (file)
index 0000000..56a3408
--- /dev/null
@@ -0,0 +1,364 @@
+import sys
+import contextlib
+import datetime
+import logging
+
+from PyQt4 import QtCore
+from PyQt4 import QtGui
+
+import misc
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+@contextlib.contextmanager
+def notify_error(log):
+       try:
+               yield
+       except:
+               log.push_exception()
+
+
+@contextlib.contextmanager
+def notify_busy(log, message):
+       log.push_busy(message)
+       try:
+               yield
+       finally:
+               log.pop(message)
+
+
+class ErrorMessage(object):
+
+       LEVEL_ERROR = 0
+       LEVEL_BUSY = 1
+       LEVEL_INFO = 2
+
+       def __init__(self, message, level):
+               self._message = message
+               self._level = level
+               self._time = datetime.datetime.now()
+
+       @property
+       def level(self):
+               return self._level
+
+       @property
+       def message(self):
+               return self._message
+
+       def __repr__(self):
+               return "%s.%s(%r, %r)" % (__name__, self.__class__.__name__, self._message, self._level)
+
+
+class QErrorLog(QtCore.QObject):
+
+       messagePushed = QtCore.pyqtSignal()
+       messagePopped = QtCore.pyqtSignal()
+
+       def __init__(self):
+               QtCore.QObject.__init__(self)
+               self._messages = []
+
+       def push_busy(self, message):
+               _moduleLogger.info("Entering state: %s" % message)
+               self._push_message(message, ErrorMessage.LEVEL_BUSY)
+
+       def push_message(self, message):
+               self._push_message(message, ErrorMessage.LEVEL_INFO)
+
+       def push_error(self, message):
+               self._push_message(message, ErrorMessage.LEVEL_ERROR)
+
+       def push_exception(self):
+               userMessage = str(sys.exc_info()[1])
+               _moduleLogger.exception(userMessage)
+               self.push_error(userMessage)
+
+       def pop(self, message = None):
+               if message is None:
+                       del self._messages[0]
+               else:
+                       _moduleLogger.info("Exiting state: %s" % message)
+                       messageIndex = [
+                               i
+                               for (i, error) in enumerate(self._messages)
+                               if error.message == message
+                       ]
+                       # Might be removed out of order
+                       if messageIndex:
+                               del self._messages[messageIndex[0]]
+               self.messagePopped.emit()
+
+       def peek_message(self):
+               return self._messages[0]
+
+       def _push_message(self, message, level):
+               self._messages.append(ErrorMessage(message, level))
+               # Sort is defined as stable, so this should be fine
+               self._messages.sort(key=lambda x: x.level)
+               self.messagePushed.emit()
+
+       def __len__(self):
+               return len(self._messages)
+
+
+class ErrorDisplay(object):
+
+       _SENTINEL_ICON = QtGui.QIcon()
+
+       def __init__(self, errorLog):
+               self._errorLog = errorLog
+               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._severityLabel = QtGui.QLabel()
+               self._severityLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+               self._message = QtGui.QLabel()
+               self._message.setText("Boo")
+               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._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)
+               self._widget.hide()
+
+       @property
+       def toplevel(self):
+               return self._widget
+
+       def _show_error(self):
+               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)
+       @misc.log_exception(_moduleLogger)
+       def _on_close(self, checked = False):
+               self._errorLog.pop()
+
+       @QtCore.pyqtSlot()
+       @misc.log_exception(_moduleLogger)
+       def _on_message_pushed(self):
+               self._show_error()
+
+       @QtCore.pyqtSlot()
+       @misc.log_exception(_moduleLogger)
+       def _on_message_popped(self):
+               if len(self._errorLog) == 0:
+                       self._message.setText("")
+                       self._widget.hide()
+               else:
+                       self._show_error()
+
+
+class QHtmlDelegate(QtGui.QStyledItemDelegate):
+
+       UNDEFINED_SIZE = -1
+
+       def __init__(self, *args, **kwd):
+               QtGui.QStyledItemDelegate.__init__(*((self, ) + args), **kwd)
+               self._width = self.UNDEFINED_SIZE
+
+       def paint(self, painter, option, index):
+               newOption = QtGui.QStyleOptionViewItemV4(option)
+               self.initStyleOption(newOption, index)
+               if newOption.widget is not None:
+                       style = newOption.widget.style()
+               else:
+                       style = QtGui.QApplication.style()
+
+               doc = QtGui.QTextDocument()
+               doc.setHtml(newOption.text)
+               doc.setTextWidth(newOption.rect.width())
+
+               newOption.text = ""
+               style.drawControl(QtGui.QStyle.CE_ItemViewItem, newOption, painter)
+
+               ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
+               if newOption.state & QtGui.QStyle.State_Selected:
+                       ctx.palette.setColor(
+                               QtGui.QPalette.Text,
+                               newOption.palette.color(
+                                       QtGui.QPalette.Active,
+                                       QtGui.QPalette.HighlightedText
+                               )
+                       )
+               else:
+                       ctx.palette.setColor(
+                               QtGui.QPalette.Text,
+                               newOption.palette.color(
+                                       QtGui.QPalette.Active,
+                                       QtGui.QPalette.Text
+                               )
+                       )
+
+               textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, newOption)
+               painter.save()
+               painter.translate(textRect.topLeft())
+               painter.setClipRect(textRect.translated(-textRect.topLeft()))
+               doc.documentLayout().draw(painter, ctx)
+               painter.restore()
+
+       def setWidth(self, width):
+               # @bug we need to be emitting sizeHintChanged but it requires an index
+               self._width = width
+
+       def sizeHint(self, option, index):
+               newOption = QtGui.QStyleOptionViewItemV4(option)
+               self.initStyleOption(newOption, index)
+
+               doc = QtGui.QTextDocument()
+               doc.setHtml(newOption.text)
+               if self._width != self.UNDEFINED_SIZE:
+                       width = self._width
+               else:
+                       width = newOption.rect.width()
+               doc.setTextWidth(width)
+               size = QtCore.QSize(doc.idealWidth(), doc.size().height())
+               return size
+
+
+def _null_set_stackable(window, isStackable):
+       pass
+
+
+def _maemo_set_stackable(window, isStackable):
+       window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
+
+
+try:
+       QtCore.Qt.WA_Maemo5StackedWindow
+       set_stackable = _maemo_set_stackable
+except AttributeError:
+       set_stackable = _null_set_stackable
+
+
+def _null_set_autorient(window, isStackable):
+       pass
+
+
+def _maemo_set_autorient(window, isStackable):
+       window.setAttribute(QtCore.Qt.WA_Maemo5AutoOrientation, isStackable)
+
+
+try:
+       QtCore.Qt.WA_Maemo5AutoOrientation
+       set_autorient = _maemo_set_autorient
+except AttributeError:
+       set_autorient = _null_set_autorient
+
+
+def screen_orientation():
+       geom = QtGui.QApplication.desktop().screenGeometry()
+       if geom.width() <= geom.height():
+               return QtCore.Qt.Vertical
+       else:
+               return QtCore.Qt.Horizontal
+
+
+def _null_set_window_orientation(window, orientation):
+       pass
+
+
+def _maemo_set_window_orientation(window, orientation):
+       if orientation == QtCore.Qt.Vertical:
+               oldHint = QtCore.Qt.WA_Maemo5LandscapeOrientation
+               newHint = QtCore.Qt.WA_Maemo5PortraitOrientation
+       elif orientation == QtCore.Qt.Horizontal:
+               oldHint = QtCore.Qt.WA_Maemo5PortraitOrientation
+               newHint = QtCore.Qt.WA_Maemo5LandscapeOrientation
+       window.setAttribute(oldHint, False)
+       window.setAttribute(newHint, True)
+
+
+try:
+       QtCore.Qt.WA_Maemo5LandscapeOrientation
+       QtCore.Qt.WA_Maemo5PortraitOrientation
+       set_window_orientation = _maemo_set_window_orientation
+except AttributeError:
+       set_window_orientation = _null_set_window_orientation
+
+
+def _null_show_progress_indicator(window, isStackable):
+       pass
+
+
+def _maemo_show_progress_indicator(window, isStackable):
+       window.setAttribute(QtCore.Qt.WA_Maemo5ShowProgressIndicator, isStackable)
+
+
+try:
+       QtCore.Qt.WA_Maemo5ShowProgressIndicator
+       show_progress_indicator = _maemo_show_progress_indicator
+except AttributeError:
+       show_progress_indicator = _null_show_progress_indicator
+
+
+def _null_mark_numbers_preferred(widget):
+       pass
+
+
+def _newqt_mark_numbers_preferred(widget):
+       widget.setInputMethodHints(QtCore.Qt.ImhPreferNumbers)
+
+
+try:
+       QtCore.Qt.ImhPreferNumbers
+       mark_numbers_preferred = _newqt_mark_numbers_preferred
+except AttributeError:
+       mark_numbers_preferred = _null_mark_numbers_preferred
+
+
+def _null_get_theme_icon(iconNames, fallback = None):
+       icon = fallback if fallback is not None else QtGui.QIcon()
+       return icon
+
+
+def _newqt_get_theme_icon(iconNames, fallback = None):
+       for iconName in iconNames:
+               if QtGui.QIcon.hasThemeIcon(iconName):
+                       icon = QtGui.QIcon.fromTheme(iconName)
+                       break
+       else:
+               icon = fallback if fallback is not None else QtGui.QIcon()
+       return icon
+
+
+try:
+       QtGui.QIcon.fromTheme
+       get_theme_icon = _newqt_get_theme_icon
+except AttributeError:
+       get_theme_icon = _null_get_theme_icon
+
diff --git a/src/util/qwrappers.py b/src/util/qwrappers.py
new file mode 100644 (file)
index 0000000..b84e8ff
--- /dev/null
@@ -0,0 +1,267 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+from __future__ import division
+
+import logging
+
+from PyQt4 import QtGui
+from PyQt4 import QtCore
+
+from util import qui_utils
+from util import misc as misc_utils
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class ApplicationWrapper(object):
+
+       def __init__(self, qapp, constants):
+               self._constants = constants
+               self._qapp = qapp
+               self._clipboard = QtGui.QApplication.clipboard()
+
+               self._errorLog = qui_utils.QErrorLog()
+               self._mainWindow = None
+
+               self._fullscreenAction = QtGui.QAction(None)
+               self._fullscreenAction.setText("Fullscreen")
+               self._fullscreenAction.setCheckable(True)
+               self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter"))
+               self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen)
+
+               self._orientationAction = QtGui.QAction(None)
+               self._orientationAction.setText("Orientation")
+               self._orientationAction.setCheckable(True)
+               self._orientationAction.setShortcut(QtGui.QKeySequence("CTRL+o"))
+               self._orientationAction.toggled.connect(self._on_toggle_orientation)
+
+               self._logAction = QtGui.QAction(None)
+               self._logAction.setText("Log")
+               self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l"))
+               self._logAction.triggered.connect(self._on_log)
+
+               self._quitAction = QtGui.QAction(None)
+               self._quitAction.setText("Quit")
+               self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q"))
+               self._quitAction.triggered.connect(self._on_quit)
+
+               self._aboutAction = QtGui.QAction(None)
+               self._aboutAction.setText("About")
+               self._aboutAction.triggered.connect(self._on_about)
+
+               self._qapp.lastWindowClosed.connect(self._on_app_quit)
+               self._mainWindow = self._new_main_window()
+               self._mainWindow.window.destroyed.connect(self._on_child_close)
+
+               self.load_settings()
+
+               self._mainWindow.show()
+               self._idleDelay = QtCore.QTimer()
+               self._idleDelay.setSingleShot(True)
+               self._idleDelay.setInterval(0)
+               self._idleDelay.timeout.connect(lambda: self._mainWindow.start())
+               self._idleDelay.start()
+
+       def load_settings(self):
+               raise NotImplementedError("Booh")
+
+       def save_settings(self):
+               raise NotImplementedError("Booh")
+
+       def _new_main_window(self):
+               raise NotImplementedError("Booh")
+
+       @property
+       def qapp(self):
+               return self._qapp
+
+       @property
+       def constants(self):
+               return self._constants
+
+       @property
+       def errorLog(self):
+               return self._errorLog
+
+       @property
+       def fullscreenAction(self):
+               return self._fullscreenAction
+
+       @property
+       def orientationAction(self):
+               return self._orientationAction
+
+       @property
+       def logAction(self):
+               return self._logAction
+
+       @property
+       def aboutAction(self):
+               return self._aboutAction
+
+       @property
+       def quitAction(self):
+               return self._quitAction
+
+       def _close_windows(self):
+               if self._mainWindow is not None:
+                       self.save_settings()
+                       self._mainWindow.window.destroyed.disconnect(self._on_child_close)
+                       self._mainWindow.close()
+                       self._mainWindow = None
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_app_quit(self, checked = False):
+               if self._mainWindow is not None:
+                       self.save_settings()
+                       self._mainWindow.destroy()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_child_close(self, obj = None):
+               if self._mainWindow is not None:
+                       self.save_settings()
+                       self._mainWindow = None
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_toggle_fullscreen(self, checked = False):
+               with qui_utils.notify_error(self._errorLog):
+                       self._mainWindow.set_fullscreen(checked)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_toggle_orientation(self, checked = False):
+               with qui_utils.notify_error(self._errorLog):
+                       self._mainWindow.set_orientation(checked)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_about(self, checked = True):
+               raise NotImplementedError("Booh")
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_log(self, checked = False):
+               with qui_utils.notify_error(self._errorLog):
+                       with open(self._constants._user_logpath_, "r") as f:
+                               logLines = f.xreadlines()
+                               log = "".join(logLines)
+                               self._clipboard.setText(log)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_quit(self, checked = False):
+               with qui_utils.notify_error(self._errorLog):
+                       self._close_windows()
+
+
+class WindowWrapper(object):
+
+       def __init__(self, parent, app):
+               self._app = app
+
+               self._errorDisplay = qui_utils.ErrorDisplay(self._app.errorLog)
+
+               self._layout = QtGui.QBoxLayout(QtGui.QBoxLayout.LeftToRight)
+               self._layout.setContentsMargins(0, 0, 0, 0)
+
+               self._superLayout = QtGui.QVBoxLayout()
+               self._superLayout.addWidget(self._errorDisplay.toplevel)
+               self._superLayout.setContentsMargins(0, 0, 0, 0)
+               self._superLayout.addLayout(self._layout)
+
+               centralWidget = QtGui.QWidget()
+               centralWidget.setLayout(self._superLayout)
+               centralWidget.setContentsMargins(0, 0, 0, 0)
+
+               self._window = QtGui.QMainWindow(parent)
+               self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
+               qui_utils.set_stackable(self._window, True)
+               self._window.setCentralWidget(centralWidget)
+
+               self._closeWindowAction = QtGui.QAction(None)
+               self._closeWindowAction.setText("Close")
+               self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
+               self._closeWindowAction.triggered.connect(self._on_close_window)
+
+               self._window.addAction(self._closeWindowAction)
+               self._window.addAction(self._app.quitAction)
+               self._window.addAction(self._app.fullscreenAction)
+               self._window.addAction(self._app.orientationAction)
+               self._window.addAction(self._app.logAction)
+
+       @property
+       def window(self):
+               return self._window
+
+       def walk_children(self):
+               return ()
+
+       def start(self):
+               pass
+
+       def close(self):
+               for child in self.walk_children():
+                       child.window.destroyed.disconnect(self._on_child_close)
+                       child.close()
+               self._window.close()
+
+       def destroy(self):
+               pass
+
+       def show(self):
+               self.set_fullscreen(self._app.fullscreenAction.isChecked())
+               self._window.show()
+               for child in self.walk_children():
+                       child.show()
+
+       def hide(self):
+               for child in self.walk_children():
+                       child.hide()
+               self._window.hide()
+
+       def set_fullscreen(self, isFullscreen):
+               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:
+                       qui_utils.set_window_orientation(self.window, QtCore.Qt.Vertical)
+               else:
+                       qui_utils.set_window_orientation(self.window, QtCore.Qt.Horizontal)
+               for child in self.walk_children():
+                       child.set_orientation(isPortrait)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_child_close(self):
+               raise NotImplementedError("Booh")
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_close_window(self, checked = True):
+               with qui_utils.notify_error(self._errorLog):
+                       self.close()
+
+
+class AutoFreezeWindowFeature(object):
+
+       def __init__(self, app, window):
+               self._app = app
+               self._window = window
+               self._app.qapp.focusChanged.connect(self._on_focus_changed)
+               if self._app.qapp.focusWidget() is not None:
+                       self._window.setUpdatesEnabled(True)
+               else:
+                       self._window.setUpdatesEnabled(False)
+
+       def close(self):
+               self._app.qapp.focusChanged.disconnect(self._on_focus_changed)
+               self._window.setUpdatesEnabled(True)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_focus_changed(self, oldWindow, newWindow):
+               with qui_utils.notify_error(self._app.errorLog):
+                       if oldWindow is None and newWindow is not None:
+                               self._window.setUpdatesEnabled(True)
+                       elif oldWindow is not None and newWindow is None:
+                               self._window.setUpdatesEnabled(False)
index 30d7629..7c55c42 100644 (file)
@@ -61,13 +61,13 @@ class WasMissedCall(object):
                                self._report_error("closed too early")
 
        def _report_success(self):
-               assert not self._didReport
+               assert not self._didReport, "Double reporting a missed call"
                self._didReport = True
                self._onTimeout.cancel()
                self.__on_success(self)
 
        def _report_error(self, reason):
-               assert not self._didReport
+               assert not self._didReport, "Double reporting a missed call"
                self._didReport = True
                self._onTimeout.cancel()
                self.__on_error(self, reason)
index f5174b8..6aefe7e 100644 (file)
@@ -9,7 +9,7 @@ from PyQt4 import QtGui
 from PyQt4 import QtCore
 
 import constants
-import maeqt
+from util import qui_utils
 from util import misc as misc_utils
 import unit_data
 
@@ -62,8 +62,7 @@ class FavoritesWindow(object):
 
                self._window = QtGui.QMainWindow(parent)
                self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
-               maeqt.set_autorient(self._window, True)
-               maeqt.set_stackable(self._window, True)
+               qui_utils.set_stackable(self._window, True)
                self._window.setWindowTitle("%s - Favorites" % constants.__pretty_app_name__)
                self._window.setWindowIcon(QtGui.QIcon(self._app.appIconPath))
                self._window.setCentralWidget(centralWidget)
@@ -179,8 +178,7 @@ class QuickJump(object):
 
                self._window = QtGui.QMainWindow(parent)
                self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
-               maeqt.set_autorient(self._window, True)
-               maeqt.set_stackable(self._window, True)
+               qui_utils.set_stackable(self._window, True)
                self._window.setWindowTitle("%s - Quick Jump" % constants.__pretty_app_name__)
                self._window.setWindowIcon(QtGui.QIcon(self._app.appIconPath))
                self._window.setCentralWidget(centralWidget)
@@ -277,8 +275,7 @@ class Recent(object):
 
                self._window = QtGui.QMainWindow(parent)
                self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
-               maeqt.set_autorient(self._window, True)
-               maeqt.set_stackable(self._window, True)
+               qui_utils.set_stackable(self._window, True)
                self._window.setWindowTitle("%s - Recent" % constants.__pretty_app_name__)
                self._window.setWindowIcon(QtGui.QIcon(self._app.appIconPath))
                self._window.setCentralWidget(centralWidget)