7 QtCore = qt_compat.QtCore
8 QtGui = qt_compat.import_module("QtGui")
10 import misc as misc_utils
13 _moduleLogger = logging.getLogger(__name__)
19 def _radius_at(center, pos):
24 radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
28 def _angle_at(center, pos):
33 radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
34 angle = math.acos(xDelta / radius)
36 angle = _TWOPI - angle
41 class QActionPieItem(object):
43 def __init__(self, action, weight = 1):
50 def setWeight(self, weight):
56 def setEnabled(self, enabled = True):
57 self._action.setEnabled(enabled)
60 return self._action.isEnabled()
63 class PieFiling(object):
65 INNER_RADIUS_DEFAULT = 64
66 OUTER_RADIUS_DEFAULT = 192
71 NULL_CENTER = QActionPieItem(QtGui.QAction(None))
74 self._innerRadius = self.INNER_RADIUS_DEFAULT
75 self._outerRadius = self.OUTER_RADIUS_DEFAULT
77 self._center = self.NULL_CENTER
79 self._cacheIndexToAngle = {}
80 self._cacheTotalWeight = 0
82 def insertItem(self, item, index = -1):
83 self._children.insert(index, item)
84 self._invalidate_cache()
86 def removeItemAt(self, index):
87 item = self._children.pop(index)
88 self._invalidate_cache()
90 def set_center(self, item):
92 item = self.NULL_CENTER
100 self._center = self.NULL_CENTER
101 self._invalidate_cache()
103 def itemAt(self, index):
104 return self._children[index]
106 def indexAt(self, center, point):
107 return self._angle_to_index(_angle_at(center, point))
109 def innerRadius(self):
110 return self._innerRadius
112 def setInnerRadius(self, radius):
113 self._innerRadius = radius
115 def outerRadius(self):
116 return self._outerRadius
118 def setOuterRadius(self, radius):
119 self._outerRadius = radius
122 return iter(self._children)
125 return len(self._children)
127 def __getitem__(self, index):
128 return self._children[index]
130 def _invalidate_cache(self):
131 self._cacheIndexToAngle.clear()
132 self._cacheTotalWeight = sum(child.weight() for child in self._children)
133 if self._cacheTotalWeight == 0:
134 self._cacheTotalWeight = 1
136 def _index_to_angle(self, index, isShifted):
137 key = index, isShifted
138 if key in self._cacheIndexToAngle:
139 return self._cacheIndexToAngle[key]
140 index = index % len(self._children)
142 baseAngle = _TWOPI / self._cacheTotalWeight
147 angle -= (self._children[0].weight() * baseAngle) / 2
149 angle -= baseAngle / 2
153 for i, child in enumerate(self._children):
156 angle += child.weight() * baseAngle
157 while _TWOPI < angle:
160 self._cacheIndexToAngle[key] = angle
163 def _angle_to_index(self, angle):
164 numChildren = len(self._children)
166 return self.SELECTION_CENTER
168 baseAngle = _TWOPI / self._cacheTotalWeight
170 iterAngle = math.pi / 2 - (self.itemAt(0).weight() * baseAngle) / 2
174 oldIterAngle = iterAngle
175 for index, child in enumerate(self._children):
176 iterAngle += child.weight() * baseAngle
177 if oldIterAngle < angle and angle <= iterAngle:
178 return index - 1 if index != 0 else numChildren - 1
179 elif oldIterAngle < (angle + _TWOPI) and (angle + _TWOPI <= iterAngle):
180 return index - 1 if index != 0 else numChildren - 1
181 oldIterAngle = iterAngle
184 class PieArtist(object):
186 ICON_SIZE_DEFAULT = 48
188 SHAPE_CIRCLE = "circle"
189 SHAPE_SQUARE = "square"
190 DEFAULT_SHAPE = SHAPE_SQUARE
192 BACKGROUND_FILL = "fill"
193 BACKGROUND_NOFILL = "no fill"
195 def __init__(self, filing, background = BACKGROUND_FILL):
196 self._filing = filing
198 self._cachedOuterRadius = self._filing.outerRadius()
199 self._cachedInnerRadius = self._filing.innerRadius()
200 canvasSize = self._cachedOuterRadius * 2 + 1
201 self._canvas = QtGui.QPixmap(canvasSize, canvasSize)
203 self._backgroundState = background
207 diameter = self._filing.outerRadius() * 2 + 1
208 return QtCore.QSize(diameter, diameter)
210 def centerSize(self):
211 painter = QtGui.QPainter(self._canvas)
212 text = self._filing.center().action().text()
213 fontMetrics = painter.fontMetrics()
215 textBoundingRect = fontMetrics.boundingRect(text)
217 textBoundingRect = QtCore.QRect()
218 textWidth = textBoundingRect.width()
219 textHeight = textBoundingRect.height()
222 textWidth + self.ICON_SIZE_DEFAULT,
223 max(textHeight, self.ICON_SIZE_DEFAULT),
226 def show(self, palette):
227 self.palette = palette
230 self._cachedOuterRadius != self._filing.outerRadius() or
231 self._cachedInnerRadius != self._filing.innerRadius()
233 self._cachedOuterRadius = self._filing.outerRadius()
234 self._cachedInnerRadius = self._filing.innerRadius()
235 self._canvas = self._canvas.scaled(self.pieSize())
237 if self._mask is None:
238 self._mask = QtGui.QBitmap(self._canvas.size())
239 self._mask.fill(QtCore.Qt.color0)
240 self._generate_mask(self._mask)
241 self._canvas.setMask(self._mask)
247 def paint(self, selectionIndex):
248 painter = QtGui.QPainter(self._canvas)
249 painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
251 self.paintPainter(selectionIndex, painter)
255 def paintPainter(self, selectionIndex, painter):
256 adjustmentRect = painter.viewport().adjusted(0, 0, -1, -1)
258 numChildren = len(self._filing)
260 self._paint_center_background(painter, adjustmentRect, selectionIndex)
261 self._paint_center_foreground(painter, adjustmentRect, 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, adjustmentRect, selectionIndex)
270 for i in xrange(len(self._filing)):
271 self._paint_slice_foreground(painter, adjustmentRect, i, selectionIndex)
273 def _generate_mask(self, mask):
275 Specifies on the mask the shape of the pie menu
277 painter = QtGui.QPainter(mask)
278 painter.setPen(QtCore.Qt.color1)
279 painter.setBrush(QtCore.Qt.color1)
280 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
281 painter.drawRect(mask.rect())
282 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
283 painter.drawEllipse(mask.rect().adjusted(0, 0, -1, -1))
285 raise NotImplementedError(self.DEFAULT_SHAPE)
287 def _paint_slice_background(self, painter, adjustmentRect, i, selectionIndex):
288 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
289 currentWidth = adjustmentRect.width()
290 newWidth = math.sqrt(2) * currentWidth
291 dx = (newWidth - currentWidth) / 2
292 adjustmentRect = adjustmentRect.adjusted(-dx, -dx, dx, dx)
293 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
296 raise NotImplementedError(self.DEFAULT_SHAPE)
298 if self._backgroundState == self.BACKGROUND_NOFILL:
299 painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
300 painter.setPen(self.palette.highlight().color())
302 if i == selectionIndex and self._filing[i].isEnabled():
303 painter.setBrush(self.palette.highlight())
304 painter.setPen(self.palette.highlight().color())
306 painter.setBrush(self.palette.window())
307 painter.setPen(self.palette.window().color())
309 a = self._filing._index_to_angle(i, True)
310 b = self._filing._index_to_angle(i + 1, True)
317 startAngleInDeg = (a * 360 * 16) / _TWOPI
318 sizeInDeg = (size * 360 * 16) / _TWOPI
319 painter.drawPie(adjustmentRect, int(startAngleInDeg), int(sizeInDeg))
321 def _paint_slice_foreground(self, painter, adjustmentRect, i, selectionIndex):
322 child = self._filing[i]
324 a = self._filing._index_to_angle(i, True)
325 b = self._filing._index_to_angle(i + 1, True)
328 middleAngle = (a + b) / 2
329 averageRadius = (self._cachedInnerRadius + self._cachedOuterRadius) / 2
331 sliceX = averageRadius * math.cos(middleAngle)
332 sliceY = - averageRadius * math.sin(middleAngle)
334 piePos = adjustmentRect.center()
338 painter, child.action(), i == selectionIndex, pieX+sliceX, pieY+sliceY
341 def _paint_label(self, painter, action, isSelected, x, y):
343 fontMetrics = painter.fontMetrics()
345 textBoundingRect = fontMetrics.boundingRect(text)
347 textBoundingRect = QtCore.QRect()
348 textWidth = textBoundingRect.width()
349 textHeight = textBoundingRect.height()
351 icon = action.icon().pixmap(
352 QtCore.QSize(self.ICON_SIZE_DEFAULT, self.ICON_SIZE_DEFAULT),
356 iconWidth = icon.width()
357 iconHeight = icon.width()
358 averageWidth = (iconWidth + textWidth)/2
359 if not icon.isNull():
360 iconRect = QtCore.QRect(
367 painter.drawPixmap(iconRect, icon)
371 if action.isEnabled():
372 pen = self.palette.highlightedText()
373 brush = self.palette.highlight()
375 pen = self.palette.mid()
376 brush = self.palette.window()
378 if action.isEnabled():
379 pen = self.palette.windowText()
381 pen = self.palette.mid()
382 brush = self.palette.window()
384 leftX = x - averageWidth + iconWidth
385 topY = y + textHeight/2
386 painter.setPen(pen.color())
387 painter.setBrush(brush)
388 painter.drawText(leftX, topY, text)
390 def _paint_center_background(self, painter, adjustmentRect, selectionIndex):
391 if self._backgroundState == self.BACKGROUND_NOFILL:
393 if len(self._filing) == 0:
394 if self._backgroundState == self.BACKGROUND_NOFILL:
395 painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
397 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
398 painter.setBrush(self.palette.highlight())
400 painter.setBrush(self.palette.window())
401 painter.setPen(self.palette.mid().color())
403 painter.drawRect(adjustmentRect)
405 dark = self.palette.mid().color()
406 light = self.palette.light().color()
407 if self._backgroundState == self.BACKGROUND_NOFILL:
408 background = QtGui.QBrush(QtCore.Qt.transparent)
410 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
411 background = self.palette.highlight().color()
413 background = self.palette.window().color()
415 innerRadius = self._cachedInnerRadius
416 adjustmentCenterPos = adjustmentRect.center()
417 innerRect = QtCore.QRect(
418 adjustmentCenterPos.x() - innerRadius,
419 adjustmentCenterPos.y() - innerRadius,
424 painter.setPen(QtCore.Qt.NoPen)
425 painter.setBrush(background)
426 painter.drawPie(innerRect, 0, 360 * 16)
428 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
430 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
431 painter.setPen(QtGui.QPen(dark, 1))
432 painter.setBrush(QtCore.Qt.NoBrush)
433 painter.drawEllipse(adjustmentRect)
435 raise NotImplementedError(self.DEFAULT_SHAPE)
437 def _paint_center_foreground(self, painter, adjustmentRect, selectionIndex):
438 centerPos = adjustmentRect.center()
447 self._filing.center().action(),
448 selectionIndex == PieFiling.SELECTION_CENTER,
453 class QPieDisplay(QtGui.QWidget):
455 def __init__(self, filing, parent = None, flags = QtCore.Qt.Window):
456 QtGui.QWidget.__init__(self, parent, flags)
457 self._filing = filing
458 self._artist = PieArtist(self._filing)
459 self._selectionIndex = PieFiling.SELECTION_NONE
461 def popup(self, pos):
462 self._update_selection(pos)
466 return self._artist.pieSize()
468 @misc_utils.log_exception(_moduleLogger)
469 def showEvent(self, showEvent):
470 mask = self._artist.show(self.palette())
473 QtGui.QWidget.showEvent(self, showEvent)
475 @misc_utils.log_exception(_moduleLogger)
476 def hideEvent(self, hideEvent):
478 self._selectionIndex = PieFiling.SELECTION_NONE
479 QtGui.QWidget.hideEvent(self, hideEvent)
481 @misc_utils.log_exception(_moduleLogger)
482 def paintEvent(self, paintEvent):
483 canvas = self._artist.paint(self._selectionIndex)
484 offset = (self.size() - canvas.size()) / 2
486 screen = QtGui.QPainter(self)
487 screen.drawPixmap(QtCore.QPoint(offset.width(), offset.height()), canvas)
489 QtGui.QWidget.paintEvent(self, paintEvent)
491 def selectAt(self, index):
492 oldIndex = self._selectionIndex
493 self._selectionIndex = index
498 class QPieButton(QtGui.QWidget):
500 activated = qt_compat.Signal(int)
501 highlighted = qt_compat.Signal(int)
502 canceled = qt_compat.Signal()
503 aboutToShow = qt_compat.Signal()
504 aboutToHide = qt_compat.Signal()
509 def __init__(self, buttonSlice, parent = None, buttonSlices = None):
510 # @bug Artifacts on Maemo 5 due to window 3D effects, find way to disable them for just these?
511 # @bug The pie's are being pushed back on screen on Maemo, leading to coordinate issues
512 QtGui.QWidget.__init__(self, parent)
513 self._cachedCenterPosition = self.rect().center()
515 self._filing = PieFiling()
516 self._display = QPieDisplay(self._filing, None, QtCore.Qt.SplashScreen)
517 self._selectionIndex = PieFiling.SELECTION_NONE
519 self._buttonFiling = PieFiling()
520 self._buttonFiling.set_center(buttonSlice)
521 if buttonSlices is not None:
522 for slice in buttonSlices:
523 self._buttonFiling.insertItem(slice)
524 self._buttonFiling.setOuterRadius(self.BUTTON_RADIUS)
525 self._buttonArtist = PieArtist(self._buttonFiling, PieArtist.BACKGROUND_NOFILL)
526 self._poppedUp = False
527 self._pressed = False
529 self._delayPopupTimer = QtCore.QTimer()
530 self._delayPopupTimer.setInterval(self.DELAY)
531 self._delayPopupTimer.setSingleShot(True)
532 self._delayPopupTimer.timeout.connect(self._on_delayed_popup)
533 self._popupLocation = None
535 self._mousePosition = None
536 self.setFocusPolicy(QtCore.Qt.StrongFocus)
539 QtGui.QSizePolicy.MinimumExpanding,
540 QtGui.QSizePolicy.MinimumExpanding,
544 def insertItem(self, item, index = -1):
545 self._filing.insertItem(item, index)
547 def removeItemAt(self, index):
548 self._filing.removeItemAt(index)
550 def set_center(self, item):
551 self._filing.set_center(item)
553 def set_button(self, item):
559 def itemAt(self, index):
560 return self._filing.itemAt(index)
562 def indexAt(self, point):
563 return self._filing.indexAt(self._cachedCenterPosition, point)
565 def innerRadius(self):
566 return self._filing.innerRadius()
568 def setInnerRadius(self, radius):
569 self._filing.setInnerRadius(radius)
571 def outerRadius(self):
572 return self._filing.outerRadius()
574 def setOuterRadius(self, radius):
575 self._filing.setOuterRadius(radius)
577 def buttonRadius(self):
578 return self._buttonFiling.outerRadius()
580 def setButtonRadius(self, radius):
581 self._buttonFiling.setOuterRadius(radius)
582 self._buttonFiling.setInnerRadius(radius / 2)
583 self._buttonArtist.show(self.palette())
585 def minimumSizeHint(self):
586 return self._buttonArtist.centerSize()
588 @misc_utils.log_exception(_moduleLogger)
589 def mousePressEvent(self, mouseEvent):
590 lastSelection = self._selectionIndex
592 lastMousePos = mouseEvent.pos()
593 self._mousePosition = lastMousePos
594 self._update_selection(self._cachedCenterPosition)
596 self.highlighted.emit(self._selectionIndex)
598 self._display.selectAt(self._selectionIndex)
601 self._popupLocation = mouseEvent.globalPos()
602 self._delayPopupTimer.start()
604 @misc_utils.log_exception(_moduleLogger)
605 def _on_delayed_popup(self):
606 self.__on_delayed_popup()
608 def __on_delayed_popup(self):
609 assert self._popupLocation is not None, "Widget location abuse"
610 self._popup_child(self._popupLocation)
612 @misc_utils.log_exception(_moduleLogger)
613 def mouseMoveEvent(self, mouseEvent):
614 lastSelection = self._selectionIndex
616 lastMousePos = mouseEvent.pos()
617 if self._mousePosition is None:
619 self._update_selection(lastMousePos)
622 self._update_selection(
623 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
627 if lastSelection != self._selectionIndex:
628 self.highlighted.emit(self._selectionIndex)
629 self._display.selectAt(self._selectionIndex)
631 if self._selectionIndex != PieFiling.SELECTION_CENTER and self._delayPopupTimer.isActive():
632 self._on_delayed_popup()
634 @misc_utils.log_exception(_moduleLogger)
635 def mouseReleaseEvent(self, mouseEvent):
636 self._delayPopupTimer.stop()
637 self._popupLocation = None
639 lastSelection = self._selectionIndex
641 lastMousePos = mouseEvent.pos()
642 if self._mousePosition is None:
644 self._update_selection(lastMousePos)
647 self._update_selection(
648 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
651 self._mousePosition = None
653 self._activate_at(self._selectionIndex)
654 self._pressed = False
658 @misc_utils.log_exception(_moduleLogger)
659 def keyPressEvent(self, keyEvent):
660 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
661 self._popup_child(QtGui.QCursor.pos())
662 if self._selectionIndex != len(self._filing) - 1:
663 nextSelection = self._selectionIndex + 1
666 self._select_at(nextSelection)
667 self._display.selectAt(self._selectionIndex)
668 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
669 self._popup_child(QtGui.QCursor.pos())
670 if 0 < self._selectionIndex:
671 nextSelection = self._selectionIndex - 1
673 nextSelection = len(self._filing) - 1
674 self._select_at(nextSelection)
675 self._display.selectAt(self._selectionIndex)
676 elif keyEvent.key() in [QtCore.Qt.Key_Space]:
677 self._popup_child(QtGui.QCursor.pos())
678 self._select_at(PieFiling.SELECTION_CENTER)
679 self._display.selectAt(self._selectionIndex)
680 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
681 self._delayPopupTimer.stop()
682 self._popupLocation = None
683 self._activate_at(self._selectionIndex)
685 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
686 self._delayPopupTimer.stop()
687 self._popupLocation = None
688 self._activate_at(PieFiling.SELECTION_NONE)
691 QtGui.QWidget.keyPressEvent(self, keyEvent)
693 @misc_utils.log_exception(_moduleLogger)
694 def resizeEvent(self, resizeEvent):
695 self.setButtonRadius(min(resizeEvent.size().width(), resizeEvent.size().height()) / 2 - 1)
696 QtGui.QWidget.resizeEvent(self, resizeEvent)
698 @misc_utils.log_exception(_moduleLogger)
699 def showEvent(self, showEvent):
700 self._buttonArtist.show(self.palette())
701 self._cachedCenterPosition = self.rect().center()
703 QtGui.QWidget.showEvent(self, showEvent)
705 @misc_utils.log_exception(_moduleLogger)
706 def hideEvent(self, hideEvent):
708 self._select_at(PieFiling.SELECTION_NONE)
709 QtGui.QWidget.hideEvent(self, hideEvent)
711 @misc_utils.log_exception(_moduleLogger)
712 def paintEvent(self, paintEvent):
713 self.setButtonRadius(min(self.rect().width(), self.rect().height()) / 2 - 1)
715 selectionIndex = PieFiling.SELECTION_CENTER
717 selectionIndex = PieFiling.SELECTION_NONE
719 screen = QtGui.QStylePainter(self)
720 screen.setRenderHint(QtGui.QPainter.Antialiasing, True)
721 option = QtGui.QStyleOptionButton()
722 option.initFrom(self)
723 option.state = QtGui.QStyle.State_Sunken if self._pressed else QtGui.QStyle.State_Raised
725 screen.drawControl(QtGui.QStyle.CE_PushButton, option)
726 self._buttonArtist.paintPainter(selectionIndex, screen)
728 QtGui.QWidget.paintEvent(self, paintEvent)
731 return iter(self._filing)
734 return len(self._filing)
736 def _popup_child(self, position):
737 self._poppedUp = True
738 self.aboutToShow.emit()
740 self._delayPopupTimer.stop()
741 self._popupLocation = None
743 position = position - QtCore.QPoint(self._filing.outerRadius(), self._filing.outerRadius())
744 self._display.move(position)
749 def _hide_child(self):
750 self._poppedUp = False
751 self.aboutToHide.emit()
755 def _select_at(self, index):
756 self._selectionIndex = index
758 def _update_selection(self, lastMousePos, ignoreOuter = False):
759 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
760 if radius < self._filing.innerRadius():
761 self._select_at(PieFiling.SELECTION_CENTER)
762 elif radius <= self._filing.outerRadius() or ignoreOuter:
763 self._select_at(self.indexAt(lastMousePos))
765 self._select_at(PieFiling.SELECTION_NONE)
767 def _activate_at(self, index):
768 if index == PieFiling.SELECTION_NONE:
771 elif index == PieFiling.SELECTION_CENTER:
772 child = self._filing.center()
774 child = self.itemAt(index)
776 if child.action().isEnabled():
777 child.action().trigger()
778 self.activated.emit(index)
783 class QPieMenu(QtGui.QWidget):
785 activated = qt_compat.Signal(int)
786 highlighted = qt_compat.Signal(int)
787 canceled = qt_compat.Signal()
788 aboutToShow = qt_compat.Signal()
789 aboutToHide = qt_compat.Signal()
791 def __init__(self, parent = None):
792 QtGui.QWidget.__init__(self, parent)
793 self._cachedCenterPosition = self.rect().center()
795 self._filing = PieFiling()
796 self._artist = PieArtist(self._filing)
797 self._selectionIndex = PieFiling.SELECTION_NONE
799 self._mousePosition = ()
800 self.setFocusPolicy(QtCore.Qt.StrongFocus)
802 def popup(self, pos):
803 self._update_selection(pos)
806 def insertItem(self, item, index = -1):
807 self._filing.insertItem(item, index)
810 def removeItemAt(self, index):
811 self._filing.removeItemAt(index)
814 def set_center(self, item):
815 self._filing.set_center(item)
822 def itemAt(self, index):
823 return self._filing.itemAt(index)
825 def indexAt(self, point):
826 return self._filing.indexAt(self._cachedCenterPosition, point)
828 def innerRadius(self):
829 return self._filing.innerRadius()
831 def setInnerRadius(self, radius):
832 self._filing.setInnerRadius(radius)
835 def outerRadius(self):
836 return self._filing.outerRadius()
838 def setOuterRadius(self, radius):
839 self._filing.setOuterRadius(radius)
843 return self._artist.pieSize()
845 @misc_utils.log_exception(_moduleLogger)
846 def mousePressEvent(self, mouseEvent):
847 lastSelection = self._selectionIndex
849 lastMousePos = mouseEvent.pos()
850 self._update_selection(lastMousePos)
851 self._mousePosition = lastMousePos
853 if lastSelection != self._selectionIndex:
854 self.highlighted.emit(self._selectionIndex)
857 @misc_utils.log_exception(_moduleLogger)
858 def mouseMoveEvent(self, mouseEvent):
859 lastSelection = self._selectionIndex
861 lastMousePos = mouseEvent.pos()
862 self._update_selection(lastMousePos)
864 if lastSelection != self._selectionIndex:
865 self.highlighted.emit(self._selectionIndex)
868 @misc_utils.log_exception(_moduleLogger)
869 def mouseReleaseEvent(self, mouseEvent):
870 lastSelection = self._selectionIndex
872 lastMousePos = mouseEvent.pos()
873 self._update_selection(lastMousePos)
874 self._mousePosition = ()
876 self._activate_at(self._selectionIndex)
879 @misc_utils.log_exception(_moduleLogger)
880 def keyPressEvent(self, keyEvent):
881 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
882 if self._selectionIndex != len(self._filing) - 1:
883 nextSelection = self._selectionIndex + 1
886 self._select_at(nextSelection)
888 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
889 if 0 < self._selectionIndex:
890 nextSelection = self._selectionIndex - 1
892 nextSelection = len(self._filing) - 1
893 self._select_at(nextSelection)
895 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
896 self._activate_at(self._selectionIndex)
897 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
898 self._activate_at(PieFiling.SELECTION_NONE)
900 QtGui.QWidget.keyPressEvent(self, keyEvent)
902 @misc_utils.log_exception(_moduleLogger)
903 def showEvent(self, showEvent):
904 self.aboutToShow.emit()
905 self._cachedCenterPosition = self.rect().center()
907 mask = self._artist.show(self.palette())
910 lastMousePos = self.mapFromGlobal(QtGui.QCursor.pos())
911 self._update_selection(lastMousePos)
913 QtGui.QWidget.showEvent(self, showEvent)
915 @misc_utils.log_exception(_moduleLogger)
916 def hideEvent(self, hideEvent):
918 self._selectionIndex = PieFiling.SELECTION_NONE
919 QtGui.QWidget.hideEvent(self, hideEvent)
921 @misc_utils.log_exception(_moduleLogger)
922 def paintEvent(self, paintEvent):
923 canvas = self._artist.paint(self._selectionIndex)
925 screen = QtGui.QPainter(self)
926 screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
928 QtGui.QWidget.paintEvent(self, paintEvent)
931 return iter(self._filing)
934 return len(self._filing)
936 def _select_at(self, index):
937 self._selectionIndex = index
939 def _update_selection(self, lastMousePos):
940 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
941 if radius < self._filing.innerRadius():
942 self._selectionIndex = PieFiling.SELECTION_CENTER
943 elif radius <= self._filing.outerRadius():
944 self._select_at(self.indexAt(lastMousePos))
946 self._selectionIndex = PieFiling.SELECTION_NONE
948 def _activate_at(self, index):
949 if index == PieFiling.SELECTION_NONE:
951 self.aboutToHide.emit()
954 elif index == PieFiling.SELECTION_CENTER:
955 child = self._filing.center()
957 child = self.itemAt(index)
959 if child.isEnabled():
960 child.action().trigger()
961 self.activated.emit(index)
964 self.aboutToHide.emit()
969 PieFiling.NULL_CENTER.setEnabled(False)
976 def _on_about_to_hide(app):
980 if __name__ == "__main__":
981 app = QtGui.QApplication([])
989 singleAction = QtGui.QAction(None)
990 singleAction.setText("Boo")
991 singleItem = QActionPieItem(singleAction)
993 spie.insertItem(singleItem)
997 oneAction = QtGui.QAction(None)
998 oneAction.setText("Chew")
999 oneItem = QActionPieItem(oneAction)
1000 twoAction = QtGui.QAction(None)
1001 twoAction.setText("Foo")
1002 twoItem = QActionPieItem(twoAction)
1003 iconTextAction = QtGui.QAction(None)
1004 iconTextAction.setText("Icon")
1005 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1006 iconTextItem = QActionPieItem(iconTextAction)
1008 mpie.insertItem(oneItem)
1009 mpie.insertItem(twoItem)
1010 mpie.insertItem(oneItem)
1011 mpie.insertItem(iconTextItem)
1015 oneAction = QtGui.QAction(None)
1016 oneAction.setText("Chew")
1017 oneAction.triggered.connect(lambda: _print("Chew"))
1018 oneItem = QActionPieItem(oneAction)
1019 twoAction = QtGui.QAction(None)
1020 twoAction.setText("Foo")
1021 twoAction.triggered.connect(lambda: _print("Foo"))
1022 twoItem = QActionPieItem(twoAction)
1023 iconAction = QtGui.QAction(None)
1024 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1025 iconAction.triggered.connect(lambda: _print("Icon"))
1026 iconItem = QActionPieItem(iconAction)
1027 iconTextAction = QtGui.QAction(None)
1028 iconTextAction.setText("Icon")
1029 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1030 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1031 iconTextItem = QActionPieItem(iconTextAction)
1033 mpie.set_center(iconItem)
1034 mpie.insertItem(oneItem)
1035 mpie.insertItem(twoItem)
1036 mpie.insertItem(oneItem)
1037 mpie.insertItem(iconTextItem)
1039 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1040 mpie.canceled.connect(lambda: _print("Canceled"))
1043 oneAction = QtGui.QAction(None)
1044 oneAction.setText("Chew")
1045 oneAction.triggered.connect(lambda: _print("Chew"))
1046 oneItem = QActionPieItem(oneAction)
1047 twoAction = QtGui.QAction(None)
1048 twoAction.setText("Foo")
1049 twoAction.triggered.connect(lambda: _print("Foo"))
1050 twoItem = QActionPieItem(twoAction)
1051 iconAction = QtGui.QAction(None)
1052 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1053 iconAction.triggered.connect(lambda: _print("Icon"))
1054 iconItem = QActionPieItem(iconAction)
1055 iconTextAction = QtGui.QAction(None)
1056 iconTextAction.setText("Icon")
1057 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1058 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1059 iconTextItem = QActionPieItem(iconTextAction)
1060 pieFiling = PieFiling()
1061 pieFiling.set_center(iconItem)
1062 pieFiling.insertItem(oneItem)
1063 pieFiling.insertItem(twoItem)
1064 pieFiling.insertItem(oneItem)
1065 pieFiling.insertItem(iconTextItem)
1066 mpie = QPieDisplay(pieFiling)
1070 oneAction = QtGui.QAction(None)
1071 oneAction.setText("Chew")
1072 oneAction.triggered.connect(lambda: _print("Chew"))
1073 oneItem = QActionPieItem(oneAction)
1074 twoAction = QtGui.QAction(None)
1075 twoAction.setText("Foo")
1076 twoAction.triggered.connect(lambda: _print("Foo"))
1077 twoItem = QActionPieItem(twoAction)
1078 iconAction = QtGui.QAction(None)
1079 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1080 iconAction.triggered.connect(lambda: _print("Icon"))
1081 iconItem = QActionPieItem(iconAction)
1082 iconTextAction = QtGui.QAction(None)
1083 iconTextAction.setText("Icon")
1084 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1085 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1086 iconTextItem = QActionPieItem(iconTextAction)
1087 mpie = QPieButton(iconItem)
1088 mpie.set_center(iconItem)
1089 mpie.insertItem(oneItem)
1090 mpie.insertItem(twoItem)
1091 mpie.insertItem(oneItem)
1092 mpie.insertItem(iconTextItem)
1094 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1095 mpie.canceled.connect(lambda: _print("Canceled"))