#!/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
+
def _radius_at(center, pos):
- xDelta = pos.x() - center.x()
- yDelta = pos.y() - center.y()
+ delta = pos - center
+ xDelta = delta.x()
+ yDelta = delta.y()
radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
return radius
def _angle_at(center, pos):
- xDelta = pos.x() - center.x()
- yDelta = pos.y() - center.y()
+ delta = pos - center
+ xDelta = delta.x()
+ yDelta = delta.y()
radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
angle = math.acos(xDelta / radius)
if 0 <= yDelta:
- angle = 2*math.pi - angle
+ angle = _TWOPI - angle
return angle
self._children = []
self._center = self.NULL_CENTER
+ self._cacheIndexToAngle = {}
+ self._cacheTotalWeight = 0
+
def insertItem(self, item, index = -1):
self._children.insert(index, item)
+ self._invalidate_cache()
def removeItemAt(self, index):
item = self._children.pop(index)
+ self._invalidate_cache()
def set_center(self, item):
if item is None:
def clear(self):
del self._children[:]
+ self._center = self.NULL_CENTER
+ self._invalidate_cache()
def itemAt(self, index):
return self._children[index]
def __getitem__(self, index):
return self._children[index]
+ def _invalidate_cache(self):
+ self._cacheIndexToAngle.clear()
+ self._cacheTotalWeight = sum(child.weight() for child in self._children)
+ if self._cacheTotalWeight == 0:
+ self._cacheTotalWeight = 1
+
def _index_to_angle(self, index, isShifted):
+ key = index, isShifted
+ if key in self._cacheIndexToAngle:
+ return self._cacheIndexToAngle[key]
index = index % len(self._children)
- totalWeight = sum(child.weight() for child in self._children)
- if totalWeight == 0:
- totalWeight = 1
- baseAngle = (2 * math.pi) / totalWeight
+ baseAngle = _TWOPI / self._cacheTotalWeight
angle = math.pi / 2
if isShifted:
else:
angle -= baseAngle / 2
while angle < 0:
- angle += 2*math.pi
+ angle += _TWOPI
for i, child in enumerate(self._children):
if index < i:
break
angle += child.weight() * baseAngle
- while (2*math.pi) < angle:
- angle -= 2*math.pi
+ while _TWOPI < angle:
+ angle -= _TWOPI
+ self._cacheIndexToAngle[key] = angle
return angle
def _angle_to_index(self, angle):
if numChildren == 0:
return self.SELECTION_CENTER
- totalWeight = sum(child.weight() for child in self._children)
- if totalWeight == 0:
- totalWeight = 1
- baseAngle = (2 * math.pi) / totalWeight
+ baseAngle = _TWOPI / self._cacheTotalWeight
iterAngle = math.pi / 2 - (self.itemAt(0).weight() * baseAngle) / 2
while iterAngle < 0:
- iterAngle += 2 * math.pi
+ iterAngle += _TWOPI
oldIterAngle = iterAngle
for index, child in enumerate(self._children):
iterAngle += child.weight() * baseAngle
if oldIterAngle < angle and angle <= iterAngle:
return index - 1 if index != 0 else numChildren - 1
- elif oldIterAngle < (angle + 2*math.pi) and (angle + 2*math.pi <= iterAngle):
+ elif oldIterAngle < (angle + _TWOPI) and (angle + _TWOPI <= iterAngle):
return index - 1 if index != 0 else numChildren - 1
oldIterAngle = iterAngle
painter.setBrush(self.palette.highlight())
else:
painter.setBrush(self.palette.background())
+ 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:
a = self._filing._index_to_angle(i, True)
b = self._filing._index_to_angle(i + 1, True)
if b < a:
- b += 2*math.pi
+ b += _TWOPI
size = b - a
if size < 0:
- size += 2*math.pi
+ size += _TWOPI
- startAngleInDeg = (a * 360 * 16) / (2*math.pi)
- sizeInDeg = (size * 360 * 16) / (2*math.pi)
+ startAngleInDeg = (a * 360 * 16) / _TWOPI
+ sizeInDeg = (size * 360 * 16) / _TWOPI
painter.drawPie(adjustmentRect, int(startAngleInDeg), int(sizeInDeg))
def _paint_slice_foreground(self, painter, i, selectionIndex):
a = self._filing._index_to_angle(i, True)
b = self._filing._index_to_angle(i + 1, True)
if b < a:
- b += 2*math.pi
+ b += _TWOPI
middleAngle = (a + b) / 2
averageRadius = (self._cachedInnerRadius + self._cachedOuterRadius) / 2
sliceX = averageRadius * math.cos(middleAngle)
sliceY = - averageRadius * math.sin(middleAngle)
- pieX = self._canvas.rect().center().x()
- pieY = self._canvas.rect().center().y()
+ piePos = self._canvas.rect().center()
+ pieX = piePos.x()
+ pieY = piePos.y()
self._paint_label(
painter, child.action(), i == selectionIndex, pieX+sliceX, pieY+sliceY
)
QtGui.QIcon.Normal,
QtGui.QIcon.On,
)
- averageWidth = (icon.width() + textWidth)/2
+ iconWidth = icon.width()
+ iconHeight = icon.width()
+ averageWidth = (iconWidth + textWidth)/2
if not icon.isNull():
iconRect = QtCore.QRect(
x - averageWidth,
- y - icon.height()/2,
- icon.width(),
- icon.height(),
+ y - iconHeight/2,
+ iconWidth,
+ iconHeight,
)
painter.drawPixmap(iconRect, icon)
pen = self.palette.mid()
brush = self.palette.background()
- leftX = x - averageWidth + icon.width()
+ leftX = x - averageWidth + iconWidth
topY = y + textHeight/2
painter.setPen(pen.color())
painter.setBrush(brush)
background = self.palette.background().color()
innerRadius = self._cachedInnerRadius
+ adjustmentCenterPos = adjustmentRect.center()
innerRect = QtCore.QRect(
- adjustmentRect.center().x() - innerRadius,
- adjustmentRect.center().y() - innerRadius,
+ adjustmentCenterPos.x() - innerRadius,
+ adjustmentCenterPos.y() - innerRadius,
innerRadius * 2 + 1,
innerRadius * 2 + 1,
)
painter.drawEllipse(adjustmentRect)
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)
+ 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)
def _paint_center_foreground(self, painter, selectionIndex):
- pieX = self._canvas.rect().center().x()
- pieY = self._canvas.rect().center().y()
+ centerPos = self._canvas.rect().center()
+ pieX = centerPos.x()
+ pieY = centerPos.y()
x = pieX
y = pieY
class QPieDisplay(QtGui.QWidget):
- def __init__(self, filing, parent = None, flags = 0):
+ def __init__(self, filing, parent = None, flags = QtCore.Qt.Window):
QtGui.QWidget.__init__(self, parent, flags)
self._filing = filing
self._artist = PieArtist(self._filing)
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)
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):
aboutToShow = QtCore.pyqtSignal()
aboutToHide = QtCore.pyqtSignal()
+ BUTTON_RADIUS = 24
+ DELAY = 250
+
def __init__(self, buttonSlice, parent = None):
QtGui.QWidget.__init__(self, parent)
+ self._cachedCenterPosition = self.rect().center()
+
self._filing = PieFiling()
self._display = QPieDisplay(self._filing, None, QtCore.Qt.SplashScreen)
self._selectionIndex = PieFiling.SELECTION_NONE
self._buttonFiling = PieFiling()
self._buttonFiling.set_center(buttonSlice)
+ # @todo Figure out how to make the button auto-fill to content
+ 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)
return self._filing.itemAt(index)
def indexAt(self, point):
- return self._filing.indexAt(self.rect().center(), point)
+ return self._filing.indexAt(self._cachedCenterPosition, point)
def innerRadius(self):
return self._filing.innerRadius()
def setOuterRadius(self, radius):
self._filing.setOuterRadius(radius)
+ def buttonRadius(self):
+ return self._buttonFiling.outerRadius()
+
+ def setButtonRadius(self, radius):
+ self._buttonFiling.setOuterRadius(radius)
+
def sizeHint(self):
return self._buttonArtist.pieSize()
+ @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.rect().center())
+ 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
self._update_selection(lastMousePos)
else:
# Relative
- self._update_selection(self.rect().center() + (lastMousePos - self._mousePosition))
+ self._update_selection(self._cachedCenterPosition + (lastMousePos - self._mousePosition))
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()
self._update_selection(lastMousePos)
else:
# Relative
- self._update_selection(self.rect().center() + (lastMousePos - self._mousePosition))
+ self._update_selection(self._cachedCenterPosition + (lastMousePos - self._mousePosition))
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())
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 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):
if self._poppedUp:
canvas = self._buttonArtist.paint(PieFiling.SELECTION_CENTER)
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()
self._selectionIndex = index
def _update_selection(self, lastMousePos):
- radius = _radius_at(self.rect().center(), lastMousePos)
+ radius = _radius_at(self._cachedCenterPosition, lastMousePos)
if radius < self._filing.innerRadius():
self._select_at(PieFiling.SELECTION_CENTER)
elif radius <= self._filing.outerRadius():
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
+ self._cachedCenterPosition = self.rect().center()
+
self._filing = PieFiling()
self._artist = PieArtist(self._filing)
self._selectionIndex = PieFiling.SELECTION_NONE
return self._filing.itemAt(index)
def indexAt(self, point):
- return self._filing.indexAt(self.rect().center(), point)
+ return self._filing.indexAt(self._cachedCenterPosition, point)
def innerRadius(self):
return self._filing.innerRadius()
def sizeHint(self):
return self._artist.pieSize()
+ @misc_utils.log_exception(_moduleLogger)
def mousePressEvent(self, mouseEvent):
lastSelection = self._selectionIndex
self.highlighted.emit(self._selectionIndex)
self.update()
+ @misc_utils.log_exception(_moduleLogger)
def mouseMoveEvent(self, mouseEvent):
lastSelection = self._selectionIndex
self.highlighted.emit(self._selectionIndex)
self.update()
+ @misc_utils.log_exception(_moduleLogger)
def mouseReleaseEvent(self, mouseEvent):
lastSelection = self._selectionIndex
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]:
- self._select_at(self._selectionIndex + 1)
+ if self._selectionIndex != len(self._filing) - 1:
+ nextSelection = self._selectionIndex + 1
+ else:
+ nextSelection = 0
+ self._select_at(nextSelection)
self.update()
elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
- self._select_at(self._selectionIndex - 1)
+ if 0 < self._selectionIndex:
+ nextSelection = self._selectionIndex - 1
+ else:
+ nextSelection = len(self._filing) - 1
+ self._select_at(nextSelection)
self.update()
elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
self._activate_at(self._selectionIndex)
else:
QtGui.QWidget.keyPressEvent(self, keyEvent)
+ @misc_utils.log_exception(_moduleLogger)
def showEvent(self, showEvent):
self.aboutToShow.emit()
+ self._cachedCenterPosition = self.rect().center()
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)
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
- numChildren = len(self._filing)
- loopDelta = max(numChildren, 1)
- while self._selectionIndex < 0:
- self._selectionIndex += loopDelta
- while numChildren <= self._selectionIndex:
- self._selectionIndex -= loopDelta
-
def _update_selection(self, lastMousePos):
- radius = _radius_at(self.rect().center(), lastMousePos)
+ radius = _radius_at(self._cachedCenterPosition, lastMousePos)
if radius < self._filing.innerRadius():
self._selectionIndex = PieFiling.SELECTION_CENTER
elif radius <= self._filing.outerRadius():
self.hide()
+def init_pies():
+ PieFiling.NULL_CENTER.setEnabled(False)
+
+
def _print(msg):
print msg
if __name__ == "__main__":
app = QtGui.QApplication([])
- PieFiling.NULL_CENTER.setEnabled(False)
+ init_pies()
if False:
pie = QPieMenu()
mpie.insertItem(iconTextItem)
mpie.show()
- if False:
+ if True:
oneAction = QtGui.QAction(None)
oneAction.setText("Chew")
oneAction.triggered.connect(lambda: _print("Chew"))
mpie = QPieDisplay(pieFiling)
mpie.show()
- if True:
+ if False:
oneAction = QtGui.QAction(None)
oneAction.setText("Chew")
oneAction.triggered.connect(lambda: _print("Chew"))