Polishing things up
[ejpi] / src / libraries / qtpie.py
index 52239a0..884d5ce 100755 (executable)
@@ -1,10 +1,26 @@
 #!/usr/bin/env python
 
 import math
+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
+
+
+_moduleLogger = logging.getLogger(__name__)
+
 
 _TWOPI = 2 * math.pi
 
@@ -55,8 +71,8 @@ class QActionPieItem(object):
 
 class PieFiling(object):
 
-       INNER_RADIUS_DEFAULT = 32
-       OUTER_RADIUS_DEFAULT = 128
+       INNER_RADIUS_DEFAULT = 64
+       OUTER_RADIUS_DEFAULT = 192
 
        SELECTION_CENTER = -1
        SELECTION_NONE = -2
@@ -176,7 +192,11 @@ class PieFiling(object):
 
 class PieArtist(object):
 
-       ICON_SIZE_DEFAULT = 32
+       ICON_SIZE_DEFAULT = 48
+
+       SHAPE_CIRCLE = "circle"
+       SHAPE_SQUARE = "square"
+       DEFAULT_SHAPE = SHAPE_SQUARE
 
        def __init__(self, filing):
                self._filing = filing
@@ -240,16 +260,17 @@ class PieArtist(object):
                        if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
                                painter.setBrush(self.palette.highlight())
                        else:
-                               painter.setBrush(self.palette.background())
+                               painter.setBrush(self.palette.window())
+                       painter.setPen(self.palette.mid().color())
 
-                       painter.fillRect(self._canvas.rect(), painter.brush())
+                       painter.drawRect(self._canvas.rect())
                        self._paint_center_foreground(painter, 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.background())
+                               painter.setBrush(self.palette.window())
 
                        painter.fillRect(self._canvas.rect(), painter.brush())
                else:
@@ -271,13 +292,28 @@ class PieArtist(object):
                painter = QtGui.QPainter(mask)
                painter.setPen(QtCore.Qt.color1)
                painter.setBrush(QtCore.Qt.color1)
-               painter.drawEllipse(mask.rect().adjusted(0, 0, -1, -1))
+               if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
+                       painter.drawRect(mask.rect())
+               elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
+                       painter.drawEllipse(mask.rect().adjusted(0, 0, -1, -1))
+               else:
+                       raise NotImplementedError(self.DEFAULT_SHAPE)
 
        def _paint_slice_background(self, painter, adjustmentRect, i, selectionIndex):
+               if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
+                       currentWidth = adjustmentRect.width()
+                       newWidth = math.sqrt(2) * currentWidth
+                       dx = (newWidth - currentWidth) / 2
+                       adjustmentRect = adjustmentRect.adjusted(-dx, -dx, dx, dx)
+               elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
+                       pass
+               else:
+                       raise NotImplementedError(self.DEFAULT_SHAPE)
+
                if i == selectionIndex and self._filing[i].isEnabled():
                        painter.setBrush(self.palette.highlight())
                else:
-                       painter.setBrush(self.palette.background())
+                       painter.setBrush(self.palette.window())
                painter.setPen(self.palette.mid().color())
 
                a = self._filing._index_to_angle(i, True)
@@ -347,13 +383,13 @@ class PieArtist(object):
                                        brush = self.palette.highlight()
                                else:
                                        pen = self.palette.mid()
-                                       brush = self.palette.background()
+                                       brush = self.palette.window()
                        else:
                                if action.isEnabled():
-                                       pen = self.palette.text()
+                                       pen = self.palette.windowText()
                                else:
                                        pen = self.palette.mid()
-                               brush = self.palette.background()
+                               brush = self.palette.window()
 
                        leftX = x - averageWidth + iconWidth
                        topY = y + textHeight/2
@@ -362,12 +398,12 @@ class PieArtist(object):
                        painter.drawText(leftX, topY, text)
 
        def _paint_center_background(self, painter, adjustmentRect, selectionIndex):
-               dark = self.palette.dark().color()
+               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.background().color()
+                       background = self.palette.window().color()
 
                innerRadius = self._cachedInnerRadius
                adjustmentCenterPos = adjustmentRect.center()
@@ -386,16 +422,14 @@ class PieArtist(object):
                painter.setBrush(QtCore.Qt.NoBrush)
                painter.drawEllipse(innerRect)
 
-               painter.setPen(QtGui.QPen(dark, 1))
-               painter.setBrush(QtCore.Qt.NoBrush)
-               painter.drawEllipse(adjustmentRect)
-
-               r = QtCore.QRect(innerRect)
-               innerCenter = r.center()
-               innerRect.setLeft(innerCenter.x() + ((r.left() - innerCenter.x()) / 3) * 1)
-               innerRect.setRight(innerCenter.x() + ((r.right() - innerCenter.x()) / 3) * 1)
-               innerRect.setTop(innerCenter.y() + ((r.top() - innerCenter.y()) / 3) * 1)
-               innerRect.setBottom(innerCenter.y() + ((r.bottom() - innerCenter.y()) / 3) * 1)
+               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()
@@ -428,17 +462,20 @@ class QPieDisplay(QtGui.QWidget):
        def sizeHint(self):
                return self._artist.pieSize()
 
+       @misc_utils.log_exception(_moduleLogger)
        def showEvent(self, showEvent):
                mask = self._artist.show(self.palette())
                self.setMask(mask)
 
                QtGui.QWidget.showEvent(self, showEvent)
 
+       @misc_utils.log_exception(_moduleLogger)
        def hideEvent(self, hideEvent):
                self._artist.hide()
                self._selectionIndex = PieFiling.SELECTION_NONE
                QtGui.QWidget.hideEvent(self, hideEvent)
 
+       @misc_utils.log_exception(_moduleLogger)
        def paintEvent(self, paintEvent):
                canvas = self._artist.paint(self._selectionIndex)
 
@@ -448,8 +485,10 @@ class QPieDisplay(QtGui.QWidget):
                QtGui.QWidget.paintEvent(self, paintEvent)
 
        def selectAt(self, index):
+               oldIndex = self._selectionIndex
                self._selectionIndex = index
-               self.update()
+               if self.isVisible():
+                       self.update()
 
 
 class QPieButton(QtGui.QWidget):
@@ -460,7 +499,12 @@ class QPieButton(QtGui.QWidget):
        aboutToShow = QtCore.pyqtSignal()
        aboutToHide = QtCore.pyqtSignal()
 
+       BUTTON_RADIUS = 24
+       DELAY = 250
+
        def __init__(self, buttonSlice, parent = 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)
                self._cachedCenterPosition = self.rect().center()
 
@@ -470,11 +514,16 @@ class QPieButton(QtGui.QWidget):
 
                self._buttonFiling = PieFiling()
                self._buttonFiling.set_center(buttonSlice)
+               self._buttonFiling.setOuterRadius(self.BUTTON_RADIUS)
                self._buttonArtist = PieArtist(self._buttonFiling)
-               centerSize = self._buttonArtist.centerSize()
-               self._buttonFiling.setOuterRadius(max(centerSize.width(), centerSize.height()))
                self._poppedUp = False
 
+               self._delayPopupTimer = QtCore.QTimer()
+               self._delayPopupTimer.setInterval(self.DELAY)
+               self._delayPopupTimer.setSingleShot(True)
+               self._delayPopupTimer.timeout.connect(self._on_delayed_popup)
+               self._popupLocation = None
+
                self._mousePosition = None
                self.setFocusPolicy(QtCore.Qt.StrongFocus)
 
@@ -511,21 +560,36 @@ class QPieButton(QtGui.QWidget):
        def setOuterRadius(self, radius):
                self._filing.setOuterRadius(radius)
 
-       def sizeHint(self):
-               return self._buttonArtist.pieSize()
+       def buttonRadius(self):
+               return self._buttonFiling.outerRadius()
+
+       def setButtonRadius(self, radius):
+               self._buttonFiling.setOuterRadius(radius)
+               self._buttonArtist.show(self.palette())
 
+       def minimumSizeHint(self):
+               return self._buttonArtist.centerSize()
+
+       @misc_utils.log_exception(_moduleLogger)
        def mousePressEvent(self, mouseEvent):
-               self._popup_child(mouseEvent.globalPos())
                lastSelection = self._selectionIndex
 
                lastMousePos = mouseEvent.pos()
                self._mousePosition = lastMousePos
                self._update_selection(self._cachedCenterPosition)
 
-               if lastSelection != self._selectionIndex:
-                       self.highlighted.emit(self._selectionIndex)
-                       self._display.selectAt(self._selectionIndex)
+               self.highlighted.emit(self._selectionIndex)
+
+               self._display.selectAt(self._selectionIndex)
+               self._popupLocation = mouseEvent.globalPos()
+               self._delayPopupTimer.start()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_delayed_popup(self):
+               assert self._popupLocation is not None
+               self._popup_child(self._popupLocation)
 
+       @misc_utils.log_exception(_moduleLogger)
        def mouseMoveEvent(self, mouseEvent):
                lastSelection = self._selectionIndex
 
@@ -535,13 +599,23 @@ class QPieButton(QtGui.QWidget):
                        self._update_selection(lastMousePos)
                else:
                        # Relative
-                       self._update_selection(self._cachedCenterPosition + (lastMousePos - self._mousePosition))
+                       self._update_selection(
+                               self._cachedCenterPosition + (lastMousePos - self._mousePosition),
+                               ignoreOuter = True,
+                       )
 
                if lastSelection != self._selectionIndex:
                        self.highlighted.emit(self._selectionIndex)
                        self._display.selectAt(self._selectionIndex)
 
+               if self._selectionIndex != PieFiling.SELECTION_CENTER and self._delayPopupTimer.isActive():
+                       self._on_delayed_popup()
+
+       @misc_utils.log_exception(_moduleLogger)
        def mouseReleaseEvent(self, mouseEvent):
+               self._delayPopupTimer.stop()
+               self._popupLocation = None
+
                lastSelection = self._selectionIndex
 
                lastMousePos = mouseEvent.pos()
@@ -550,12 +624,16 @@ class QPieButton(QtGui.QWidget):
                        self._update_selection(lastMousePos)
                else:
                        # Relative
-                       self._update_selection(self._cachedCenterPosition + (lastMousePos - self._mousePosition))
+                       self._update_selection(
+                               self._cachedCenterPosition + (lastMousePos - self._mousePosition),
+                               ignoreOuter = True,
+                       )
                self._mousePosition = None
 
                self._activate_at(self._selectionIndex)
                self._hide_child()
 
+       @misc_utils.log_exception(_moduleLogger)
        def keyPressEvent(self, keyEvent):
                if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
                        self._popup_child(QtGui.QCursor.pos())
@@ -578,26 +656,39 @@ class QPieButton(QtGui.QWidget):
                        self._select_at(PieFiling.SELECTION_CENTER)
                        self._display.selectAt(self._selectionIndex)
                elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
+                       self._delayPopupTimer.stop()
+                       self._popupLocation = None
                        self._activate_at(self._selectionIndex)
                        self._hide_child()
                elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
+                       self._delayPopupTimer.stop()
+                       self._popupLocation = None
                        self._activate_at(PieFiling.SELECTION_NONE)
                        self._hide_child()
                else:
                        QtGui.QWidget.keyPressEvent(self, keyEvent)
 
+       @misc_utils.log_exception(_moduleLogger)
+       def resizeEvent(self, resizeEvent):
+               self.setButtonRadius(min(resizeEvent.size().width(), resizeEvent.size().height()) / 2 - 1)
+               QtGui.QWidget.resizeEvent(self, resizeEvent)
+
+       @misc_utils.log_exception(_moduleLogger)
        def showEvent(self, showEvent):
                self._buttonArtist.show(self.palette())
                self._cachedCenterPosition = self.rect().center()
 
                QtGui.QWidget.showEvent(self, showEvent)
 
+       @misc_utils.log_exception(_moduleLogger)
        def hideEvent(self, hideEvent):
                self._display.hide()
                self._select_at(PieFiling.SELECTION_NONE)
                QtGui.QWidget.hideEvent(self, hideEvent)
 
+       @misc_utils.log_exception(_moduleLogger)
        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)
                else:
@@ -608,10 +699,19 @@ class QPieButton(QtGui.QWidget):
 
                QtGui.QWidget.paintEvent(self, paintEvent)
 
+       def __iter__(self):
+               return iter(self._filing)
+
+       def __len__(self):
+               return len(self._filing)
+
        def _popup_child(self, position):
                self._poppedUp = True
                self.aboutToShow.emit()
 
+               self._delayPopupTimer.stop()
+               self._popupLocation = None
+
                position = position - QtCore.QPoint(self._filing.outerRadius(), self._filing.outerRadius())
                self._display.move(position)
                self._display.show()
@@ -627,11 +727,11 @@ class QPieButton(QtGui.QWidget):
        def _select_at(self, index):
                self._selectionIndex = index
 
-       def _update_selection(self, lastMousePos):
+       def _update_selection(self, lastMousePos, ignoreOuter = False):
                radius = _radius_at(self._cachedCenterPosition, lastMousePos)
                if radius < self._filing.innerRadius():
                        self._select_at(PieFiling.SELECTION_CENTER)
-               elif radius <= self._filing.outerRadius():
+               elif radius <= self._filing.outerRadius() or ignoreOuter:
                        self._select_at(self.indexAt(lastMousePos))
                else:
                        self._select_at(PieFiling.SELECTION_NONE)
@@ -714,6 +814,7 @@ class QPieMenu(QtGui.QWidget):
        def sizeHint(self):
                return self._artist.pieSize()
 
+       @misc_utils.log_exception(_moduleLogger)
        def mousePressEvent(self, mouseEvent):
                lastSelection = self._selectionIndex
 
@@ -725,6 +826,7 @@ class QPieMenu(QtGui.QWidget):
                        self.highlighted.emit(self._selectionIndex)
                        self.update()
 
+       @misc_utils.log_exception(_moduleLogger)
        def mouseMoveEvent(self, mouseEvent):
                lastSelection = self._selectionIndex
 
@@ -735,6 +837,7 @@ class QPieMenu(QtGui.QWidget):
                        self.highlighted.emit(self._selectionIndex)
                        self.update()
 
+       @misc_utils.log_exception(_moduleLogger)
        def mouseReleaseEvent(self, mouseEvent):
                lastSelection = self._selectionIndex
 
@@ -745,6 +848,7 @@ class QPieMenu(QtGui.QWidget):
                self._activate_at(self._selectionIndex)
                self.update()
 
+       @misc_utils.log_exception(_moduleLogger)
        def keyPressEvent(self, keyEvent):
                if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
                        if self._selectionIndex != len(self._filing) - 1:
@@ -767,6 +871,7 @@ class QPieMenu(QtGui.QWidget):
                else:
                        QtGui.QWidget.keyPressEvent(self, keyEvent)
 
+       @misc_utils.log_exception(_moduleLogger)
        def showEvent(self, showEvent):
                self.aboutToShow.emit()
                self._cachedCenterPosition = self.rect().center()
@@ -779,11 +884,13 @@ class QPieMenu(QtGui.QWidget):
 
                QtGui.QWidget.showEvent(self, showEvent)
 
+       @misc_utils.log_exception(_moduleLogger)
        def hideEvent(self, hideEvent):
                self._artist.hide()
                self._selectionIndex = PieFiling.SELECTION_NONE
                QtGui.QWidget.hideEvent(self, hideEvent)
 
+       @misc_utils.log_exception(_moduleLogger)
        def paintEvent(self, paintEvent):
                canvas = self._artist.paint(self._selectionIndex)
 
@@ -792,6 +899,12 @@ class QPieMenu(QtGui.QWidget):
 
                QtGui.QWidget.paintEvent(self, paintEvent)
 
+       def __iter__(self):
+               return iter(self._filing)
+
+       def __len__(self):
+               return len(self._filing)
+
        def _select_at(self, index):
                self._selectionIndex = index
 
@@ -824,6 +937,10 @@ class QPieMenu(QtGui.QWidget):
                self.hide()
 
 
+def init_pies():
+       PieFiling.NULL_CENTER.setEnabled(False)
+
+
 def _print(msg):
        print msg
 
@@ -834,7 +951,7 @@ def _on_about_to_hide(app):
 
 if __name__ == "__main__":
        app = QtGui.QApplication([])
-       PieFiling.NULL_CENTER.setEnabled(False)
+       init_pies()
 
        if False:
                pie = QPieMenu()