6 from PyQt4 import QtGui
7 from PyQt4 import QtCore
10 from util import misc as misc_utils
12 class misc_utils(object):
15 def log_exception(logger):
22 _moduleLogger = logging.getLogger(__name__)
28 def _radius_at(center, pos):
33 radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
37 def _angle_at(center, pos):
42 radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
43 angle = math.acos(xDelta / radius)
45 angle = _TWOPI - angle
50 class QActionPieItem(object):
52 def __init__(self, action, weight = 1):
59 def setWeight(self, weight):
65 def setEnabled(self, enabled = True):
66 self._action.setEnabled(enabled)
69 return self._action.isEnabled()
72 class PieFiling(object):
74 INNER_RADIUS_DEFAULT = 64
75 OUTER_RADIUS_DEFAULT = 192
80 NULL_CENTER = QActionPieItem(QtGui.QAction(None))
83 self._innerRadius = self.INNER_RADIUS_DEFAULT
84 self._outerRadius = self.OUTER_RADIUS_DEFAULT
86 self._center = self.NULL_CENTER
88 self._cacheIndexToAngle = {}
89 self._cacheTotalWeight = 0
91 def insertItem(self, item, index = -1):
92 self._children.insert(index, item)
93 self._invalidate_cache()
95 def removeItemAt(self, index):
96 item = self._children.pop(index)
97 self._invalidate_cache()
99 def set_center(self, item):
101 item = self.NULL_CENTER
108 del self._children[:]
109 self._center = self.NULL_CENTER
110 self._invalidate_cache()
112 def itemAt(self, index):
113 return self._children[index]
115 def indexAt(self, center, point):
116 return self._angle_to_index(_angle_at(center, point))
118 def innerRadius(self):
119 return self._innerRadius
121 def setInnerRadius(self, radius):
122 self._innerRadius = radius
124 def outerRadius(self):
125 return self._outerRadius
127 def setOuterRadius(self, radius):
128 self._outerRadius = radius
131 return iter(self._children)
134 return len(self._children)
136 def __getitem__(self, index):
137 return self._children[index]
139 def _invalidate_cache(self):
140 self._cacheIndexToAngle.clear()
141 self._cacheTotalWeight = sum(child.weight() for child in self._children)
142 if self._cacheTotalWeight == 0:
143 self._cacheTotalWeight = 1
145 def _index_to_angle(self, index, isShifted):
146 key = index, isShifted
147 if key in self._cacheIndexToAngle:
148 return self._cacheIndexToAngle[key]
149 index = index % len(self._children)
151 baseAngle = _TWOPI / self._cacheTotalWeight
156 angle -= (self._children[0].weight() * baseAngle) / 2
158 angle -= baseAngle / 2
162 for i, child in enumerate(self._children):
165 angle += child.weight() * baseAngle
166 while _TWOPI < angle:
169 self._cacheIndexToAngle[key] = angle
172 def _angle_to_index(self, angle):
173 numChildren = len(self._children)
175 return self.SELECTION_CENTER
177 baseAngle = _TWOPI / self._cacheTotalWeight
179 iterAngle = math.pi / 2 - (self.itemAt(0).weight() * baseAngle) / 2
183 oldIterAngle = iterAngle
184 for index, child in enumerate(self._children):
185 iterAngle += child.weight() * baseAngle
186 if oldIterAngle < angle and angle <= iterAngle:
187 return index - 1 if index != 0 else numChildren - 1
188 elif oldIterAngle < (angle + _TWOPI) and (angle + _TWOPI <= iterAngle):
189 return index - 1 if index != 0 else numChildren - 1
190 oldIterAngle = iterAngle
193 class PieArtist(object):
195 ICON_SIZE_DEFAULT = 48
197 SHAPE_CIRCLE = "circle"
198 SHAPE_SQUARE = "square"
199 DEFAULT_SHAPE = SHAPE_SQUARE
201 def __init__(self, filing):
202 self._filing = filing
204 self._cachedOuterRadius = self._filing.outerRadius()
205 self._cachedInnerRadius = self._filing.innerRadius()
206 canvasSize = self._cachedOuterRadius * 2 + 1
207 self._canvas = QtGui.QPixmap(canvasSize, canvasSize)
212 diameter = self._filing.outerRadius() * 2 + 1
213 return QtCore.QSize(diameter, diameter)
215 def centerSize(self):
216 painter = QtGui.QPainter(self._canvas)
217 text = self._filing.center().action().text()
218 fontMetrics = painter.fontMetrics()
220 textBoundingRect = fontMetrics.boundingRect(text)
222 textBoundingRect = QtCore.QRect()
223 textWidth = textBoundingRect.width()
224 textHeight = textBoundingRect.height()
227 textWidth + self.ICON_SIZE_DEFAULT,
228 max(textHeight, self.ICON_SIZE_DEFAULT),
231 def show(self, palette):
232 self.palette = palette
235 self._cachedOuterRadius != self._filing.outerRadius() or
236 self._cachedInnerRadius != self._filing.innerRadius()
238 self._cachedOuterRadius = self._filing.outerRadius()
239 self._cachedInnerRadius = self._filing.innerRadius()
240 self._canvas = self._canvas.scaled(self.pieSize())
242 if self._mask is None:
243 self._mask = QtGui.QBitmap(self._canvas.size())
244 self._mask.fill(QtCore.Qt.color0)
245 self._generate_mask(self._mask)
246 self._canvas.setMask(self._mask)
252 def paint(self, selectionIndex):
253 painter = QtGui.QPainter(self._canvas)
254 painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
256 adjustmentRect = self._canvas.rect().adjusted(0, 0, -1, -1)
258 numChildren = len(self._filing)
260 self._paint_center_background(painter, adjustmentRect, selectionIndex)
261 self._paint_center_foreground(painter, selectionIndex)
264 for i in xrange(len(self._filing)):
265 self._paint_slice_background(painter, adjustmentRect, i, selectionIndex)
267 self._paint_center_background(painter, adjustmentRect, selectionIndex)
268 self._paint_center_foreground(painter, selectionIndex)
270 for i in xrange(len(self._filing)):
271 self._paint_slice_foreground(painter, i, selectionIndex)
275 def _generate_mask(self, mask):
277 Specifies on the mask the shape of the pie menu
279 painter = QtGui.QPainter(mask)
280 painter.setPen(QtCore.Qt.color1)
281 painter.setBrush(QtCore.Qt.color1)
282 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
283 painter.drawRect(mask.rect())
284 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
285 painter.drawEllipse(mask.rect().adjusted(0, 0, -1, -1))
287 raise NotImplementedError(self.DEFAULT_SHAPE)
289 def _paint_slice_background(self, painter, adjustmentRect, i, selectionIndex):
290 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
291 currentWidth = adjustmentRect.width()
292 newWidth = math.sqrt(2) * currentWidth
293 dx = (newWidth - currentWidth) / 2
294 adjustmentRect = adjustmentRect.adjusted(-dx, -dx, dx, dx)
295 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
298 raise NotImplementedError(self.DEFAULT_SHAPE)
300 if i == selectionIndex and self._filing[i].isEnabled():
301 painter.setBrush(self.palette.highlight())
302 painter.setPen(self.palette.highlight().color())
304 painter.setBrush(self.palette.window())
305 painter.setPen(self.palette.window().color())
307 a = self._filing._index_to_angle(i, True)
308 b = self._filing._index_to_angle(i + 1, True)
315 startAngleInDeg = (a * 360 * 16) / _TWOPI
316 sizeInDeg = (size * 360 * 16) / _TWOPI
317 painter.drawPie(adjustmentRect, int(startAngleInDeg), int(sizeInDeg))
319 def _paint_slice_foreground(self, painter, i, selectionIndex):
320 child = self._filing[i]
322 a = self._filing._index_to_angle(i, True)
323 b = self._filing._index_to_angle(i + 1, True)
326 middleAngle = (a + b) / 2
327 averageRadius = (self._cachedInnerRadius + self._cachedOuterRadius) / 2
329 sliceX = averageRadius * math.cos(middleAngle)
330 sliceY = - averageRadius * math.sin(middleAngle)
332 piePos = self._canvas.rect().center()
336 painter, child.action(), i == selectionIndex, pieX+sliceX, pieY+sliceY
339 def _paint_label(self, painter, action, isSelected, x, y):
341 fontMetrics = painter.fontMetrics()
343 textBoundingRect = fontMetrics.boundingRect(text)
345 textBoundingRect = QtCore.QRect()
346 textWidth = textBoundingRect.width()
347 textHeight = textBoundingRect.height()
349 icon = action.icon().pixmap(
350 QtCore.QSize(self.ICON_SIZE_DEFAULT, self.ICON_SIZE_DEFAULT),
354 iconWidth = icon.width()
355 iconHeight = icon.width()
356 averageWidth = (iconWidth + textWidth)/2
357 if not icon.isNull():
358 iconRect = QtCore.QRect(
365 painter.drawPixmap(iconRect, icon)
369 if action.isEnabled():
370 pen = self.palette.highlightedText()
371 brush = self.palette.highlight()
373 pen = self.palette.mid()
374 brush = self.palette.window()
376 if action.isEnabled():
377 pen = self.palette.windowText()
379 pen = self.palette.mid()
380 brush = self.palette.window()
382 leftX = x - averageWidth + iconWidth
383 topY = y + textHeight/2
384 painter.setPen(pen.color())
385 painter.setBrush(brush)
386 painter.drawText(leftX, topY, text)
388 def _paint_center_background(self, painter, adjustmentRect, selectionIndex):
389 if len(self._filing) == 0:
390 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
391 painter.setBrush(self.palette.highlight())
393 painter.setBrush(self.palette.window())
394 painter.setPen(self.palette.mid().color())
396 painter.drawRect(self._canvas.rect())
398 dark = self.palette.mid().color()
399 light = self.palette.light().color()
400 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
401 background = self.palette.highlight().color()
403 background = self.palette.window().color()
405 innerRadius = self._cachedInnerRadius
406 adjustmentCenterPos = adjustmentRect.center()
407 innerRect = QtCore.QRect(
408 adjustmentCenterPos.x() - innerRadius,
409 adjustmentCenterPos.y() - innerRadius,
414 painter.setPen(QtCore.Qt.NoPen)
415 painter.setBrush(background)
416 painter.drawPie(innerRect, 0, 360 * 16)
418 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
420 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
421 painter.setPen(QtGui.QPen(dark, 1))
422 painter.setBrush(QtCore.Qt.NoBrush)
423 painter.drawEllipse(adjustmentRect)
425 raise NotImplementedError(self.DEFAULT_SHAPE)
427 def _paint_center_foreground(self, painter, selectionIndex):
428 centerPos = self._canvas.rect().center()
437 self._filing.center().action(),
438 selectionIndex == PieFiling.SELECTION_CENTER,
443 class QPieDisplay(QtGui.QWidget):
445 def __init__(self, filing, parent = None, flags = QtCore.Qt.Window):
446 QtGui.QWidget.__init__(self, parent, flags)
447 self._filing = filing
448 self._artist = PieArtist(self._filing)
449 self._selectionIndex = PieFiling.SELECTION_NONE
451 def popup(self, pos):
452 self._update_selection(pos)
456 return self._artist.pieSize()
458 @misc_utils.log_exception(_moduleLogger)
459 def showEvent(self, showEvent):
460 mask = self._artist.show(self.palette())
463 QtGui.QWidget.showEvent(self, showEvent)
465 @misc_utils.log_exception(_moduleLogger)
466 def hideEvent(self, hideEvent):
468 self._selectionIndex = PieFiling.SELECTION_NONE
469 QtGui.QWidget.hideEvent(self, hideEvent)
471 @misc_utils.log_exception(_moduleLogger)
472 def paintEvent(self, paintEvent):
473 canvas = self._artist.paint(self._selectionIndex)
474 offset = (self.size() - canvas.size()) / 2
476 screen = QtGui.QPainter(self)
477 screen.drawPixmap(QtCore.QPoint(offset.width(), offset.height()), canvas)
479 QtGui.QWidget.paintEvent(self, paintEvent)
481 def selectAt(self, index):
482 oldIndex = self._selectionIndex
483 self._selectionIndex = index
488 class QPieButton(QtGui.QWidget):
490 activated = QtCore.pyqtSignal(int)
491 highlighted = QtCore.pyqtSignal(int)
492 canceled = QtCore.pyqtSignal()
493 aboutToShow = QtCore.pyqtSignal()
494 aboutToHide = QtCore.pyqtSignal()
499 def __init__(self, buttonSlice, parent = None, buttonSlices = None):
500 # @bug Artifacts on Maemo 5 due to window 3D effects, find way to disable them for just these?
501 # @bug The pie's are being pushed back on screen on Maemo, leading to coordinate issues
502 QtGui.QWidget.__init__(self, parent)
503 self._cachedCenterPosition = self.rect().center()
505 self._filing = PieFiling()
506 self._display = QPieDisplay(self._filing, None, QtCore.Qt.SplashScreen)
507 self._selectionIndex = PieFiling.SELECTION_NONE
509 self._buttonFiling = PieFiling()
510 self._buttonFiling.set_center(buttonSlice)
511 if buttonSlices is not None:
512 for slice in buttonSlices:
513 self._buttonFiling.insertItem(slice)
514 self._buttonFiling.setOuterRadius(self.BUTTON_RADIUS)
515 self._buttonArtist = PieArtist(self._buttonFiling)
516 self._poppedUp = False
518 self._delayPopupTimer = QtCore.QTimer()
519 self._delayPopupTimer.setInterval(self.DELAY)
520 self._delayPopupTimer.setSingleShot(True)
521 self._delayPopupTimer.timeout.connect(self._on_delayed_popup)
522 self._popupLocation = None
524 self._mousePosition = None
525 self.setFocusPolicy(QtCore.Qt.StrongFocus)
528 QtGui.QSizePolicy.MinimumExpanding,
529 QtGui.QSizePolicy.MinimumExpanding,
533 def insertItem(self, item, index = -1):
534 self._filing.insertItem(item, index)
536 def removeItemAt(self, index):
537 self._filing.removeItemAt(index)
539 def set_center(self, item):
540 self._filing.set_center(item)
542 def set_button(self, item):
548 def itemAt(self, index):
549 return self._filing.itemAt(index)
551 def indexAt(self, point):
552 return self._filing.indexAt(self._cachedCenterPosition, point)
554 def innerRadius(self):
555 return self._filing.innerRadius()
557 def setInnerRadius(self, radius):
558 self._filing.setInnerRadius(radius)
560 def outerRadius(self):
561 return self._filing.outerRadius()
563 def setOuterRadius(self, radius):
564 self._filing.setOuterRadius(radius)
566 def buttonRadius(self):
567 return self._buttonFiling.outerRadius()
569 def setButtonRadius(self, radius):
570 self._buttonFiling.setOuterRadius(radius)
571 self._buttonFiling.setInnerRadius(radius / 2)
572 self._buttonArtist.show(self.palette())
574 def minimumSizeHint(self):
575 return self._buttonArtist.centerSize()
577 @misc_utils.log_exception(_moduleLogger)
578 def mousePressEvent(self, mouseEvent):
579 lastSelection = self._selectionIndex
581 lastMousePos = mouseEvent.pos()
582 self._mousePosition = lastMousePos
583 self._update_selection(self._cachedCenterPosition)
585 self.highlighted.emit(self._selectionIndex)
587 self._display.selectAt(self._selectionIndex)
588 self._popupLocation = mouseEvent.globalPos()
589 self._delayPopupTimer.start()
591 @misc_utils.log_exception(_moduleLogger)
592 def _on_delayed_popup(self):
593 assert self._popupLocation is not None, "Widget location abuse"
594 self._popup_child(self._popupLocation)
596 @misc_utils.log_exception(_moduleLogger)
597 def mouseMoveEvent(self, mouseEvent):
598 lastSelection = self._selectionIndex
600 lastMousePos = mouseEvent.pos()
601 if self._mousePosition is None:
603 self._update_selection(lastMousePos)
606 self._update_selection(
607 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
611 if lastSelection != self._selectionIndex:
612 self.highlighted.emit(self._selectionIndex)
613 self._display.selectAt(self._selectionIndex)
615 if self._selectionIndex != PieFiling.SELECTION_CENTER and self._delayPopupTimer.isActive():
616 self._on_delayed_popup()
618 @misc_utils.log_exception(_moduleLogger)
619 def mouseReleaseEvent(self, mouseEvent):
620 self._delayPopupTimer.stop()
621 self._popupLocation = None
623 lastSelection = self._selectionIndex
625 lastMousePos = mouseEvent.pos()
626 if self._mousePosition is None:
628 self._update_selection(lastMousePos)
631 self._update_selection(
632 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
635 self._mousePosition = None
637 self._activate_at(self._selectionIndex)
640 @misc_utils.log_exception(_moduleLogger)
641 def keyPressEvent(self, keyEvent):
642 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
643 self._popup_child(QtGui.QCursor.pos())
644 if self._selectionIndex != len(self._filing) - 1:
645 nextSelection = self._selectionIndex + 1
648 self._select_at(nextSelection)
649 self._display.selectAt(self._selectionIndex)
650 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
651 self._popup_child(QtGui.QCursor.pos())
652 if 0 < self._selectionIndex:
653 nextSelection = self._selectionIndex - 1
655 nextSelection = len(self._filing) - 1
656 self._select_at(nextSelection)
657 self._display.selectAt(self._selectionIndex)
658 elif keyEvent.key() in [QtCore.Qt.Key_Space]:
659 self._popup_child(QtGui.QCursor.pos())
660 self._select_at(PieFiling.SELECTION_CENTER)
661 self._display.selectAt(self._selectionIndex)
662 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
663 self._delayPopupTimer.stop()
664 self._popupLocation = None
665 self._activate_at(self._selectionIndex)
667 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
668 self._delayPopupTimer.stop()
669 self._popupLocation = None
670 self._activate_at(PieFiling.SELECTION_NONE)
673 QtGui.QWidget.keyPressEvent(self, keyEvent)
675 @misc_utils.log_exception(_moduleLogger)
676 def resizeEvent(self, resizeEvent):
677 self.setButtonRadius(min(resizeEvent.size().width(), resizeEvent.size().height()) / 2 - 1)
678 QtGui.QWidget.resizeEvent(self, resizeEvent)
680 @misc_utils.log_exception(_moduleLogger)
681 def showEvent(self, showEvent):
682 self._buttonArtist.show(self.palette())
683 self._cachedCenterPosition = self.rect().center()
685 QtGui.QWidget.showEvent(self, showEvent)
687 @misc_utils.log_exception(_moduleLogger)
688 def hideEvent(self, hideEvent):
690 self._select_at(PieFiling.SELECTION_NONE)
691 QtGui.QWidget.hideEvent(self, hideEvent)
693 @misc_utils.log_exception(_moduleLogger)
694 def paintEvent(self, paintEvent):
695 self.setButtonRadius(min(self.rect().width(), self.rect().height()) / 2 - 1)
697 canvas = self._buttonArtist.paint(PieFiling.SELECTION_CENTER)
699 canvas = self._buttonArtist.paint(PieFiling.SELECTION_NONE)
700 offset = (self.size() - canvas.size()) / 2
702 screen = QtGui.QPainter(self)
703 screen.drawPixmap(QtCore.QPoint(offset.width(), offset.height()), canvas)
705 QtGui.QWidget.paintEvent(self, paintEvent)
708 return iter(self._filing)
711 return len(self._filing)
713 def _popup_child(self, position):
714 self._poppedUp = True
715 self.aboutToShow.emit()
717 self._delayPopupTimer.stop()
718 self._popupLocation = None
720 position = position - QtCore.QPoint(self._filing.outerRadius(), self._filing.outerRadius())
721 self._display.move(position)
726 def _hide_child(self):
727 self._poppedUp = False
728 self.aboutToHide.emit()
732 def _select_at(self, index):
733 self._selectionIndex = index
735 def _update_selection(self, lastMousePos, ignoreOuter = False):
736 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
737 if radius < self._filing.innerRadius():
738 self._select_at(PieFiling.SELECTION_CENTER)
739 elif radius <= self._filing.outerRadius() or ignoreOuter:
740 self._select_at(self.indexAt(lastMousePos))
742 self._select_at(PieFiling.SELECTION_NONE)
744 def _activate_at(self, index):
745 if index == PieFiling.SELECTION_NONE:
748 elif index == PieFiling.SELECTION_CENTER:
749 child = self._filing.center()
751 child = self.itemAt(index)
753 if child.action().isEnabled():
754 child.action().trigger()
755 self.activated.emit(index)
760 class QPieMenu(QtGui.QWidget):
762 activated = QtCore.pyqtSignal(int)
763 highlighted = QtCore.pyqtSignal(int)
764 canceled = QtCore.pyqtSignal()
765 aboutToShow = QtCore.pyqtSignal()
766 aboutToHide = QtCore.pyqtSignal()
768 def __init__(self, parent = None):
769 QtGui.QWidget.__init__(self, parent)
770 self._cachedCenterPosition = self.rect().center()
772 self._filing = PieFiling()
773 self._artist = PieArtist(self._filing)
774 self._selectionIndex = PieFiling.SELECTION_NONE
776 self._mousePosition = ()
777 self.setFocusPolicy(QtCore.Qt.StrongFocus)
779 def popup(self, pos):
780 self._update_selection(pos)
783 def insertItem(self, item, index = -1):
784 self._filing.insertItem(item, index)
787 def removeItemAt(self, index):
788 self._filing.removeItemAt(index)
791 def set_center(self, item):
792 self._filing.set_center(item)
799 def itemAt(self, index):
800 return self._filing.itemAt(index)
802 def indexAt(self, point):
803 return self._filing.indexAt(self._cachedCenterPosition, point)
805 def innerRadius(self):
806 return self._filing.innerRadius()
808 def setInnerRadius(self, radius):
809 self._filing.setInnerRadius(radius)
812 def outerRadius(self):
813 return self._filing.outerRadius()
815 def setOuterRadius(self, radius):
816 self._filing.setOuterRadius(radius)
820 return self._artist.pieSize()
822 @misc_utils.log_exception(_moduleLogger)
823 def mousePressEvent(self, mouseEvent):
824 lastSelection = self._selectionIndex
826 lastMousePos = mouseEvent.pos()
827 self._update_selection(lastMousePos)
828 self._mousePosition = lastMousePos
830 if lastSelection != self._selectionIndex:
831 self.highlighted.emit(self._selectionIndex)
834 @misc_utils.log_exception(_moduleLogger)
835 def mouseMoveEvent(self, mouseEvent):
836 lastSelection = self._selectionIndex
838 lastMousePos = mouseEvent.pos()
839 self._update_selection(lastMousePos)
841 if lastSelection != self._selectionIndex:
842 self.highlighted.emit(self._selectionIndex)
845 @misc_utils.log_exception(_moduleLogger)
846 def mouseReleaseEvent(self, mouseEvent):
847 lastSelection = self._selectionIndex
849 lastMousePos = mouseEvent.pos()
850 self._update_selection(lastMousePos)
851 self._mousePosition = ()
853 self._activate_at(self._selectionIndex)
856 @misc_utils.log_exception(_moduleLogger)
857 def keyPressEvent(self, keyEvent):
858 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
859 if self._selectionIndex != len(self._filing) - 1:
860 nextSelection = self._selectionIndex + 1
863 self._select_at(nextSelection)
865 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
866 if 0 < self._selectionIndex:
867 nextSelection = self._selectionIndex - 1
869 nextSelection = len(self._filing) - 1
870 self._select_at(nextSelection)
872 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
873 self._activate_at(self._selectionIndex)
874 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
875 self._activate_at(PieFiling.SELECTION_NONE)
877 QtGui.QWidget.keyPressEvent(self, keyEvent)
879 @misc_utils.log_exception(_moduleLogger)
880 def showEvent(self, showEvent):
881 self.aboutToShow.emit()
882 self._cachedCenterPosition = self.rect().center()
884 mask = self._artist.show(self.palette())
887 lastMousePos = self.mapFromGlobal(QtGui.QCursor.pos())
888 self._update_selection(lastMousePos)
890 QtGui.QWidget.showEvent(self, showEvent)
892 @misc_utils.log_exception(_moduleLogger)
893 def hideEvent(self, hideEvent):
895 self._selectionIndex = PieFiling.SELECTION_NONE
896 QtGui.QWidget.hideEvent(self, hideEvent)
898 @misc_utils.log_exception(_moduleLogger)
899 def paintEvent(self, paintEvent):
900 canvas = self._artist.paint(self._selectionIndex)
902 screen = QtGui.QPainter(self)
903 screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
905 QtGui.QWidget.paintEvent(self, paintEvent)
908 return iter(self._filing)
911 return len(self._filing)
913 def _select_at(self, index):
914 self._selectionIndex = index
916 def _update_selection(self, lastMousePos):
917 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
918 if radius < self._filing.innerRadius():
919 self._selectionIndex = PieFiling.SELECTION_CENTER
920 elif radius <= self._filing.outerRadius():
921 self._select_at(self.indexAt(lastMousePos))
923 self._selectionIndex = PieFiling.SELECTION_NONE
925 def _activate_at(self, index):
926 if index == PieFiling.SELECTION_NONE:
928 self.aboutToHide.emit()
931 elif index == PieFiling.SELECTION_CENTER:
932 child = self._filing.center()
934 child = self.itemAt(index)
936 if child.isEnabled():
937 child.action().trigger()
938 self.activated.emit(index)
941 self.aboutToHide.emit()
946 PieFiling.NULL_CENTER.setEnabled(False)
953 def _on_about_to_hide(app):
957 if __name__ == "__main__":
958 app = QtGui.QApplication([])
966 singleAction = QtGui.QAction(None)
967 singleAction.setText("Boo")
968 singleItem = QActionPieItem(singleAction)
970 spie.insertItem(singleItem)
974 oneAction = QtGui.QAction(None)
975 oneAction.setText("Chew")
976 oneItem = QActionPieItem(oneAction)
977 twoAction = QtGui.QAction(None)
978 twoAction.setText("Foo")
979 twoItem = QActionPieItem(twoAction)
980 iconTextAction = QtGui.QAction(None)
981 iconTextAction.setText("Icon")
982 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
983 iconTextItem = QActionPieItem(iconTextAction)
985 mpie.insertItem(oneItem)
986 mpie.insertItem(twoItem)
987 mpie.insertItem(oneItem)
988 mpie.insertItem(iconTextItem)
992 oneAction = QtGui.QAction(None)
993 oneAction.setText("Chew")
994 oneAction.triggered.connect(lambda: _print("Chew"))
995 oneItem = QActionPieItem(oneAction)
996 twoAction = QtGui.QAction(None)
997 twoAction.setText("Foo")
998 twoAction.triggered.connect(lambda: _print("Foo"))
999 twoItem = QActionPieItem(twoAction)
1000 iconAction = QtGui.QAction(None)
1001 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1002 iconAction.triggered.connect(lambda: _print("Icon"))
1003 iconItem = QActionPieItem(iconAction)
1004 iconTextAction = QtGui.QAction(None)
1005 iconTextAction.setText("Icon")
1006 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1007 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1008 iconTextItem = QActionPieItem(iconTextAction)
1010 mpie.set_center(iconItem)
1011 mpie.insertItem(oneItem)
1012 mpie.insertItem(twoItem)
1013 mpie.insertItem(oneItem)
1014 mpie.insertItem(iconTextItem)
1016 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1017 mpie.canceled.connect(lambda: _print("Canceled"))
1020 oneAction = QtGui.QAction(None)
1021 oneAction.setText("Chew")
1022 oneAction.triggered.connect(lambda: _print("Chew"))
1023 oneItem = QActionPieItem(oneAction)
1024 twoAction = QtGui.QAction(None)
1025 twoAction.setText("Foo")
1026 twoAction.triggered.connect(lambda: _print("Foo"))
1027 twoItem = QActionPieItem(twoAction)
1028 iconAction = QtGui.QAction(None)
1029 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1030 iconAction.triggered.connect(lambda: _print("Icon"))
1031 iconItem = QActionPieItem(iconAction)
1032 iconTextAction = QtGui.QAction(None)
1033 iconTextAction.setText("Icon")
1034 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1035 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1036 iconTextItem = QActionPieItem(iconTextAction)
1037 pieFiling = PieFiling()
1038 pieFiling.set_center(iconItem)
1039 pieFiling.insertItem(oneItem)
1040 pieFiling.insertItem(twoItem)
1041 pieFiling.insertItem(oneItem)
1042 pieFiling.insertItem(iconTextItem)
1043 mpie = QPieDisplay(pieFiling)
1047 oneAction = QtGui.QAction(None)
1048 oneAction.setText("Chew")
1049 oneAction.triggered.connect(lambda: _print("Chew"))
1050 oneItem = QActionPieItem(oneAction)
1051 twoAction = QtGui.QAction(None)
1052 twoAction.setText("Foo")
1053 twoAction.triggered.connect(lambda: _print("Foo"))
1054 twoItem = QActionPieItem(twoAction)
1055 iconAction = QtGui.QAction(None)
1056 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1057 iconAction.triggered.connect(lambda: _print("Icon"))
1058 iconItem = QActionPieItem(iconAction)
1059 iconTextAction = QtGui.QAction(None)
1060 iconTextAction.setText("Icon")
1061 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1062 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1063 iconTextItem = QActionPieItem(iconTextAction)
1064 mpie = QPieButton(iconItem)
1065 mpie.set_center(iconItem)
1066 mpie.insertItem(oneItem)
1067 mpie.insertItem(twoItem)
1068 mpie.insertItem(oneItem)
1069 mpie.insertItem(iconTextItem)
1071 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1072 mpie.canceled.connect(lambda: _print("Canceled"))