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 BACKGROUND_FILL = "fill"
202 BACKGROUND_NOFILL = "no fill"
204 def __init__(self, filing, background = BACKGROUND_FILL):
205 self._filing = filing
207 self._cachedOuterRadius = self._filing.outerRadius()
208 self._cachedInnerRadius = self._filing.innerRadius()
209 canvasSize = self._cachedOuterRadius * 2 + 1
210 self._canvas = QtGui.QPixmap(canvasSize, canvasSize)
212 self._backgroundState = background
216 diameter = self._filing.outerRadius() * 2 + 1
217 return QtCore.QSize(diameter, diameter)
219 def centerSize(self):
220 painter = QtGui.QPainter(self._canvas)
221 text = self._filing.center().action().text()
222 fontMetrics = painter.fontMetrics()
224 textBoundingRect = fontMetrics.boundingRect(text)
226 textBoundingRect = QtCore.QRect()
227 textWidth = textBoundingRect.width()
228 textHeight = textBoundingRect.height()
231 textWidth + self.ICON_SIZE_DEFAULT,
232 max(textHeight, self.ICON_SIZE_DEFAULT),
235 def show(self, palette):
236 self.palette = palette
239 self._cachedOuterRadius != self._filing.outerRadius() or
240 self._cachedInnerRadius != self._filing.innerRadius()
242 self._cachedOuterRadius = self._filing.outerRadius()
243 self._cachedInnerRadius = self._filing.innerRadius()
244 self._canvas = self._canvas.scaled(self.pieSize())
246 if self._mask is None:
247 self._mask = QtGui.QBitmap(self._canvas.size())
248 self._mask.fill(QtCore.Qt.color0)
249 self._generate_mask(self._mask)
250 self._canvas.setMask(self._mask)
256 def paint(self, selectionIndex):
257 painter = QtGui.QPainter(self._canvas)
258 painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
260 self.paintPainter(selectionIndex, painter)
264 def paintPainter(self, selectionIndex, painter):
265 adjustmentRect = painter.viewport().adjusted(0, 0, -1, -1)
267 numChildren = len(self._filing)
269 self._paint_center_background(painter, adjustmentRect, selectionIndex)
270 self._paint_center_foreground(painter, adjustmentRect, selectionIndex)
273 for i in xrange(len(self._filing)):
274 self._paint_slice_background(painter, adjustmentRect, i, selectionIndex)
276 self._paint_center_background(painter, adjustmentRect, selectionIndex)
277 self._paint_center_foreground(painter, adjustmentRect, selectionIndex)
279 for i in xrange(len(self._filing)):
280 self._paint_slice_foreground(painter, adjustmentRect, i, selectionIndex)
282 def _generate_mask(self, mask):
284 Specifies on the mask the shape of the pie menu
286 painter = QtGui.QPainter(mask)
287 painter.setPen(QtCore.Qt.color1)
288 painter.setBrush(QtCore.Qt.color1)
289 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
290 painter.drawRect(mask.rect())
291 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
292 painter.drawEllipse(mask.rect().adjusted(0, 0, -1, -1))
294 raise NotImplementedError(self.DEFAULT_SHAPE)
296 def _paint_slice_background(self, painter, adjustmentRect, i, selectionIndex):
297 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
298 currentWidth = adjustmentRect.width()
299 newWidth = math.sqrt(2) * currentWidth
300 dx = (newWidth - currentWidth) / 2
301 adjustmentRect = adjustmentRect.adjusted(-dx, -dx, dx, dx)
302 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
305 raise NotImplementedError(self.DEFAULT_SHAPE)
307 if self._backgroundState == self.BACKGROUND_NOFILL:
308 painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
309 painter.setPen(self.palette.highlight().color())
311 if i == selectionIndex and self._filing[i].isEnabled():
312 painter.setBrush(self.palette.highlight())
313 painter.setPen(self.palette.highlight().color())
315 painter.setBrush(self.palette.window())
316 painter.setPen(self.palette.window().color())
318 a = self._filing._index_to_angle(i, True)
319 b = self._filing._index_to_angle(i + 1, True)
326 startAngleInDeg = (a * 360 * 16) / _TWOPI
327 sizeInDeg = (size * 360 * 16) / _TWOPI
328 painter.drawPie(adjustmentRect, int(startAngleInDeg), int(sizeInDeg))
330 def _paint_slice_foreground(self, painter, adjustmentRect, i, selectionIndex):
331 child = self._filing[i]
333 a = self._filing._index_to_angle(i, True)
334 b = self._filing._index_to_angle(i + 1, True)
337 middleAngle = (a + b) / 2
338 averageRadius = (self._cachedInnerRadius + self._cachedOuterRadius) / 2
340 sliceX = averageRadius * math.cos(middleAngle)
341 sliceY = - averageRadius * math.sin(middleAngle)
343 piePos = adjustmentRect.center()
347 painter, child.action(), i == selectionIndex, pieX+sliceX, pieY+sliceY
350 def _paint_label(self, painter, action, isSelected, x, y):
352 fontMetrics = painter.fontMetrics()
354 textBoundingRect = fontMetrics.boundingRect(text)
356 textBoundingRect = QtCore.QRect()
357 textWidth = textBoundingRect.width()
358 textHeight = textBoundingRect.height()
360 icon = action.icon().pixmap(
361 QtCore.QSize(self.ICON_SIZE_DEFAULT, self.ICON_SIZE_DEFAULT),
365 iconWidth = icon.width()
366 iconHeight = icon.width()
367 averageWidth = (iconWidth + textWidth)/2
368 if not icon.isNull():
369 iconRect = QtCore.QRect(
376 painter.drawPixmap(iconRect, icon)
380 if action.isEnabled():
381 pen = self.palette.highlightedText()
382 brush = self.palette.highlight()
384 pen = self.palette.mid()
385 brush = self.palette.window()
387 if action.isEnabled():
388 pen = self.palette.windowText()
390 pen = self.palette.mid()
391 brush = self.palette.window()
393 leftX = x - averageWidth + iconWidth
394 topY = y + textHeight/2
395 painter.setPen(pen.color())
396 painter.setBrush(brush)
397 painter.drawText(leftX, topY, text)
399 def _paint_center_background(self, painter, adjustmentRect, selectionIndex):
400 if self._backgroundState == self.BACKGROUND_NOFILL:
402 if len(self._filing) == 0:
403 if self._backgroundState == self.BACKGROUND_NOFILL:
404 painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
406 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
407 painter.setBrush(self.palette.highlight())
409 painter.setBrush(self.palette.window())
410 painter.setPen(self.palette.mid().color())
412 painter.drawRect(adjustmentRect)
414 dark = self.palette.mid().color()
415 light = self.palette.light().color()
416 if self._backgroundState == self.BACKGROUND_NOFILL:
417 background = QtGui.QBrush(QtCore.Qt.transparent)
419 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
420 background = self.palette.highlight().color()
422 background = self.palette.window().color()
424 innerRadius = self._cachedInnerRadius
425 adjustmentCenterPos = adjustmentRect.center()
426 innerRect = QtCore.QRect(
427 adjustmentCenterPos.x() - innerRadius,
428 adjustmentCenterPos.y() - innerRadius,
433 painter.setPen(QtCore.Qt.NoPen)
434 painter.setBrush(background)
435 painter.drawPie(innerRect, 0, 360 * 16)
437 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
439 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
440 painter.setPen(QtGui.QPen(dark, 1))
441 painter.setBrush(QtCore.Qt.NoBrush)
442 painter.drawEllipse(adjustmentRect)
444 raise NotImplementedError(self.DEFAULT_SHAPE)
446 def _paint_center_foreground(self, painter, adjustmentRect, selectionIndex):
447 centerPos = adjustmentRect.center()
456 self._filing.center().action(),
457 selectionIndex == PieFiling.SELECTION_CENTER,
462 class QPieDisplay(QtGui.QWidget):
464 def __init__(self, filing, parent = None, flags = QtCore.Qt.Window):
465 QtGui.QWidget.__init__(self, parent, flags)
466 self._filing = filing
467 self._artist = PieArtist(self._filing)
468 self._selectionIndex = PieFiling.SELECTION_NONE
470 def popup(self, pos):
471 self._update_selection(pos)
475 return self._artist.pieSize()
477 @misc_utils.log_exception(_moduleLogger)
478 def showEvent(self, showEvent):
479 mask = self._artist.show(self.palette())
482 QtGui.QWidget.showEvent(self, showEvent)
484 @misc_utils.log_exception(_moduleLogger)
485 def hideEvent(self, hideEvent):
487 self._selectionIndex = PieFiling.SELECTION_NONE
488 QtGui.QWidget.hideEvent(self, hideEvent)
490 @misc_utils.log_exception(_moduleLogger)
491 def paintEvent(self, paintEvent):
492 canvas = self._artist.paint(self._selectionIndex)
493 offset = (self.size() - canvas.size()) / 2
495 screen = QtGui.QPainter(self)
496 screen.drawPixmap(QtCore.QPoint(offset.width(), offset.height()), canvas)
498 QtGui.QWidget.paintEvent(self, paintEvent)
500 def selectAt(self, index):
501 oldIndex = self._selectionIndex
502 self._selectionIndex = index
507 class QPieButton(QtGui.QWidget):
509 activated = QtCore.pyqtSignal(int)
510 highlighted = QtCore.pyqtSignal(int)
511 canceled = QtCore.pyqtSignal()
512 aboutToShow = QtCore.pyqtSignal()
513 aboutToHide = QtCore.pyqtSignal()
518 def __init__(self, buttonSlice, parent = None, buttonSlices = None):
519 # @bug Artifacts on Maemo 5 due to window 3D effects, find way to disable them for just these?
520 # @bug The pie's are being pushed back on screen on Maemo, leading to coordinate issues
521 QtGui.QWidget.__init__(self, parent)
522 self._cachedCenterPosition = self.rect().center()
524 self._filing = PieFiling()
525 self._display = QPieDisplay(self._filing, None, QtCore.Qt.SplashScreen)
526 self._selectionIndex = PieFiling.SELECTION_NONE
528 self._buttonFiling = PieFiling()
529 self._buttonFiling.set_center(buttonSlice)
530 if buttonSlices is not None:
531 for slice in buttonSlices:
532 self._buttonFiling.insertItem(slice)
533 self._buttonFiling.setOuterRadius(self.BUTTON_RADIUS)
534 self._buttonArtist = PieArtist(self._buttonFiling, PieArtist.BACKGROUND_NOFILL)
535 self._poppedUp = False
537 self._delayPopupTimer = QtCore.QTimer()
538 self._delayPopupTimer.setInterval(self.DELAY)
539 self._delayPopupTimer.setSingleShot(True)
540 self._delayPopupTimer.timeout.connect(self._on_delayed_popup)
541 self._popupLocation = None
543 self._mousePosition = None
544 self.setFocusPolicy(QtCore.Qt.StrongFocus)
547 QtGui.QSizePolicy.MinimumExpanding,
548 QtGui.QSizePolicy.MinimumExpanding,
552 def insertItem(self, item, index = -1):
553 self._filing.insertItem(item, index)
555 def removeItemAt(self, index):
556 self._filing.removeItemAt(index)
558 def set_center(self, item):
559 self._filing.set_center(item)
561 def set_button(self, item):
567 def itemAt(self, index):
568 return self._filing.itemAt(index)
570 def indexAt(self, point):
571 return self._filing.indexAt(self._cachedCenterPosition, point)
573 def innerRadius(self):
574 return self._filing.innerRadius()
576 def setInnerRadius(self, radius):
577 self._filing.setInnerRadius(radius)
579 def outerRadius(self):
580 return self._filing.outerRadius()
582 def setOuterRadius(self, radius):
583 self._filing.setOuterRadius(radius)
585 def buttonRadius(self):
586 return self._buttonFiling.outerRadius()
588 def setButtonRadius(self, radius):
589 self._buttonFiling.setOuterRadius(radius)
590 self._buttonFiling.setInnerRadius(radius / 2)
591 self._buttonArtist.show(self.palette())
593 def minimumSizeHint(self):
594 return self._buttonArtist.centerSize()
596 @misc_utils.log_exception(_moduleLogger)
597 def mousePressEvent(self, mouseEvent):
598 lastSelection = self._selectionIndex
600 lastMousePos = mouseEvent.pos()
601 self._mousePosition = lastMousePos
602 self._update_selection(self._cachedCenterPosition)
604 self.highlighted.emit(self._selectionIndex)
606 self._display.selectAt(self._selectionIndex)
607 self._popupLocation = mouseEvent.globalPos()
608 self._delayPopupTimer.start()
610 @misc_utils.log_exception(_moduleLogger)
611 def _on_delayed_popup(self):
612 assert self._popupLocation is not None, "Widget location abuse"
613 self._popup_child(self._popupLocation)
615 @misc_utils.log_exception(_moduleLogger)
616 def mouseMoveEvent(self, mouseEvent):
617 lastSelection = self._selectionIndex
619 lastMousePos = mouseEvent.pos()
620 if self._mousePosition is None:
622 self._update_selection(lastMousePos)
625 self._update_selection(
626 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
630 if lastSelection != self._selectionIndex:
631 self.highlighted.emit(self._selectionIndex)
632 self._display.selectAt(self._selectionIndex)
634 if self._selectionIndex != PieFiling.SELECTION_CENTER and self._delayPopupTimer.isActive():
635 self._on_delayed_popup()
637 @misc_utils.log_exception(_moduleLogger)
638 def mouseReleaseEvent(self, mouseEvent):
639 self._delayPopupTimer.stop()
640 self._popupLocation = None
642 lastSelection = self._selectionIndex
644 lastMousePos = mouseEvent.pos()
645 if self._mousePosition is None:
647 self._update_selection(lastMousePos)
650 self._update_selection(
651 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
654 self._mousePosition = None
656 self._activate_at(self._selectionIndex)
659 @misc_utils.log_exception(_moduleLogger)
660 def keyPressEvent(self, keyEvent):
661 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
662 self._popup_child(QtGui.QCursor.pos())
663 if self._selectionIndex != len(self._filing) - 1:
664 nextSelection = self._selectionIndex + 1
667 self._select_at(nextSelection)
668 self._display.selectAt(self._selectionIndex)
669 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
670 self._popup_child(QtGui.QCursor.pos())
671 if 0 < self._selectionIndex:
672 nextSelection = self._selectionIndex - 1
674 nextSelection = len(self._filing) - 1
675 self._select_at(nextSelection)
676 self._display.selectAt(self._selectionIndex)
677 elif keyEvent.key() in [QtCore.Qt.Key_Space]:
678 self._popup_child(QtGui.QCursor.pos())
679 self._select_at(PieFiling.SELECTION_CENTER)
680 self._display.selectAt(self._selectionIndex)
681 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
682 self._delayPopupTimer.stop()
683 self._popupLocation = None
684 self._activate_at(self._selectionIndex)
686 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
687 self._delayPopupTimer.stop()
688 self._popupLocation = None
689 self._activate_at(PieFiling.SELECTION_NONE)
692 QtGui.QWidget.keyPressEvent(self, keyEvent)
694 @misc_utils.log_exception(_moduleLogger)
695 def resizeEvent(self, resizeEvent):
696 self.setButtonRadius(min(resizeEvent.size().width(), resizeEvent.size().height()) / 2 - 1)
697 QtGui.QWidget.resizeEvent(self, resizeEvent)
699 @misc_utils.log_exception(_moduleLogger)
700 def showEvent(self, showEvent):
701 self._buttonArtist.show(self.palette())
702 self._cachedCenterPosition = self.rect().center()
704 QtGui.QWidget.showEvent(self, showEvent)
706 @misc_utils.log_exception(_moduleLogger)
707 def hideEvent(self, hideEvent):
709 self._select_at(PieFiling.SELECTION_NONE)
710 QtGui.QWidget.hideEvent(self, hideEvent)
712 @misc_utils.log_exception(_moduleLogger)
713 def paintEvent(self, paintEvent):
714 self.setButtonRadius(min(self.rect().width(), self.rect().height()) / 2 - 1)
716 selectionIndex = PieFiling.SELECTION_CENTER
718 selectionIndex = PieFiling.SELECTION_NONE
720 screen = QtGui.QStylePainter(self)
721 screen.setRenderHint(QtGui.QPainter.Antialiasing, True)
722 option = QtGui.QStyleOptionButton()
723 option.initFrom(self)
724 option.state = QtGui.QStyle.State_Sunken if self._poppedUp else QtGui.QStyle.State_Raised
726 screen.drawControl(QtGui.QStyle.CE_PushButton, option)
727 self._buttonArtist.paintPainter(selectionIndex, screen)
729 QtGui.QWidget.paintEvent(self, paintEvent)
732 return iter(self._filing)
735 return len(self._filing)
737 def _popup_child(self, position):
738 self._poppedUp = True
739 self.aboutToShow.emit()
741 self._delayPopupTimer.stop()
742 self._popupLocation = None
744 position = position - QtCore.QPoint(self._filing.outerRadius(), self._filing.outerRadius())
745 self._display.move(position)
750 def _hide_child(self):
751 self._poppedUp = False
752 self.aboutToHide.emit()
756 def _select_at(self, index):
757 self._selectionIndex = index
759 def _update_selection(self, lastMousePos, ignoreOuter = False):
760 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
761 if radius < self._filing.innerRadius():
762 self._select_at(PieFiling.SELECTION_CENTER)
763 elif radius <= self._filing.outerRadius() or ignoreOuter:
764 self._select_at(self.indexAt(lastMousePos))
766 self._select_at(PieFiling.SELECTION_NONE)
768 def _activate_at(self, index):
769 if index == PieFiling.SELECTION_NONE:
772 elif index == PieFiling.SELECTION_CENTER:
773 child = self._filing.center()
775 child = self.itemAt(index)
777 if child.action().isEnabled():
778 child.action().trigger()
779 self.activated.emit(index)
784 class QPieMenu(QtGui.QWidget):
786 activated = QtCore.pyqtSignal(int)
787 highlighted = QtCore.pyqtSignal(int)
788 canceled = QtCore.pyqtSignal()
789 aboutToShow = QtCore.pyqtSignal()
790 aboutToHide = QtCore.pyqtSignal()
792 def __init__(self, parent = None):
793 QtGui.QWidget.__init__(self, parent)
794 self._cachedCenterPosition = self.rect().center()
796 self._filing = PieFiling()
797 self._artist = PieArtist(self._filing)
798 self._selectionIndex = PieFiling.SELECTION_NONE
800 self._mousePosition = ()
801 self.setFocusPolicy(QtCore.Qt.StrongFocus)
803 def popup(self, pos):
804 self._update_selection(pos)
807 def insertItem(self, item, index = -1):
808 self._filing.insertItem(item, index)
811 def removeItemAt(self, index):
812 self._filing.removeItemAt(index)
815 def set_center(self, item):
816 self._filing.set_center(item)
823 def itemAt(self, index):
824 return self._filing.itemAt(index)
826 def indexAt(self, point):
827 return self._filing.indexAt(self._cachedCenterPosition, point)
829 def innerRadius(self):
830 return self._filing.innerRadius()
832 def setInnerRadius(self, radius):
833 self._filing.setInnerRadius(radius)
836 def outerRadius(self):
837 return self._filing.outerRadius()
839 def setOuterRadius(self, radius):
840 self._filing.setOuterRadius(radius)
844 return self._artist.pieSize()
846 @misc_utils.log_exception(_moduleLogger)
847 def mousePressEvent(self, mouseEvent):
848 lastSelection = self._selectionIndex
850 lastMousePos = mouseEvent.pos()
851 self._update_selection(lastMousePos)
852 self._mousePosition = lastMousePos
854 if lastSelection != self._selectionIndex:
855 self.highlighted.emit(self._selectionIndex)
858 @misc_utils.log_exception(_moduleLogger)
859 def mouseMoveEvent(self, mouseEvent):
860 lastSelection = self._selectionIndex
862 lastMousePos = mouseEvent.pos()
863 self._update_selection(lastMousePos)
865 if lastSelection != self._selectionIndex:
866 self.highlighted.emit(self._selectionIndex)
869 @misc_utils.log_exception(_moduleLogger)
870 def mouseReleaseEvent(self, mouseEvent):
871 lastSelection = self._selectionIndex
873 lastMousePos = mouseEvent.pos()
874 self._update_selection(lastMousePos)
875 self._mousePosition = ()
877 self._activate_at(self._selectionIndex)
880 @misc_utils.log_exception(_moduleLogger)
881 def keyPressEvent(self, keyEvent):
882 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
883 if self._selectionIndex != len(self._filing) - 1:
884 nextSelection = self._selectionIndex + 1
887 self._select_at(nextSelection)
889 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
890 if 0 < self._selectionIndex:
891 nextSelection = self._selectionIndex - 1
893 nextSelection = len(self._filing) - 1
894 self._select_at(nextSelection)
896 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
897 self._activate_at(self._selectionIndex)
898 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
899 self._activate_at(PieFiling.SELECTION_NONE)
901 QtGui.QWidget.keyPressEvent(self, keyEvent)
903 @misc_utils.log_exception(_moduleLogger)
904 def showEvent(self, showEvent):
905 self.aboutToShow.emit()
906 self._cachedCenterPosition = self.rect().center()
908 mask = self._artist.show(self.palette())
911 lastMousePos = self.mapFromGlobal(QtGui.QCursor.pos())
912 self._update_selection(lastMousePos)
914 QtGui.QWidget.showEvent(self, showEvent)
916 @misc_utils.log_exception(_moduleLogger)
917 def hideEvent(self, hideEvent):
919 self._selectionIndex = PieFiling.SELECTION_NONE
920 QtGui.QWidget.hideEvent(self, hideEvent)
922 @misc_utils.log_exception(_moduleLogger)
923 def paintEvent(self, paintEvent):
924 canvas = self._artist.paint(self._selectionIndex)
926 screen = QtGui.QPainter(self)
927 screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
929 QtGui.QWidget.paintEvent(self, paintEvent)
932 return iter(self._filing)
935 return len(self._filing)
937 def _select_at(self, index):
938 self._selectionIndex = index
940 def _update_selection(self, lastMousePos):
941 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
942 if radius < self._filing.innerRadius():
943 self._selectionIndex = PieFiling.SELECTION_CENTER
944 elif radius <= self._filing.outerRadius():
945 self._select_at(self.indexAt(lastMousePos))
947 self._selectionIndex = PieFiling.SELECTION_NONE
949 def _activate_at(self, index):
950 if index == PieFiling.SELECTION_NONE:
952 self.aboutToHide.emit()
955 elif index == PieFiling.SELECTION_CENTER:
956 child = self._filing.center()
958 child = self.itemAt(index)
960 if child.isEnabled():
961 child.action().trigger()
962 self.activated.emit(index)
965 self.aboutToHide.emit()
970 PieFiling.NULL_CENTER.setEnabled(False)
977 def _on_about_to_hide(app):
981 if __name__ == "__main__":
982 app = QtGui.QApplication([])
990 singleAction = QtGui.QAction(None)
991 singleAction.setText("Boo")
992 singleItem = QActionPieItem(singleAction)
994 spie.insertItem(singleItem)
998 oneAction = QtGui.QAction(None)
999 oneAction.setText("Chew")
1000 oneItem = QActionPieItem(oneAction)
1001 twoAction = QtGui.QAction(None)
1002 twoAction.setText("Foo")
1003 twoItem = QActionPieItem(twoAction)
1004 iconTextAction = QtGui.QAction(None)
1005 iconTextAction.setText("Icon")
1006 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1007 iconTextItem = QActionPieItem(iconTextAction)
1009 mpie.insertItem(oneItem)
1010 mpie.insertItem(twoItem)
1011 mpie.insertItem(oneItem)
1012 mpie.insertItem(iconTextItem)
1016 oneAction = QtGui.QAction(None)
1017 oneAction.setText("Chew")
1018 oneAction.triggered.connect(lambda: _print("Chew"))
1019 oneItem = QActionPieItem(oneAction)
1020 twoAction = QtGui.QAction(None)
1021 twoAction.setText("Foo")
1022 twoAction.triggered.connect(lambda: _print("Foo"))
1023 twoItem = QActionPieItem(twoAction)
1024 iconAction = QtGui.QAction(None)
1025 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1026 iconAction.triggered.connect(lambda: _print("Icon"))
1027 iconItem = QActionPieItem(iconAction)
1028 iconTextAction = QtGui.QAction(None)
1029 iconTextAction.setText("Icon")
1030 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1031 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1032 iconTextItem = QActionPieItem(iconTextAction)
1034 mpie.set_center(iconItem)
1035 mpie.insertItem(oneItem)
1036 mpie.insertItem(twoItem)
1037 mpie.insertItem(oneItem)
1038 mpie.insertItem(iconTextItem)
1040 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1041 mpie.canceled.connect(lambda: _print("Canceled"))
1044 oneAction = QtGui.QAction(None)
1045 oneAction.setText("Chew")
1046 oneAction.triggered.connect(lambda: _print("Chew"))
1047 oneItem = QActionPieItem(oneAction)
1048 twoAction = QtGui.QAction(None)
1049 twoAction.setText("Foo")
1050 twoAction.triggered.connect(lambda: _print("Foo"))
1051 twoItem = QActionPieItem(twoAction)
1052 iconAction = QtGui.QAction(None)
1053 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1054 iconAction.triggered.connect(lambda: _print("Icon"))
1055 iconItem = QActionPieItem(iconAction)
1056 iconTextAction = QtGui.QAction(None)
1057 iconTextAction.setText("Icon")
1058 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1059 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1060 iconTextItem = QActionPieItem(iconTextAction)
1061 pieFiling = PieFiling()
1062 pieFiling.set_center(iconItem)
1063 pieFiling.insertItem(oneItem)
1064 pieFiling.insertItem(twoItem)
1065 pieFiling.insertItem(oneItem)
1066 pieFiling.insertItem(iconTextItem)
1067 mpie = QPieDisplay(pieFiling)
1071 oneAction = QtGui.QAction(None)
1072 oneAction.setText("Chew")
1073 oneAction.triggered.connect(lambda: _print("Chew"))
1074 oneItem = QActionPieItem(oneAction)
1075 twoAction = QtGui.QAction(None)
1076 twoAction.setText("Foo")
1077 twoAction.triggered.connect(lambda: _print("Foo"))
1078 twoItem = QActionPieItem(twoAction)
1079 iconAction = QtGui.QAction(None)
1080 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1081 iconAction.triggered.connect(lambda: _print("Icon"))
1082 iconItem = QActionPieItem(iconAction)
1083 iconTextAction = QtGui.QAction(None)
1084 iconTextAction.setText("Icon")
1085 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1086 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1087 iconTextItem = QActionPieItem(iconTextAction)
1088 mpie = QPieButton(iconItem)
1089 mpie.set_center(iconItem)
1090 mpie.insertItem(oneItem)
1091 mpie.insertItem(twoItem)
1092 mpie.insertItem(oneItem)
1093 mpie.insertItem(iconTextItem)
1095 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1096 mpie.canceled.connect(lambda: _print("Canceled"))