6 from PyQt4 import QtGui
7 from PyQt4 import QtCore
9 import misc as misc_utils
12 _moduleLogger = logging.getLogger(__name__)
18 def _radius_at(center, pos):
23 radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
27 def _angle_at(center, pos):
32 radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
33 angle = math.acos(xDelta / radius)
35 angle = _TWOPI - angle
40 class QActionPieItem(object):
42 def __init__(self, action, weight = 1):
49 def setWeight(self, weight):
55 def setEnabled(self, enabled = True):
56 self._action.setEnabled(enabled)
59 return self._action.isEnabled()
62 class PieFiling(object):
64 INNER_RADIUS_DEFAULT = 64
65 OUTER_RADIUS_DEFAULT = 192
70 NULL_CENTER = QActionPieItem(QtGui.QAction(None))
73 self._innerRadius = self.INNER_RADIUS_DEFAULT
74 self._outerRadius = self.OUTER_RADIUS_DEFAULT
76 self._center = self.NULL_CENTER
78 self._cacheIndexToAngle = {}
79 self._cacheTotalWeight = 0
81 def insertItem(self, item, index = -1):
82 self._children.insert(index, item)
83 self._invalidate_cache()
85 def removeItemAt(self, index):
86 item = self._children.pop(index)
87 self._invalidate_cache()
89 def set_center(self, item):
91 item = self.NULL_CENTER
99 self._center = self.NULL_CENTER
100 self._invalidate_cache()
102 def itemAt(self, index):
103 return self._children[index]
105 def indexAt(self, center, point):
106 return self._angle_to_index(_angle_at(center, point))
108 def innerRadius(self):
109 return self._innerRadius
111 def setInnerRadius(self, radius):
112 self._innerRadius = radius
114 def outerRadius(self):
115 return self._outerRadius
117 def setOuterRadius(self, radius):
118 self._outerRadius = radius
121 return iter(self._children)
124 return len(self._children)
126 def __getitem__(self, index):
127 return self._children[index]
129 def _invalidate_cache(self):
130 self._cacheIndexToAngle.clear()
131 self._cacheTotalWeight = sum(child.weight() for child in self._children)
132 if self._cacheTotalWeight == 0:
133 self._cacheTotalWeight = 1
135 def _index_to_angle(self, index, isShifted):
136 key = index, isShifted
137 if key in self._cacheIndexToAngle:
138 return self._cacheIndexToAngle[key]
139 index = index % len(self._children)
141 baseAngle = _TWOPI / self._cacheTotalWeight
146 angle -= (self._children[0].weight() * baseAngle) / 2
148 angle -= baseAngle / 2
152 for i, child in enumerate(self._children):
155 angle += child.weight() * baseAngle
156 while _TWOPI < angle:
159 self._cacheIndexToAngle[key] = angle
162 def _angle_to_index(self, angle):
163 numChildren = len(self._children)
165 return self.SELECTION_CENTER
167 baseAngle = _TWOPI / self._cacheTotalWeight
169 iterAngle = math.pi / 2 - (self.itemAt(0).weight() * baseAngle) / 2
173 oldIterAngle = iterAngle
174 for index, child in enumerate(self._children):
175 iterAngle += child.weight() * baseAngle
176 if oldIterAngle < angle and angle <= iterAngle:
177 return index - 1 if index != 0 else numChildren - 1
178 elif oldIterAngle < (angle + _TWOPI) and (angle + _TWOPI <= iterAngle):
179 return index - 1 if index != 0 else numChildren - 1
180 oldIterAngle = iterAngle
183 class PieArtist(object):
185 ICON_SIZE_DEFAULT = 48
187 SHAPE_CIRCLE = "circle"
188 SHAPE_SQUARE = "square"
189 DEFAULT_SHAPE = SHAPE_SQUARE
191 BACKGROUND_FILL = "fill"
192 BACKGROUND_NOFILL = "no fill"
194 def __init__(self, filing, background = BACKGROUND_FILL):
195 self._filing = filing
197 self._cachedOuterRadius = self._filing.outerRadius()
198 self._cachedInnerRadius = self._filing.innerRadius()
199 canvasSize = self._cachedOuterRadius * 2 + 1
200 self._canvas = QtGui.QPixmap(canvasSize, canvasSize)
202 self._backgroundState = background
206 diameter = self._filing.outerRadius() * 2 + 1
207 return QtCore.QSize(diameter, diameter)
209 def centerSize(self):
210 painter = QtGui.QPainter(self._canvas)
211 text = self._filing.center().action().text()
212 fontMetrics = painter.fontMetrics()
214 textBoundingRect = fontMetrics.boundingRect(text)
216 textBoundingRect = QtCore.QRect()
217 textWidth = textBoundingRect.width()
218 textHeight = textBoundingRect.height()
221 textWidth + self.ICON_SIZE_DEFAULT,
222 max(textHeight, self.ICON_SIZE_DEFAULT),
225 def show(self, palette):
226 self.palette = palette
229 self._cachedOuterRadius != self._filing.outerRadius() or
230 self._cachedInnerRadius != self._filing.innerRadius()
232 self._cachedOuterRadius = self._filing.outerRadius()
233 self._cachedInnerRadius = self._filing.innerRadius()
234 self._canvas = self._canvas.scaled(self.pieSize())
236 if self._mask is None:
237 self._mask = QtGui.QBitmap(self._canvas.size())
238 self._mask.fill(QtCore.Qt.color0)
239 self._generate_mask(self._mask)
240 self._canvas.setMask(self._mask)
246 def paint(self, selectionIndex):
247 painter = QtGui.QPainter(self._canvas)
248 painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
250 self.paintPainter(selectionIndex, painter)
254 def paintPainter(self, selectionIndex, painter):
255 adjustmentRect = painter.viewport().adjusted(0, 0, -1, -1)
257 numChildren = len(self._filing)
259 self._paint_center_background(painter, adjustmentRect, selectionIndex)
260 self._paint_center_foreground(painter, adjustmentRect, selectionIndex)
263 for i in xrange(len(self._filing)):
264 self._paint_slice_background(painter, adjustmentRect, i, selectionIndex)
266 self._paint_center_background(painter, adjustmentRect, selectionIndex)
267 self._paint_center_foreground(painter, adjustmentRect, selectionIndex)
269 for i in xrange(len(self._filing)):
270 self._paint_slice_foreground(painter, adjustmentRect, i, selectionIndex)
272 def _generate_mask(self, mask):
274 Specifies on the mask the shape of the pie menu
276 painter = QtGui.QPainter(mask)
277 painter.setPen(QtCore.Qt.color1)
278 painter.setBrush(QtCore.Qt.color1)
279 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
280 painter.drawRect(mask.rect())
281 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
282 painter.drawEllipse(mask.rect().adjusted(0, 0, -1, -1))
284 raise NotImplementedError(self.DEFAULT_SHAPE)
286 def _paint_slice_background(self, painter, adjustmentRect, i, selectionIndex):
287 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
288 currentWidth = adjustmentRect.width()
289 newWidth = math.sqrt(2) * currentWidth
290 dx = (newWidth - currentWidth) / 2
291 adjustmentRect = adjustmentRect.adjusted(-dx, -dx, dx, dx)
292 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
295 raise NotImplementedError(self.DEFAULT_SHAPE)
297 if self._backgroundState == self.BACKGROUND_NOFILL:
298 painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
299 painter.setPen(self.palette.highlight().color())
301 if i == selectionIndex and self._filing[i].isEnabled():
302 painter.setBrush(self.palette.highlight())
303 painter.setPen(self.palette.highlight().color())
305 painter.setBrush(self.palette.window())
306 painter.setPen(self.palette.window().color())
308 a = self._filing._index_to_angle(i, True)
309 b = self._filing._index_to_angle(i + 1, True)
316 startAngleInDeg = (a * 360 * 16) / _TWOPI
317 sizeInDeg = (size * 360 * 16) / _TWOPI
318 painter.drawPie(adjustmentRect, int(startAngleInDeg), int(sizeInDeg))
320 def _paint_slice_foreground(self, painter, adjustmentRect, i, selectionIndex):
321 child = self._filing[i]
323 a = self._filing._index_to_angle(i, True)
324 b = self._filing._index_to_angle(i + 1, True)
327 middleAngle = (a + b) / 2
328 averageRadius = (self._cachedInnerRadius + self._cachedOuterRadius) / 2
330 sliceX = averageRadius * math.cos(middleAngle)
331 sliceY = - averageRadius * math.sin(middleAngle)
333 piePos = adjustmentRect.center()
337 painter, child.action(), i == selectionIndex, pieX+sliceX, pieY+sliceY
340 def _paint_label(self, painter, action, isSelected, x, y):
342 fontMetrics = painter.fontMetrics()
344 textBoundingRect = fontMetrics.boundingRect(text)
346 textBoundingRect = QtCore.QRect()
347 textWidth = textBoundingRect.width()
348 textHeight = textBoundingRect.height()
350 icon = action.icon().pixmap(
351 QtCore.QSize(self.ICON_SIZE_DEFAULT, self.ICON_SIZE_DEFAULT),
355 iconWidth = icon.width()
356 iconHeight = icon.width()
357 averageWidth = (iconWidth + textWidth)/2
358 if not icon.isNull():
359 iconRect = QtCore.QRect(
366 painter.drawPixmap(iconRect, icon)
370 if action.isEnabled():
371 pen = self.palette.highlightedText()
372 brush = self.palette.highlight()
374 pen = self.palette.mid()
375 brush = self.palette.window()
377 if action.isEnabled():
378 pen = self.palette.windowText()
380 pen = self.palette.mid()
381 brush = self.palette.window()
383 leftX = x - averageWidth + iconWidth
384 topY = y + textHeight/2
385 painter.setPen(pen.color())
386 painter.setBrush(brush)
387 painter.drawText(leftX, topY, text)
389 def _paint_center_background(self, painter, adjustmentRect, selectionIndex):
390 if self._backgroundState == self.BACKGROUND_NOFILL:
392 if len(self._filing) == 0:
393 if self._backgroundState == self.BACKGROUND_NOFILL:
394 painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
396 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
397 painter.setBrush(self.palette.highlight())
399 painter.setBrush(self.palette.window())
400 painter.setPen(self.palette.mid().color())
402 painter.drawRect(adjustmentRect)
404 dark = self.palette.mid().color()
405 light = self.palette.light().color()
406 if self._backgroundState == self.BACKGROUND_NOFILL:
407 background = QtGui.QBrush(QtCore.Qt.transparent)
409 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
410 background = self.palette.highlight().color()
412 background = self.palette.window().color()
414 innerRadius = self._cachedInnerRadius
415 adjustmentCenterPos = adjustmentRect.center()
416 innerRect = QtCore.QRect(
417 adjustmentCenterPos.x() - innerRadius,
418 adjustmentCenterPos.y() - innerRadius,
423 painter.setPen(QtCore.Qt.NoPen)
424 painter.setBrush(background)
425 painter.drawPie(innerRect, 0, 360 * 16)
427 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
429 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
430 painter.setPen(QtGui.QPen(dark, 1))
431 painter.setBrush(QtCore.Qt.NoBrush)
432 painter.drawEllipse(adjustmentRect)
434 raise NotImplementedError(self.DEFAULT_SHAPE)
436 def _paint_center_foreground(self, painter, adjustmentRect, selectionIndex):
437 centerPos = adjustmentRect.center()
446 self._filing.center().action(),
447 selectionIndex == PieFiling.SELECTION_CENTER,
452 class QPieDisplay(QtGui.QWidget):
454 def __init__(self, filing, parent = None, flags = QtCore.Qt.Window):
455 QtGui.QWidget.__init__(self, parent, flags)
456 self._filing = filing
457 self._artist = PieArtist(self._filing)
458 self._selectionIndex = PieFiling.SELECTION_NONE
460 def popup(self, pos):
461 self._update_selection(pos)
465 return self._artist.pieSize()
467 @misc_utils.log_exception(_moduleLogger)
468 def showEvent(self, showEvent):
469 mask = self._artist.show(self.palette())
472 QtGui.QWidget.showEvent(self, showEvent)
474 @misc_utils.log_exception(_moduleLogger)
475 def hideEvent(self, hideEvent):
477 self._selectionIndex = PieFiling.SELECTION_NONE
478 QtGui.QWidget.hideEvent(self, hideEvent)
480 @misc_utils.log_exception(_moduleLogger)
481 def paintEvent(self, paintEvent):
482 canvas = self._artist.paint(self._selectionIndex)
483 offset = (self.size() - canvas.size()) / 2
485 screen = QtGui.QPainter(self)
486 screen.drawPixmap(QtCore.QPoint(offset.width(), offset.height()), canvas)
488 QtGui.QWidget.paintEvent(self, paintEvent)
490 def selectAt(self, index):
491 oldIndex = self._selectionIndex
492 self._selectionIndex = index
497 class QPieButton(QtGui.QWidget):
499 activated = QtCore.pyqtSignal(int)
500 highlighted = QtCore.pyqtSignal(int)
501 canceled = QtCore.pyqtSignal()
502 aboutToShow = QtCore.pyqtSignal()
503 aboutToHide = QtCore.pyqtSignal()
508 def __init__(self, buttonSlice, parent = None, buttonSlices = None):
509 # @bug Artifacts on Maemo 5 due to window 3D effects, find way to disable them for just these?
510 # @bug The pie's are being pushed back on screen on Maemo, leading to coordinate issues
511 QtGui.QWidget.__init__(self, parent)
512 self._cachedCenterPosition = self.rect().center()
514 self._filing = PieFiling()
515 self._display = QPieDisplay(self._filing, None, QtCore.Qt.SplashScreen)
516 self._selectionIndex = PieFiling.SELECTION_NONE
518 self._buttonFiling = PieFiling()
519 self._buttonFiling.set_center(buttonSlice)
520 if buttonSlices is not None:
521 for slice in buttonSlices:
522 self._buttonFiling.insertItem(slice)
523 self._buttonFiling.setOuterRadius(self.BUTTON_RADIUS)
524 self._buttonArtist = PieArtist(self._buttonFiling, PieArtist.BACKGROUND_NOFILL)
525 self._poppedUp = False
526 self._pressed = False
528 self._delayPopupTimer = QtCore.QTimer()
529 self._delayPopupTimer.setInterval(self.DELAY)
530 self._delayPopupTimer.setSingleShot(True)
531 self._delayPopupTimer.timeout.connect(self._on_delayed_popup)
532 self._popupLocation = None
534 self._mousePosition = None
535 self.setFocusPolicy(QtCore.Qt.StrongFocus)
538 QtGui.QSizePolicy.MinimumExpanding,
539 QtGui.QSizePolicy.MinimumExpanding,
543 def insertItem(self, item, index = -1):
544 self._filing.insertItem(item, index)
546 def removeItemAt(self, index):
547 self._filing.removeItemAt(index)
549 def set_center(self, item):
550 self._filing.set_center(item)
552 def set_button(self, item):
558 def itemAt(self, index):
559 return self._filing.itemAt(index)
561 def indexAt(self, point):
562 return self._filing.indexAt(self._cachedCenterPosition, point)
564 def innerRadius(self):
565 return self._filing.innerRadius()
567 def setInnerRadius(self, radius):
568 self._filing.setInnerRadius(radius)
570 def outerRadius(self):
571 return self._filing.outerRadius()
573 def setOuterRadius(self, radius):
574 self._filing.setOuterRadius(radius)
576 def buttonRadius(self):
577 return self._buttonFiling.outerRadius()
579 def setButtonRadius(self, radius):
580 self._buttonFiling.setOuterRadius(radius)
581 self._buttonFiling.setInnerRadius(radius / 2)
582 self._buttonArtist.show(self.palette())
584 def minimumSizeHint(self):
585 return self._buttonArtist.centerSize()
587 @misc_utils.log_exception(_moduleLogger)
588 def mousePressEvent(self, mouseEvent):
589 lastSelection = self._selectionIndex
591 lastMousePos = mouseEvent.pos()
592 self._mousePosition = lastMousePos
593 self._update_selection(self._cachedCenterPosition)
595 self.highlighted.emit(self._selectionIndex)
597 self._display.selectAt(self._selectionIndex)
600 self._popupLocation = mouseEvent.globalPos()
601 self._delayPopupTimer.start()
603 @misc_utils.log_exception(_moduleLogger)
604 def _on_delayed_popup(self):
605 assert self._popupLocation is not None, "Widget location abuse"
606 self._popup_child(self._popupLocation)
608 @misc_utils.log_exception(_moduleLogger)
609 def mouseMoveEvent(self, mouseEvent):
610 lastSelection = self._selectionIndex
612 lastMousePos = mouseEvent.pos()
613 if self._mousePosition is None:
615 self._update_selection(lastMousePos)
618 self._update_selection(
619 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
623 if lastSelection != self._selectionIndex:
624 self.highlighted.emit(self._selectionIndex)
625 self._display.selectAt(self._selectionIndex)
627 if self._selectionIndex != PieFiling.SELECTION_CENTER and self._delayPopupTimer.isActive():
628 self._on_delayed_popup()
630 @misc_utils.log_exception(_moduleLogger)
631 def mouseReleaseEvent(self, mouseEvent):
632 self._delayPopupTimer.stop()
633 self._popupLocation = None
635 lastSelection = self._selectionIndex
637 lastMousePos = mouseEvent.pos()
638 if self._mousePosition is None:
640 self._update_selection(lastMousePos)
643 self._update_selection(
644 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
647 self._mousePosition = None
649 self._activate_at(self._selectionIndex)
650 self._pressed = False
654 @misc_utils.log_exception(_moduleLogger)
655 def keyPressEvent(self, keyEvent):
656 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
657 self._popup_child(QtGui.QCursor.pos())
658 if self._selectionIndex != len(self._filing) - 1:
659 nextSelection = self._selectionIndex + 1
662 self._select_at(nextSelection)
663 self._display.selectAt(self._selectionIndex)
664 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
665 self._popup_child(QtGui.QCursor.pos())
666 if 0 < self._selectionIndex:
667 nextSelection = self._selectionIndex - 1
669 nextSelection = len(self._filing) - 1
670 self._select_at(nextSelection)
671 self._display.selectAt(self._selectionIndex)
672 elif keyEvent.key() in [QtCore.Qt.Key_Space]:
673 self._popup_child(QtGui.QCursor.pos())
674 self._select_at(PieFiling.SELECTION_CENTER)
675 self._display.selectAt(self._selectionIndex)
676 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
677 self._delayPopupTimer.stop()
678 self._popupLocation = None
679 self._activate_at(self._selectionIndex)
681 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
682 self._delayPopupTimer.stop()
683 self._popupLocation = None
684 self._activate_at(PieFiling.SELECTION_NONE)
687 QtGui.QWidget.keyPressEvent(self, keyEvent)
689 @misc_utils.log_exception(_moduleLogger)
690 def resizeEvent(self, resizeEvent):
691 self.setButtonRadius(min(resizeEvent.size().width(), resizeEvent.size().height()) / 2 - 1)
692 QtGui.QWidget.resizeEvent(self, resizeEvent)
694 @misc_utils.log_exception(_moduleLogger)
695 def showEvent(self, showEvent):
696 self._buttonArtist.show(self.palette())
697 self._cachedCenterPosition = self.rect().center()
699 QtGui.QWidget.showEvent(self, showEvent)
701 @misc_utils.log_exception(_moduleLogger)
702 def hideEvent(self, hideEvent):
704 self._select_at(PieFiling.SELECTION_NONE)
705 QtGui.QWidget.hideEvent(self, hideEvent)
707 @misc_utils.log_exception(_moduleLogger)
708 def paintEvent(self, paintEvent):
709 self.setButtonRadius(min(self.rect().width(), self.rect().height()) / 2 - 1)
711 selectionIndex = PieFiling.SELECTION_CENTER
713 selectionIndex = PieFiling.SELECTION_NONE
715 screen = QtGui.QStylePainter(self)
716 screen.setRenderHint(QtGui.QPainter.Antialiasing, True)
717 option = QtGui.QStyleOptionButton()
718 option.initFrom(self)
719 option.state = QtGui.QStyle.State_Sunken if self._pressed else QtGui.QStyle.State_Raised
721 screen.drawControl(QtGui.QStyle.CE_PushButton, option)
722 self._buttonArtist.paintPainter(selectionIndex, screen)
724 QtGui.QWidget.paintEvent(self, paintEvent)
727 return iter(self._filing)
730 return len(self._filing)
732 def _popup_child(self, position):
733 self._poppedUp = True
734 self.aboutToShow.emit()
736 self._delayPopupTimer.stop()
737 self._popupLocation = None
739 position = position - QtCore.QPoint(self._filing.outerRadius(), self._filing.outerRadius())
740 self._display.move(position)
745 def _hide_child(self):
746 self._poppedUp = False
747 self.aboutToHide.emit()
751 def _select_at(self, index):
752 self._selectionIndex = index
754 def _update_selection(self, lastMousePos, ignoreOuter = False):
755 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
756 if radius < self._filing.innerRadius():
757 self._select_at(PieFiling.SELECTION_CENTER)
758 elif radius <= self._filing.outerRadius() or ignoreOuter:
759 self._select_at(self.indexAt(lastMousePos))
761 self._select_at(PieFiling.SELECTION_NONE)
763 def _activate_at(self, index):
764 if index == PieFiling.SELECTION_NONE:
767 elif index == PieFiling.SELECTION_CENTER:
768 child = self._filing.center()
770 child = self.itemAt(index)
772 if child.action().isEnabled():
773 child.action().trigger()
774 self.activated.emit(index)
779 class QPieMenu(QtGui.QWidget):
781 activated = QtCore.pyqtSignal(int)
782 highlighted = QtCore.pyqtSignal(int)
783 canceled = QtCore.pyqtSignal()
784 aboutToShow = QtCore.pyqtSignal()
785 aboutToHide = QtCore.pyqtSignal()
787 def __init__(self, parent = None):
788 QtGui.QWidget.__init__(self, parent)
789 self._cachedCenterPosition = self.rect().center()
791 self._filing = PieFiling()
792 self._artist = PieArtist(self._filing)
793 self._selectionIndex = PieFiling.SELECTION_NONE
795 self._mousePosition = ()
796 self.setFocusPolicy(QtCore.Qt.StrongFocus)
798 def popup(self, pos):
799 self._update_selection(pos)
802 def insertItem(self, item, index = -1):
803 self._filing.insertItem(item, index)
806 def removeItemAt(self, index):
807 self._filing.removeItemAt(index)
810 def set_center(self, item):
811 self._filing.set_center(item)
818 def itemAt(self, index):
819 return self._filing.itemAt(index)
821 def indexAt(self, point):
822 return self._filing.indexAt(self._cachedCenterPosition, point)
824 def innerRadius(self):
825 return self._filing.innerRadius()
827 def setInnerRadius(self, radius):
828 self._filing.setInnerRadius(radius)
831 def outerRadius(self):
832 return self._filing.outerRadius()
834 def setOuterRadius(self, radius):
835 self._filing.setOuterRadius(radius)
839 return self._artist.pieSize()
841 @misc_utils.log_exception(_moduleLogger)
842 def mousePressEvent(self, mouseEvent):
843 lastSelection = self._selectionIndex
845 lastMousePos = mouseEvent.pos()
846 self._update_selection(lastMousePos)
847 self._mousePosition = lastMousePos
849 if lastSelection != self._selectionIndex:
850 self.highlighted.emit(self._selectionIndex)
853 @misc_utils.log_exception(_moduleLogger)
854 def mouseMoveEvent(self, mouseEvent):
855 lastSelection = self._selectionIndex
857 lastMousePos = mouseEvent.pos()
858 self._update_selection(lastMousePos)
860 if lastSelection != self._selectionIndex:
861 self.highlighted.emit(self._selectionIndex)
864 @misc_utils.log_exception(_moduleLogger)
865 def mouseReleaseEvent(self, mouseEvent):
866 lastSelection = self._selectionIndex
868 lastMousePos = mouseEvent.pos()
869 self._update_selection(lastMousePos)
870 self._mousePosition = ()
872 self._activate_at(self._selectionIndex)
875 @misc_utils.log_exception(_moduleLogger)
876 def keyPressEvent(self, keyEvent):
877 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
878 if self._selectionIndex != len(self._filing) - 1:
879 nextSelection = self._selectionIndex + 1
882 self._select_at(nextSelection)
884 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
885 if 0 < self._selectionIndex:
886 nextSelection = self._selectionIndex - 1
888 nextSelection = len(self._filing) - 1
889 self._select_at(nextSelection)
891 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
892 self._activate_at(self._selectionIndex)
893 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
894 self._activate_at(PieFiling.SELECTION_NONE)
896 QtGui.QWidget.keyPressEvent(self, keyEvent)
898 @misc_utils.log_exception(_moduleLogger)
899 def showEvent(self, showEvent):
900 self.aboutToShow.emit()
901 self._cachedCenterPosition = self.rect().center()
903 mask = self._artist.show(self.palette())
906 lastMousePos = self.mapFromGlobal(QtGui.QCursor.pos())
907 self._update_selection(lastMousePos)
909 QtGui.QWidget.showEvent(self, showEvent)
911 @misc_utils.log_exception(_moduleLogger)
912 def hideEvent(self, hideEvent):
914 self._selectionIndex = PieFiling.SELECTION_NONE
915 QtGui.QWidget.hideEvent(self, hideEvent)
917 @misc_utils.log_exception(_moduleLogger)
918 def paintEvent(self, paintEvent):
919 canvas = self._artist.paint(self._selectionIndex)
921 screen = QtGui.QPainter(self)
922 screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
924 QtGui.QWidget.paintEvent(self, paintEvent)
927 return iter(self._filing)
930 return len(self._filing)
932 def _select_at(self, index):
933 self._selectionIndex = index
935 def _update_selection(self, lastMousePos):
936 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
937 if radius < self._filing.innerRadius():
938 self._selectionIndex = PieFiling.SELECTION_CENTER
939 elif radius <= self._filing.outerRadius():
940 self._select_at(self.indexAt(lastMousePos))
942 self._selectionIndex = PieFiling.SELECTION_NONE
944 def _activate_at(self, index):
945 if index == PieFiling.SELECTION_NONE:
947 self.aboutToHide.emit()
950 elif index == PieFiling.SELECTION_CENTER:
951 child = self._filing.center()
953 child = self.itemAt(index)
955 if child.isEnabled():
956 child.action().trigger()
957 self.activated.emit(index)
960 self.aboutToHide.emit()
965 PieFiling.NULL_CENTER.setEnabled(False)
972 def _on_about_to_hide(app):
976 if __name__ == "__main__":
977 app = QtGui.QApplication([])
985 singleAction = QtGui.QAction(None)
986 singleAction.setText("Boo")
987 singleItem = QActionPieItem(singleAction)
989 spie.insertItem(singleItem)
993 oneAction = QtGui.QAction(None)
994 oneAction.setText("Chew")
995 oneItem = QActionPieItem(oneAction)
996 twoAction = QtGui.QAction(None)
997 twoAction.setText("Foo")
998 twoItem = QActionPieItem(twoAction)
999 iconTextAction = QtGui.QAction(None)
1000 iconTextAction.setText("Icon")
1001 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1002 iconTextItem = QActionPieItem(iconTextAction)
1004 mpie.insertItem(oneItem)
1005 mpie.insertItem(twoItem)
1006 mpie.insertItem(oneItem)
1007 mpie.insertItem(iconTextItem)
1011 oneAction = QtGui.QAction(None)
1012 oneAction.setText("Chew")
1013 oneAction.triggered.connect(lambda: _print("Chew"))
1014 oneItem = QActionPieItem(oneAction)
1015 twoAction = QtGui.QAction(None)
1016 twoAction.setText("Foo")
1017 twoAction.triggered.connect(lambda: _print("Foo"))
1018 twoItem = QActionPieItem(twoAction)
1019 iconAction = QtGui.QAction(None)
1020 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1021 iconAction.triggered.connect(lambda: _print("Icon"))
1022 iconItem = QActionPieItem(iconAction)
1023 iconTextAction = QtGui.QAction(None)
1024 iconTextAction.setText("Icon")
1025 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1026 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1027 iconTextItem = QActionPieItem(iconTextAction)
1029 mpie.set_center(iconItem)
1030 mpie.insertItem(oneItem)
1031 mpie.insertItem(twoItem)
1032 mpie.insertItem(oneItem)
1033 mpie.insertItem(iconTextItem)
1035 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1036 mpie.canceled.connect(lambda: _print("Canceled"))
1039 oneAction = QtGui.QAction(None)
1040 oneAction.setText("Chew")
1041 oneAction.triggered.connect(lambda: _print("Chew"))
1042 oneItem = QActionPieItem(oneAction)
1043 twoAction = QtGui.QAction(None)
1044 twoAction.setText("Foo")
1045 twoAction.triggered.connect(lambda: _print("Foo"))
1046 twoItem = QActionPieItem(twoAction)
1047 iconAction = QtGui.QAction(None)
1048 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1049 iconAction.triggered.connect(lambda: _print("Icon"))
1050 iconItem = QActionPieItem(iconAction)
1051 iconTextAction = QtGui.QAction(None)
1052 iconTextAction.setText("Icon")
1053 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1054 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1055 iconTextItem = QActionPieItem(iconTextAction)
1056 pieFiling = PieFiling()
1057 pieFiling.set_center(iconItem)
1058 pieFiling.insertItem(oneItem)
1059 pieFiling.insertItem(twoItem)
1060 pieFiling.insertItem(oneItem)
1061 pieFiling.insertItem(iconTextItem)
1062 mpie = QPieDisplay(pieFiling)
1066 oneAction = QtGui.QAction(None)
1067 oneAction.setText("Chew")
1068 oneAction.triggered.connect(lambda: _print("Chew"))
1069 oneItem = QActionPieItem(oneAction)
1070 twoAction = QtGui.QAction(None)
1071 twoAction.setText("Foo")
1072 twoAction.triggered.connect(lambda: _print("Foo"))
1073 twoItem = QActionPieItem(twoAction)
1074 iconAction = QtGui.QAction(None)
1075 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1076 iconAction.triggered.connect(lambda: _print("Icon"))
1077 iconItem = QActionPieItem(iconAction)
1078 iconTextAction = QtGui.QAction(None)
1079 iconTextAction.setText("Icon")
1080 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1081 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1082 iconTextItem = QActionPieItem(iconTextAction)
1083 mpie = QPieButton(iconItem)
1084 mpie.set_center(iconItem)
1085 mpie.insertItem(oneItem)
1086 mpie.insertItem(twoItem)
1087 mpie.insertItem(oneItem)
1088 mpie.insertItem(iconTextItem)
1090 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1091 mpie.canceled.connect(lambda: _print("Canceled"))