Can now draw a basic pie
authorEd Page <eopage@byu.net>
Wed, 23 Jun 2010 11:41:00 +0000 (06:41 -0500)
committerEd Page <eopage@byu.net>
Wed, 23 Jun 2010 11:41:00 +0000 (06:41 -0500)
src/libraries/qtpie.py [new file with mode: 0755]

diff --git a/src/libraries/qtpie.py b/src/libraries/qtpie.py
new file mode 100755 (executable)
index 0000000..4a463fa
--- /dev/null
@@ -0,0 +1,420 @@
+#!/usr/bin/env python
+
+import math
+
+from PyQt4 import QtGui
+from PyQt4 import QtCore
+
+
+class QActionPieItem(object):
+
+       def __init__(self, action, weight = 1):
+               self._action = action
+               self._weight = weight
+
+       def action(self):
+               return self._action
+
+       def setWeight(self, weight):
+               self._weight = weight
+
+       def weight(self):
+               return self._weight
+
+       def setEnabled(self, enabled = True):
+               self._action.setEnabled(enabled)
+
+       def isEnabled(self):
+               return self._action.isEnabled()
+
+
+class QPieMenu(QtGui.QWidget):
+
+       INNER_RADIUS_DEFAULT = 24
+       OUTER_RADIUS_DEFAULT = 64
+       ICON_SIZE_DEFAULT = 32
+
+       activated = QtCore.pyqtSignal((), (int, ))
+       highlighted = QtCore.pyqtSignal(int)
+       canceled = QtCore.pyqtSignal()
+       aboutToShow = QtCore.pyqtSignal()
+       aboutToHide = QtCore.pyqtSignal()
+
+       def __init__(self, parent = None):
+               QtGui.QWidget.__init__(self, parent)
+               self._innerRadius = self.INNER_RADIUS_DEFAULT
+               self._outerRadius = self.OUTER_RADIUS_DEFAULT
+               self._children = []
+               self._selectionIndex = -2
+
+               self._motion = 0
+               self._mouseButtonPressed = True
+               self._mousePosition = ()
+
+               canvasSize = self._outerRadius * 2 + 1
+               self._canvas = QtGui.QPixmap(canvasSize, canvasSize)
+               self._mask = None
+
+       def popup(self, pos):
+               index = self.indexAt(self.mapFromGlobal(QtGui.QCursor.pos()))
+               self._mousePosition = pos
+               self.show()
+
+       def insertItem(self, item, index = -1):
+               self._children.insert(index, item)
+               self._invalidate_view()
+
+       def removeItemAt(self, index):
+               item = self._children.pop(index)
+               self._invalidate_view()
+
+       def clear(self):
+               del self._children[:]
+               self._invalidate_view()
+
+       def itemAt(self, index):
+               return self._children[index]
+
+       def indexAt(self, point):
+               return self._angle_to_index(self._angle_at(point))
+
+       def setHighlightedItem(self, index):
+               pass
+
+       def highlightedItem(self):
+               pass
+
+       def innerRadius(self):
+               return self._innerRadius
+
+       def setInnerRadius(self, radius):
+               self._innerRadius = radius
+
+       def outerRadius(self):
+               return self._outerRadius
+
+       def setOuterRadius(self, radius):
+               self._outerRadius = radius
+               self._canvas = self._canvas.scaled(self.sizeHint())
+
+       def sizeHint(self):
+               diameter = self._outerRadius * 2 + 1
+               return QtCore.QSize(diameter, diameter)
+
+       def showEvent(self, showEvent):
+               self.aboutToShow.emit()
+
+               if self._mask is None:
+                       self._mask = QtGui.QBitmap(self._canvas.size())
+                       self._mask.fill(QtCore.Qt.color0)
+                       self._generate_mask(self._mask)
+                       self._canvas.setMask(self._mask)
+                       self.setMask(self._mask)
+
+               self._motion = 0
+
+               lastMousePos = self.mapFromGlobal(QtGui.QCursor.pos())
+               radius = self._radius_at(lastMousePos)
+               if self._innerRadius <= radius and radius <= self._outerRadius:
+                       self._select_at(self._angle_to_index(lastMousePos))
+               else:
+                       if radius < self._innerRadius:
+                               self._selectionIndex = -1
+                       else:
+                               self._selectionIndex = -2
+
+               QtGui.QWidget.showEvent(self, showEvent)
+
+       def hideEvent(self, hideEvent):
+               self.canceled.emit()
+               self._selectionIndex = -2
+               QtGui.QWidget.hideEvent(self, hideEvent)
+
+       def paintEvent(self, paintEvent):
+               painter = QtGui.QPainter(self._canvas)
+               painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
+
+               adjustmentRect = self._canvas.rect().adjusted(0, 0, -1, -1)
+
+               numChildren = len(self._children)
+               if numChildren < 2:
+                       if self._selectionIndex == 0 and self._children[0].isEnabled():
+                               painter.setBrush(self.palette().highlight())
+                       else:
+                               painter.setBrush(self.palette().background())
+
+                       painter.fillRect(self.rect(), painter.brush())
+               else:
+                       for i, child in enumerate(self._children):
+                               if i == self._selectionIndex:
+                                       painter.setBrush(self.palette().highlight())
+                               else:
+                                       painter.setBrush(self.palette().background())
+                               painter.setPen(self.palette().mid().color())
+
+                               a = self._index_to_angle(i, True)
+                               b = self._index_to_angle(i + 1, True)
+                               if b < a:
+                                       b += 2*math.pi
+                               size = b - a
+                               if size < 0:
+                                       size += 2*math.pi
+
+                               startAngleInDeg = (a * 360 * 16) / (2*math.pi)
+                               sizeInDeg = (size * 360 * 16) / (2*math.pi)
+                               painter.drawPie(adjustmentRect, int(startAngleInDeg), int(sizeInDeg))
+
+               dark = self.palette().dark().color()
+               light = self.palette().light().color()
+               if self._selectionIndex == -1:
+                       background = self.palette().highlight().color()
+               else:
+                       background = self.palette().background().color()
+
+               innerRect = QtCore.QRect(
+                       adjustmentRect.center().x() - self._innerRadius,
+                       adjustmentRect.center().y() - self._innerRadius,
+                       self._innerRadius * 2 + 1,
+                       self._innerRadius * 2 + 1,
+               )
+
+               painter.setPen(QtCore.Qt.NoPen)
+               painter.setBrush(background)
+               painter.drawPie(innerRect, 0, 360 * 16)
+
+               light.setAlpha(128)
+               painter.setPen(QtGui.QPen(light, 1))
+               painter.setBrush(QtCore.Qt.NoBrush)
+               painter.drawArc(innerRect, 225 * 16, 180 * 16)
+
+               painter.setPen(QtGui.QPen(dark, 1))
+               painter.drawArc(innerRect, 45 * 16, 180 * 16)
+
+               painter.setPen(QtGui.QPen(light, 1))
+               painter.setBrush(QtCore.Qt.NoBrush)
+               painter.drawArc(adjustmentRect, 45 * 16, 180 * 16)
+               painter.setPen(QtGui.QPen(dark, 1))
+               painter.drawArc(adjustmentRect, 225 * 16, 180 * 16)
+
+               r = QtCore.QRect(innerRect)
+               innerRect.setLeft(r.center().x() + ((r.left() - r.center().x()) / 3) * 1)
+               innerRect.setRight(r.center().x() + ((r.right() - r.center().x()) / 3) * 1)
+               innerRect.setTop(r.center().y() + ((r.top() - r.center().y()) / 3) * 1)
+               innerRect.setBottom(r.center().y() + ((r.bottom() - r.center().y()) / 3) * 1)
+
+               if self._selectionIndex == -1:
+                       text = self.palette().highlightedText().color()
+               else:
+                       text = self.palette().text().color()
+
+               for i, child in enumerate(self._children):
+                       text = child.action().text()
+
+                       a = self._index_to_angle(i, True)
+                       b = self._index_to_angle(i + 1, True)
+                       if b < a:
+                               b += 2*math.pi
+                       middleAngle = (a + b) / 2
+                       averageRadius = (self._innerRadius + self._outerRadius) / 2
+
+                       sliceX = averageRadius * math.cos(middleAngle)
+                       sliceY = - averageRadius * math.sin(middleAngle)
+
+                       pieX = self._canvas.rect().center().x()
+                       pieY = self._canvas.rect().center().y()
+
+                       fontMetrics = painter.fontMetrics()
+                       if text:
+                               textBoundingRect = fontMetrics.boundingRect(text)
+                       else:
+                               textBoundingRect = QtCore.QRect()
+                       textWidth = textBoundingRect.width()
+                       textHeight = textBoundingRect.height()
+
+                       icon = child.action().icon().pixmap(
+                               QtCore.QSize(self.ICON_SIZE_DEFAULT, self.ICON_SIZE_DEFAULT),
+                               QtGui.QIcon.Normal,
+                               QtGui.QIcon.On,
+                       )
+                       averageWidth = (icon.width() + textWidth)/2
+                       if not icon.isNull():
+                               iconRect = QtCore.QRect(
+                                       pieX + sliceX - averageWidth,
+                                       pieY + sliceY - icon.height()/2,
+                                       icon.width(),
+                                       icon.height(),
+                               )
+
+                               painter.drawPixmap(iconRect, icon)
+
+                       if text:
+                               if i == self._selectionIndex:
+                                       if child.action().isEnabled():
+                                               pen = self.palette().highlightedText()
+                                               brush = self.palette().highlight()
+                                       else:
+                                               pen = self.palette().mid()
+                                               brush = self.palette().background()
+                               else:
+                                       if child.action().isEnabled():
+                                               pen = self.palette().text()
+                                       else:
+                                               pen = self.palette().mid()
+                                       brush = self.palette().background()
+
+                               leftX = pieX + sliceX - averageWidth + icon.width()
+                               topY = pieY + sliceY + textHeight/2
+                               painter.setPen(pen.color())
+                               painter.setBrush(brush)
+                               painter.drawText(leftX, topY, text)
+
+               screen = QtGui.QPainter(self)
+               screen.drawPixmap(QtCore.QPoint(0, 0), self._canvas)
+
+               QtGui.QWidget.paintEvent(self, paintEvent)
+
+       def __len__(self):
+               return len(self._children)
+
+       def _invalidate_view(self):
+               pass
+
+       def _generate_mask(self, mask):
+               """
+               Specifies on the mask the shape of the pie menu
+               """
+               painter = QtGui.QPainter(mask)
+               painter.setPen(QtCore.Qt.color1)
+               painter.setBrush(QtCore.Qt.color1)
+               painter.drawEllipse(mask.rect().adjusted(0, 0, -1, -1))
+
+       def _select_at(self, index):
+               self._selectionIndex = index
+
+               numChildren = len(self._children)
+               loopDelta = max(numChildren, 1)
+               while self._selectionIndex < 0:
+                       self._selectionIndex += loopDelta
+               while numChildren <= self._selectionIndex:
+                       self._selectionIndex -= loopDelta
+
+       def _activate_at(self, index):
+               child = self.itemAt(index)
+               if child.action.isEnabled:
+                       child.action.trigger()
+               self.activated.emit()
+               self.aboutToHide.emit()
+               self.hide()
+
+       def _index_to_angle(self, index, isShifted):
+               index = index % len(self._children)
+
+               totalWeight = sum(child.weight() for child in self._children)
+               if totalWeight == 0:
+                       totalWeight = 1
+               baseAngle = (2 * math.pi) / totalWeight
+
+               angle = math.pi / 2
+               if isShifted:
+                       if self._children:
+                               angle -= (self._children[0].weight() * baseAngle) / 2
+                       else:
+                               angle -= baseAngle / 2
+               while angle < 0:
+                       angle += 2*math.pi
+
+               for i, child in enumerate(self._children):
+                       if index < i:
+                               break
+                       angle += child.weight() * baseAngle
+               while (2*math.pi) < angle:
+                       angle -= 2*math.pi
+
+               return angle
+
+       def _angle_to_index(self, angle):
+               numChildren = len(self._children)
+               if numChildren == 0:
+                       return -1
+
+               totalWeight = sum(child.weight() for child in self._children)
+               if totalWeight == 0:
+                       totalWeight = 1
+               baseAngle = (2 * math.pi) / totalWeight
+
+               iterAngle = math.pi / 2 - (self.itemAt(0).weight * baseAngle) / 2
+               while iterAngle < 0:
+                       iterAngle += 2 * math.pi
+
+               oldIterAngle = iterAngle
+               for index, child in enumerate(self._children):
+                       iterAngle += child.weight * baseAngle
+                       if oldIterAngle < iterAngle and angle <= iterAngle:
+                               return index
+                       elif oldIterAngle < (iterAngle + 2*math.pi) and angle <= (iterAngle + 2*math.pi):
+                               return index
+                       oldIterAngle = iterAngle
+
+       def _radius_at(self, pos):
+               xDelta = pos.x() - self.rect().center().x()
+               yDelta = pos.y() - self.rect().center().y()
+
+               radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
+               return radius
+
+       def _angle_at(self, pos):
+               xDelta = pos.x() - self.rect().center().x()
+               yDelta = pos.y() - self.rect().center().y()
+
+               radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
+               angle = math.acos(xDelta / radius)
+               if 0 <= yDelta:
+                       angle = 2*math.pi - angle
+
+               return angle
+
+       def _on_key_press(self, keyEvent):
+               if keyEvent.key in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
+                       self._select_at(self._selectionIndex + 1)
+               elif keyEvent.key in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
+                       self._select_at(self._selectionIndex - 1)
+               elif keyEvent.key in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
+                       self._motion = 0
+                       self._activate_at(self._selectionIndex)
+               elif keyEvent.key in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
+                       pass
+
+       def _on_mouse_press(self, mouseEvent):
+               self._mouseButtonPressed = True
+
+
+if __name__ == "__main__":
+       app = QtGui.QApplication([])
+
+       if False:
+               pie = QPieMenu()
+               pie.show()
+
+       if False:
+               singleAction = QtGui.QAction(None)
+               singleAction.setText("Boo")
+               singleItem = QActionPieItem(singleAction)
+               spie = QPieMenu()
+               spie.insertItem(singleItem)
+               spie.show()
+
+       if True:
+               oneAction = QtGui.QAction(None)
+               oneAction.setText("Chew")
+               oneItem = QActionPieItem(oneAction)
+               twoAction = QtGui.QAction(None)
+               twoAction.setText("Foo")
+               twoItem = QActionPieItem(twoAction)
+               mpie = QPieMenu()
+               mpie.insertItem(oneItem)
+               mpie.insertItem(twoItem)
+               mpie.insertItem(oneItem)
+               mpie.insertItem(twoItem)
+               mpie.show()
+
+       app.exec_()