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
527 self._delayPopupTimer = QtCore.QTimer()
528 self._delayPopupTimer.setInterval(self.DELAY)
529 self._delayPopupTimer.setSingleShot(True)
530 self._delayPopupTimer.timeout.connect(self._on_delayed_popup)
531 self._popupLocation = None
533 self._mousePosition = None
534 self.setFocusPolicy(QtCore.Qt.StrongFocus)
537 QtGui.QSizePolicy.MinimumExpanding,
538 QtGui.QSizePolicy.MinimumExpanding,
542 def insertItem(self, item, index = -1):
543 self._filing.insertItem(item, index)
545 def removeItemAt(self, index):
546 self._filing.removeItemAt(index)
548 def set_center(self, item):
549 self._filing.set_center(item)
551 def set_button(self, item):
557 def itemAt(self, index):
558 return self._filing.itemAt(index)
560 def indexAt(self, point):
561 return self._filing.indexAt(self._cachedCenterPosition, point)
563 def innerRadius(self):
564 return self._filing.innerRadius()
566 def setInnerRadius(self, radius):
567 self._filing.setInnerRadius(radius)
569 def outerRadius(self):
570 return self._filing.outerRadius()
572 def setOuterRadius(self, radius):
573 self._filing.setOuterRadius(radius)
575 def buttonRadius(self):
576 return self._buttonFiling.outerRadius()
578 def setButtonRadius(self, radius):
579 self._buttonFiling.setOuterRadius(radius)
580 self._buttonFiling.setInnerRadius(radius / 2)
581 self._buttonArtist.show(self.palette())
583 def minimumSizeHint(self):
584 return self._buttonArtist.centerSize()
586 @misc_utils.log_exception(_moduleLogger)
587 def mousePressEvent(self, mouseEvent):
588 lastSelection = self._selectionIndex
590 lastMousePos = mouseEvent.pos()
591 self._mousePosition = lastMousePos
592 self._update_selection(self._cachedCenterPosition)
594 self.highlighted.emit(self._selectionIndex)
596 self._display.selectAt(self._selectionIndex)
597 self._popupLocation = mouseEvent.globalPos()
598 self._delayPopupTimer.start()
600 @misc_utils.log_exception(_moduleLogger)
601 def _on_delayed_popup(self):
602 assert self._popupLocation is not None, "Widget location abuse"
603 self._popup_child(self._popupLocation)
605 @misc_utils.log_exception(_moduleLogger)
606 def mouseMoveEvent(self, mouseEvent):
607 lastSelection = self._selectionIndex
609 lastMousePos = mouseEvent.pos()
610 if self._mousePosition is None:
612 self._update_selection(lastMousePos)
615 self._update_selection(
616 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
620 if lastSelection != self._selectionIndex:
621 self.highlighted.emit(self._selectionIndex)
622 self._display.selectAt(self._selectionIndex)
624 if self._selectionIndex != PieFiling.SELECTION_CENTER and self._delayPopupTimer.isActive():
625 self._on_delayed_popup()
627 @misc_utils.log_exception(_moduleLogger)
628 def mouseReleaseEvent(self, mouseEvent):
629 self._delayPopupTimer.stop()
630 self._popupLocation = None
632 lastSelection = self._selectionIndex
634 lastMousePos = mouseEvent.pos()
635 if self._mousePosition is None:
637 self._update_selection(lastMousePos)
640 self._update_selection(
641 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
644 self._mousePosition = None
646 self._activate_at(self._selectionIndex)
649 @misc_utils.log_exception(_moduleLogger)
650 def keyPressEvent(self, keyEvent):
651 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
652 self._popup_child(QtGui.QCursor.pos())
653 if self._selectionIndex != len(self._filing) - 1:
654 nextSelection = self._selectionIndex + 1
657 self._select_at(nextSelection)
658 self._display.selectAt(self._selectionIndex)
659 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
660 self._popup_child(QtGui.QCursor.pos())
661 if 0 < self._selectionIndex:
662 nextSelection = self._selectionIndex - 1
664 nextSelection = len(self._filing) - 1
665 self._select_at(nextSelection)
666 self._display.selectAt(self._selectionIndex)
667 elif keyEvent.key() in [QtCore.Qt.Key_Space]:
668 self._popup_child(QtGui.QCursor.pos())
669 self._select_at(PieFiling.SELECTION_CENTER)
670 self._display.selectAt(self._selectionIndex)
671 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
672 self._delayPopupTimer.stop()
673 self._popupLocation = None
674 self._activate_at(self._selectionIndex)
676 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
677 self._delayPopupTimer.stop()
678 self._popupLocation = None
679 self._activate_at(PieFiling.SELECTION_NONE)
682 QtGui.QWidget.keyPressEvent(self, keyEvent)
684 @misc_utils.log_exception(_moduleLogger)
685 def resizeEvent(self, resizeEvent):
686 self.setButtonRadius(min(resizeEvent.size().width(), resizeEvent.size().height()) / 2 - 1)
687 QtGui.QWidget.resizeEvent(self, resizeEvent)
689 @misc_utils.log_exception(_moduleLogger)
690 def showEvent(self, showEvent):
691 self._buttonArtist.show(self.palette())
692 self._cachedCenterPosition = self.rect().center()
694 QtGui.QWidget.showEvent(self, showEvent)
696 @misc_utils.log_exception(_moduleLogger)
697 def hideEvent(self, hideEvent):
699 self._select_at(PieFiling.SELECTION_NONE)
700 QtGui.QWidget.hideEvent(self, hideEvent)
702 @misc_utils.log_exception(_moduleLogger)
703 def paintEvent(self, paintEvent):
704 self.setButtonRadius(min(self.rect().width(), self.rect().height()) / 2 - 1)
706 selectionIndex = PieFiling.SELECTION_CENTER
708 selectionIndex = PieFiling.SELECTION_NONE
710 screen = QtGui.QStylePainter(self)
711 screen.setRenderHint(QtGui.QPainter.Antialiasing, True)
712 option = QtGui.QStyleOptionButton()
713 option.initFrom(self)
714 option.state = QtGui.QStyle.State_Sunken if self._poppedUp else QtGui.QStyle.State_Raised
716 screen.drawControl(QtGui.QStyle.CE_PushButton, option)
717 self._buttonArtist.paintPainter(selectionIndex, screen)
719 QtGui.QWidget.paintEvent(self, paintEvent)
722 return iter(self._filing)
725 return len(self._filing)
727 def _popup_child(self, position):
728 self._poppedUp = True
729 self.aboutToShow.emit()
731 self._delayPopupTimer.stop()
732 self._popupLocation = None
734 position = position - QtCore.QPoint(self._filing.outerRadius(), self._filing.outerRadius())
735 self._display.move(position)
740 def _hide_child(self):
741 self._poppedUp = False
742 self.aboutToHide.emit()
746 def _select_at(self, index):
747 self._selectionIndex = index
749 def _update_selection(self, lastMousePos, ignoreOuter = False):
750 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
751 if radius < self._filing.innerRadius():
752 self._select_at(PieFiling.SELECTION_CENTER)
753 elif radius <= self._filing.outerRadius() or ignoreOuter:
754 self._select_at(self.indexAt(lastMousePos))
756 self._select_at(PieFiling.SELECTION_NONE)
758 def _activate_at(self, index):
759 if index == PieFiling.SELECTION_NONE:
762 elif index == PieFiling.SELECTION_CENTER:
763 child = self._filing.center()
765 child = self.itemAt(index)
767 if child.action().isEnabled():
768 child.action().trigger()
769 self.activated.emit(index)
774 class QPieMenu(QtGui.QWidget):
776 activated = QtCore.pyqtSignal(int)
777 highlighted = QtCore.pyqtSignal(int)
778 canceled = QtCore.pyqtSignal()
779 aboutToShow = QtCore.pyqtSignal()
780 aboutToHide = QtCore.pyqtSignal()
782 def __init__(self, parent = None):
783 QtGui.QWidget.__init__(self, parent)
784 self._cachedCenterPosition = self.rect().center()
786 self._filing = PieFiling()
787 self._artist = PieArtist(self._filing)
788 self._selectionIndex = PieFiling.SELECTION_NONE
790 self._mousePosition = ()
791 self.setFocusPolicy(QtCore.Qt.StrongFocus)
793 def popup(self, pos):
794 self._update_selection(pos)
797 def insertItem(self, item, index = -1):
798 self._filing.insertItem(item, index)
801 def removeItemAt(self, index):
802 self._filing.removeItemAt(index)
805 def set_center(self, item):
806 self._filing.set_center(item)
813 def itemAt(self, index):
814 return self._filing.itemAt(index)
816 def indexAt(self, point):
817 return self._filing.indexAt(self._cachedCenterPosition, point)
819 def innerRadius(self):
820 return self._filing.innerRadius()
822 def setInnerRadius(self, radius):
823 self._filing.setInnerRadius(radius)
826 def outerRadius(self):
827 return self._filing.outerRadius()
829 def setOuterRadius(self, radius):
830 self._filing.setOuterRadius(radius)
834 return self._artist.pieSize()
836 @misc_utils.log_exception(_moduleLogger)
837 def mousePressEvent(self, mouseEvent):
838 lastSelection = self._selectionIndex
840 lastMousePos = mouseEvent.pos()
841 self._update_selection(lastMousePos)
842 self._mousePosition = lastMousePos
844 if lastSelection != self._selectionIndex:
845 self.highlighted.emit(self._selectionIndex)
848 @misc_utils.log_exception(_moduleLogger)
849 def mouseMoveEvent(self, mouseEvent):
850 lastSelection = self._selectionIndex
852 lastMousePos = mouseEvent.pos()
853 self._update_selection(lastMousePos)
855 if lastSelection != self._selectionIndex:
856 self.highlighted.emit(self._selectionIndex)
859 @misc_utils.log_exception(_moduleLogger)
860 def mouseReleaseEvent(self, mouseEvent):
861 lastSelection = self._selectionIndex
863 lastMousePos = mouseEvent.pos()
864 self._update_selection(lastMousePos)
865 self._mousePosition = ()
867 self._activate_at(self._selectionIndex)
870 @misc_utils.log_exception(_moduleLogger)
871 def keyPressEvent(self, keyEvent):
872 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
873 if self._selectionIndex != len(self._filing) - 1:
874 nextSelection = self._selectionIndex + 1
877 self._select_at(nextSelection)
879 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
880 if 0 < self._selectionIndex:
881 nextSelection = self._selectionIndex - 1
883 nextSelection = len(self._filing) - 1
884 self._select_at(nextSelection)
886 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
887 self._activate_at(self._selectionIndex)
888 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
889 self._activate_at(PieFiling.SELECTION_NONE)
891 QtGui.QWidget.keyPressEvent(self, keyEvent)
893 @misc_utils.log_exception(_moduleLogger)
894 def showEvent(self, showEvent):
895 self.aboutToShow.emit()
896 self._cachedCenterPosition = self.rect().center()
898 mask = self._artist.show(self.palette())
901 lastMousePos = self.mapFromGlobal(QtGui.QCursor.pos())
902 self._update_selection(lastMousePos)
904 QtGui.QWidget.showEvent(self, showEvent)
906 @misc_utils.log_exception(_moduleLogger)
907 def hideEvent(self, hideEvent):
909 self._selectionIndex = PieFiling.SELECTION_NONE
910 QtGui.QWidget.hideEvent(self, hideEvent)
912 @misc_utils.log_exception(_moduleLogger)
913 def paintEvent(self, paintEvent):
914 canvas = self._artist.paint(self._selectionIndex)
916 screen = QtGui.QPainter(self)
917 screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
919 QtGui.QWidget.paintEvent(self, paintEvent)
922 return iter(self._filing)
925 return len(self._filing)
927 def _select_at(self, index):
928 self._selectionIndex = index
930 def _update_selection(self, lastMousePos):
931 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
932 if radius < self._filing.innerRadius():
933 self._selectionIndex = PieFiling.SELECTION_CENTER
934 elif radius <= self._filing.outerRadius():
935 self._select_at(self.indexAt(lastMousePos))
937 self._selectionIndex = PieFiling.SELECTION_NONE
939 def _activate_at(self, index):
940 if index == PieFiling.SELECTION_NONE:
942 self.aboutToHide.emit()
945 elif index == PieFiling.SELECTION_CENTER:
946 child = self._filing.center()
948 child = self.itemAt(index)
950 if child.isEnabled():
951 child.action().trigger()
952 self.activated.emit(index)
955 self.aboutToHide.emit()
960 PieFiling.NULL_CENTER.setEnabled(False)
967 def _on_about_to_hide(app):
971 if __name__ == "__main__":
972 app = QtGui.QApplication([])
980 singleAction = QtGui.QAction(None)
981 singleAction.setText("Boo")
982 singleItem = QActionPieItem(singleAction)
984 spie.insertItem(singleItem)
988 oneAction = QtGui.QAction(None)
989 oneAction.setText("Chew")
990 oneItem = QActionPieItem(oneAction)
991 twoAction = QtGui.QAction(None)
992 twoAction.setText("Foo")
993 twoItem = QActionPieItem(twoAction)
994 iconTextAction = QtGui.QAction(None)
995 iconTextAction.setText("Icon")
996 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
997 iconTextItem = QActionPieItem(iconTextAction)
999 mpie.insertItem(oneItem)
1000 mpie.insertItem(twoItem)
1001 mpie.insertItem(oneItem)
1002 mpie.insertItem(iconTextItem)
1006 oneAction = QtGui.QAction(None)
1007 oneAction.setText("Chew")
1008 oneAction.triggered.connect(lambda: _print("Chew"))
1009 oneItem = QActionPieItem(oneAction)
1010 twoAction = QtGui.QAction(None)
1011 twoAction.setText("Foo")
1012 twoAction.triggered.connect(lambda: _print("Foo"))
1013 twoItem = QActionPieItem(twoAction)
1014 iconAction = QtGui.QAction(None)
1015 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1016 iconAction.triggered.connect(lambda: _print("Icon"))
1017 iconItem = QActionPieItem(iconAction)
1018 iconTextAction = QtGui.QAction(None)
1019 iconTextAction.setText("Icon")
1020 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1021 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1022 iconTextItem = QActionPieItem(iconTextAction)
1024 mpie.set_center(iconItem)
1025 mpie.insertItem(oneItem)
1026 mpie.insertItem(twoItem)
1027 mpie.insertItem(oneItem)
1028 mpie.insertItem(iconTextItem)
1030 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1031 mpie.canceled.connect(lambda: _print("Canceled"))
1034 oneAction = QtGui.QAction(None)
1035 oneAction.setText("Chew")
1036 oneAction.triggered.connect(lambda: _print("Chew"))
1037 oneItem = QActionPieItem(oneAction)
1038 twoAction = QtGui.QAction(None)
1039 twoAction.setText("Foo")
1040 twoAction.triggered.connect(lambda: _print("Foo"))
1041 twoItem = QActionPieItem(twoAction)
1042 iconAction = QtGui.QAction(None)
1043 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1044 iconAction.triggered.connect(lambda: _print("Icon"))
1045 iconItem = QActionPieItem(iconAction)
1046 iconTextAction = QtGui.QAction(None)
1047 iconTextAction.setText("Icon")
1048 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1049 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1050 iconTextItem = QActionPieItem(iconTextAction)
1051 pieFiling = PieFiling()
1052 pieFiling.set_center(iconItem)
1053 pieFiling.insertItem(oneItem)
1054 pieFiling.insertItem(twoItem)
1055 pieFiling.insertItem(oneItem)
1056 pieFiling.insertItem(iconTextItem)
1057 mpie = QPieDisplay(pieFiling)
1061 oneAction = QtGui.QAction(None)
1062 oneAction.setText("Chew")
1063 oneAction.triggered.connect(lambda: _print("Chew"))
1064 oneItem = QActionPieItem(oneAction)
1065 twoAction = QtGui.QAction(None)
1066 twoAction.setText("Foo")
1067 twoAction.triggered.connect(lambda: _print("Foo"))
1068 twoItem = QActionPieItem(twoAction)
1069 iconAction = QtGui.QAction(None)
1070 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1071 iconAction.triggered.connect(lambda: _print("Icon"))
1072 iconItem = QActionPieItem(iconAction)
1073 iconTextAction = QtGui.QAction(None)
1074 iconTextAction.setText("Icon")
1075 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1076 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1077 iconTextItem = QActionPieItem(iconTextAction)
1078 mpie = QPieButton(iconItem)
1079 mpie.set_center(iconItem)
1080 mpie.insertItem(oneItem)
1081 mpie.insertItem(twoItem)
1082 mpie.insertItem(oneItem)
1083 mpie.insertItem(iconTextItem)
1085 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1086 mpie.canceled.connect(lambda: _print("Canceled"))