6 from PyQt4 import QtGui
7 from PyQt4 import QtCore
10 from util import misc as misc_utils
12 class misc_utils(object):
15 def log_exception(logger):
22 _moduleLogger = logging.getLogger(__name__)
28 def _radius_at(center, pos):
33 radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
37 def _angle_at(center, pos):
42 radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
43 angle = math.acos(xDelta / radius)
45 angle = _TWOPI - angle
50 class QActionPieItem(object):
52 def __init__(self, action, weight = 1):
59 def setWeight(self, weight):
65 def setEnabled(self, enabled = True):
66 self._action.setEnabled(enabled)
69 return self._action.isEnabled()
72 class PieFiling(object):
74 INNER_RADIUS_DEFAULT = 64
75 OUTER_RADIUS_DEFAULT = 192
80 NULL_CENTER = QActionPieItem(QtGui.QAction(None))
83 self._innerRadius = self.INNER_RADIUS_DEFAULT
84 self._outerRadius = self.OUTER_RADIUS_DEFAULT
86 self._center = self.NULL_CENTER
88 self._cacheIndexToAngle = {}
89 self._cacheTotalWeight = 0
91 def insertItem(self, item, index = -1):
92 self._children.insert(index, item)
93 self._invalidate_cache()
95 def removeItemAt(self, index):
96 item = self._children.pop(index)
97 self._invalidate_cache()
99 def set_center(self, item):
101 item = self.NULL_CENTER
108 del self._children[:]
109 self._center = self.NULL_CENTER
110 self._invalidate_cache()
112 def itemAt(self, index):
113 return self._children[index]
115 def indexAt(self, center, point):
116 return self._angle_to_index(_angle_at(center, point))
118 def innerRadius(self):
119 return self._innerRadius
121 def setInnerRadius(self, radius):
122 self._innerRadius = radius
124 def outerRadius(self):
125 return self._outerRadius
127 def setOuterRadius(self, radius):
128 self._outerRadius = radius
131 return iter(self._children)
134 return len(self._children)
136 def __getitem__(self, index):
137 return self._children[index]
139 def _invalidate_cache(self):
140 self._cacheIndexToAngle.clear()
141 self._cacheTotalWeight = sum(child.weight() for child in self._children)
142 if self._cacheTotalWeight == 0:
143 self._cacheTotalWeight = 1
145 def _index_to_angle(self, index, isShifted):
146 key = index, isShifted
147 if key in self._cacheIndexToAngle:
148 return self._cacheIndexToAngle[key]
149 index = index % len(self._children)
151 baseAngle = _TWOPI / self._cacheTotalWeight
156 angle -= (self._children[0].weight() * baseAngle) / 2
158 angle -= baseAngle / 2
162 for i, child in enumerate(self._children):
165 angle += child.weight() * baseAngle
166 while _TWOPI < angle:
169 self._cacheIndexToAngle[key] = angle
172 def _angle_to_index(self, angle):
173 numChildren = len(self._children)
175 return self.SELECTION_CENTER
177 baseAngle = _TWOPI / self._cacheTotalWeight
179 iterAngle = math.pi / 2 - (self.itemAt(0).weight() * baseAngle) / 2
183 oldIterAngle = iterAngle
184 for index, child in enumerate(self._children):
185 iterAngle += child.weight() * baseAngle
186 if oldIterAngle < angle and angle <= iterAngle:
187 return index - 1 if index != 0 else numChildren - 1
188 elif oldIterAngle < (angle + _TWOPI) and (angle + _TWOPI <= iterAngle):
189 return index - 1 if index != 0 else numChildren - 1
190 oldIterAngle = iterAngle
193 class PieArtist(object):
195 ICON_SIZE_DEFAULT = 48
197 SHAPE_CIRCLE = "circle"
198 SHAPE_SQUARE = "square"
199 DEFAULT_SHAPE = SHAPE_SQUARE
201 def __init__(self, filing):
202 self._filing = filing
204 self._cachedOuterRadius = self._filing.outerRadius()
205 self._cachedInnerRadius = self._filing.innerRadius()
206 canvasSize = self._cachedOuterRadius * 2 + 1
207 self._canvas = QtGui.QPixmap(canvasSize, canvasSize)
212 diameter = self._filing.outerRadius() * 2 + 1
213 return QtCore.QSize(diameter, diameter)
215 def centerSize(self):
216 painter = QtGui.QPainter(self._canvas)
217 text = self._filing.center().action().text()
218 fontMetrics = painter.fontMetrics()
220 textBoundingRect = fontMetrics.boundingRect(text)
222 textBoundingRect = QtCore.QRect()
223 textWidth = textBoundingRect.width()
224 textHeight = textBoundingRect.height()
227 textWidth + self.ICON_SIZE_DEFAULT,
228 max(textHeight, self.ICON_SIZE_DEFAULT),
231 def show(self, palette):
232 self.palette = palette
235 self._cachedOuterRadius != self._filing.outerRadius() or
236 self._cachedInnerRadius != self._filing.innerRadius()
238 self._cachedOuterRadius = self._filing.outerRadius()
239 self._cachedInnerRadius = self._filing.innerRadius()
240 self._canvas = self._canvas.scaled(self.pieSize())
242 if self._mask is None:
243 self._mask = QtGui.QBitmap(self._canvas.size())
244 self._mask.fill(QtCore.Qt.color0)
245 self._generate_mask(self._mask)
246 self._canvas.setMask(self._mask)
252 def paint(self, selectionIndex):
253 painter = QtGui.QPainter(self._canvas)
254 painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
256 adjustmentRect = self._canvas.rect().adjusted(0, 0, -1, -1)
258 numChildren = len(self._filing)
260 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
261 painter.setBrush(self.palette.highlight())
263 painter.setBrush(self.palette.window())
264 painter.setPen(self.palette.mid().color())
266 painter.drawRect(self._canvas.rect())
267 self._paint_center_foreground(painter, selectionIndex)
269 elif numChildren == 1:
270 if selectionIndex == 0 and self._filing[0].isEnabled():
271 painter.setBrush(self.palette.highlight())
273 painter.setBrush(self.palette.window())
275 painter.fillRect(self._canvas.rect(), painter.brush())
277 for i in xrange(len(self._filing)):
278 self._paint_slice_background(painter, adjustmentRect, i, selectionIndex)
280 self._paint_center_background(painter, adjustmentRect, selectionIndex)
281 self._paint_center_foreground(painter, selectionIndex)
283 for i in xrange(len(self._filing)):
284 self._paint_slice_foreground(painter, i, selectionIndex)
288 def _generate_mask(self, mask):
290 Specifies on the mask the shape of the pie menu
292 painter = QtGui.QPainter(mask)
293 painter.setPen(QtCore.Qt.color1)
294 painter.setBrush(QtCore.Qt.color1)
295 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
296 painter.drawRect(mask.rect())
297 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
298 painter.drawEllipse(mask.rect().adjusted(0, 0, -1, -1))
300 raise NotImplementedError(self.DEFAULT_SHAPE)
302 def _paint_slice_background(self, painter, adjustmentRect, i, selectionIndex):
303 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
304 currentWidth = adjustmentRect.width()
305 newWidth = math.sqrt(2) * currentWidth
306 dx = (newWidth - currentWidth) / 2
307 adjustmentRect = adjustmentRect.adjusted(-dx, -dx, dx, dx)
308 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
311 raise NotImplementedError(self.DEFAULT_SHAPE)
313 if i == selectionIndex and self._filing[i].isEnabled():
314 painter.setBrush(self.palette.highlight())
315 painter.setPen(self.palette.highlight().color())
317 painter.setBrush(self.palette.window())
318 painter.setPen(self.palette.window().color())
320 a = self._filing._index_to_angle(i, True)
321 b = self._filing._index_to_angle(i + 1, True)
328 startAngleInDeg = (a * 360 * 16) / _TWOPI
329 sizeInDeg = (size * 360 * 16) / _TWOPI
330 painter.drawPie(adjustmentRect, int(startAngleInDeg), int(sizeInDeg))
332 def _paint_slice_foreground(self, painter, i, selectionIndex):
333 child = self._filing[i]
335 a = self._filing._index_to_angle(i, True)
336 b = self._filing._index_to_angle(i + 1, True)
339 middleAngle = (a + b) / 2
340 averageRadius = (self._cachedInnerRadius + self._cachedOuterRadius) / 2
342 sliceX = averageRadius * math.cos(middleAngle)
343 sliceY = - averageRadius * math.sin(middleAngle)
345 piePos = self._canvas.rect().center()
349 painter, child.action(), i == selectionIndex, pieX+sliceX, pieY+sliceY
352 def _paint_label(self, painter, action, isSelected, x, y):
354 fontMetrics = painter.fontMetrics()
356 textBoundingRect = fontMetrics.boundingRect(text)
358 textBoundingRect = QtCore.QRect()
359 textWidth = textBoundingRect.width()
360 textHeight = textBoundingRect.height()
362 icon = action.icon().pixmap(
363 QtCore.QSize(self.ICON_SIZE_DEFAULT, self.ICON_SIZE_DEFAULT),
367 iconWidth = icon.width()
368 iconHeight = icon.width()
369 averageWidth = (iconWidth + textWidth)/2
370 if not icon.isNull():
371 iconRect = QtCore.QRect(
378 painter.drawPixmap(iconRect, icon)
382 if action.isEnabled():
383 pen = self.palette.highlightedText()
384 brush = self.palette.highlight()
386 pen = self.palette.mid()
387 brush = self.palette.window()
389 if action.isEnabled():
390 pen = self.palette.windowText()
392 pen = self.palette.mid()
393 brush = self.palette.window()
395 leftX = x - averageWidth + iconWidth
396 topY = y + textHeight/2
397 painter.setPen(pen.color())
398 painter.setBrush(brush)
399 painter.drawText(leftX, topY, text)
401 def _paint_center_background(self, painter, adjustmentRect, selectionIndex):
402 dark = self.palette.mid().color()
403 light = self.palette.light().color()
404 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
405 background = self.palette.highlight().color()
407 background = self.palette.window().color()
409 innerRadius = self._cachedInnerRadius
410 adjustmentCenterPos = adjustmentRect.center()
411 innerRect = QtCore.QRect(
412 adjustmentCenterPos.x() - innerRadius,
413 adjustmentCenterPos.y() - innerRadius,
418 painter.setPen(QtCore.Qt.NoPen)
419 painter.setBrush(background)
420 painter.drawPie(innerRect, 0, 360 * 16)
422 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
424 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
425 painter.setPen(QtGui.QPen(dark, 1))
426 painter.setBrush(QtCore.Qt.NoBrush)
427 painter.drawEllipse(adjustmentRect)
429 raise NotImplementedError(self.DEFAULT_SHAPE)
431 def _paint_center_foreground(self, painter, selectionIndex):
432 centerPos = self._canvas.rect().center()
441 self._filing.center().action(),
442 selectionIndex == PieFiling.SELECTION_CENTER,
447 class QPieDisplay(QtGui.QWidget):
449 def __init__(self, filing, parent = None, flags = QtCore.Qt.Window):
450 QtGui.QWidget.__init__(self, parent, flags)
451 self._filing = filing
452 self._artist = PieArtist(self._filing)
453 self._selectionIndex = PieFiling.SELECTION_NONE
455 def popup(self, pos):
456 self._update_selection(pos)
460 return self._artist.pieSize()
462 @misc_utils.log_exception(_moduleLogger)
463 def showEvent(self, showEvent):
464 mask = self._artist.show(self.palette())
467 QtGui.QWidget.showEvent(self, showEvent)
469 @misc_utils.log_exception(_moduleLogger)
470 def hideEvent(self, hideEvent):
472 self._selectionIndex = PieFiling.SELECTION_NONE
473 QtGui.QWidget.hideEvent(self, hideEvent)
475 @misc_utils.log_exception(_moduleLogger)
476 def paintEvent(self, paintEvent):
477 canvas = self._artist.paint(self._selectionIndex)
478 offset = (self.size() - canvas.size()) / 2
480 screen = QtGui.QPainter(self)
481 screen.drawPixmap(QtCore.QPoint(offset.width(), offset.height()), canvas)
483 QtGui.QWidget.paintEvent(self, paintEvent)
485 def selectAt(self, index):
486 oldIndex = self._selectionIndex
487 self._selectionIndex = index
492 class QPieButton(QtGui.QWidget):
494 activated = QtCore.pyqtSignal(int)
495 highlighted = QtCore.pyqtSignal(int)
496 canceled = QtCore.pyqtSignal()
497 aboutToShow = QtCore.pyqtSignal()
498 aboutToHide = QtCore.pyqtSignal()
503 def __init__(self, buttonSlice, parent = None, buttonSlices = None):
504 # @bug Artifacts on Maemo 5 due to window 3D effects, find way to disable them for just these?
505 # @bug The pie's are being pushed back on screen on Maemo, leading to coordinate issues
506 QtGui.QWidget.__init__(self, parent)
507 self._cachedCenterPosition = self.rect().center()
509 self._filing = PieFiling()
510 self._display = QPieDisplay(self._filing, None, QtCore.Qt.SplashScreen)
511 self._selectionIndex = PieFiling.SELECTION_NONE
513 self._buttonFiling = PieFiling()
514 self._buttonFiling.set_center(buttonSlice)
515 if buttonSlices is not None:
516 for slice in buttonSlices:
517 self._buttonFiling.insertItem(slice)
518 self._buttonFiling.setOuterRadius(self.BUTTON_RADIUS)
519 self._buttonArtist = PieArtist(self._buttonFiling)
520 self._poppedUp = False
522 self._delayPopupTimer = QtCore.QTimer()
523 self._delayPopupTimer.setInterval(self.DELAY)
524 self._delayPopupTimer.setSingleShot(True)
525 self._delayPopupTimer.timeout.connect(self._on_delayed_popup)
526 self._popupLocation = None
528 self._mousePosition = None
529 self.setFocusPolicy(QtCore.Qt.StrongFocus)
532 QtGui.QSizePolicy.MinimumExpanding,
533 QtGui.QSizePolicy.MinimumExpanding,
537 def insertItem(self, item, index = -1):
538 self._filing.insertItem(item, index)
540 def removeItemAt(self, index):
541 self._filing.removeItemAt(index)
543 def set_center(self, item):
544 self._filing.set_center(item)
546 def set_button(self, item):
552 def itemAt(self, index):
553 return self._filing.itemAt(index)
555 def indexAt(self, point):
556 return self._filing.indexAt(self._cachedCenterPosition, point)
558 def innerRadius(self):
559 return self._filing.innerRadius()
561 def setInnerRadius(self, radius):
562 self._filing.setInnerRadius(radius)
564 def outerRadius(self):
565 return self._filing.outerRadius()
567 def setOuterRadius(self, radius):
568 self._filing.setOuterRadius(radius)
570 def buttonRadius(self):
571 return self._buttonFiling.outerRadius()
573 def setButtonRadius(self, radius):
574 self._buttonFiling.setOuterRadius(radius)
575 self._buttonFiling.setInnerRadius(radius / 2)
576 self._buttonArtist.show(self.palette())
578 def minimumSizeHint(self):
579 return self._buttonArtist.centerSize()
581 @misc_utils.log_exception(_moduleLogger)
582 def mousePressEvent(self, mouseEvent):
583 lastSelection = self._selectionIndex
585 lastMousePos = mouseEvent.pos()
586 self._mousePosition = lastMousePos
587 self._update_selection(self._cachedCenterPosition)
589 self.highlighted.emit(self._selectionIndex)
591 self._display.selectAt(self._selectionIndex)
592 self._popupLocation = mouseEvent.globalPos()
593 self._delayPopupTimer.start()
595 @misc_utils.log_exception(_moduleLogger)
596 def _on_delayed_popup(self):
597 assert self._popupLocation is not None, "Widget location abuse"
598 self._popup_child(self._popupLocation)
600 @misc_utils.log_exception(_moduleLogger)
601 def mouseMoveEvent(self, mouseEvent):
602 lastSelection = self._selectionIndex
604 lastMousePos = mouseEvent.pos()
605 if self._mousePosition is None:
607 self._update_selection(lastMousePos)
610 self._update_selection(
611 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
615 if lastSelection != self._selectionIndex:
616 self.highlighted.emit(self._selectionIndex)
617 self._display.selectAt(self._selectionIndex)
619 if self._selectionIndex != PieFiling.SELECTION_CENTER and self._delayPopupTimer.isActive():
620 self._on_delayed_popup()
622 @misc_utils.log_exception(_moduleLogger)
623 def mouseReleaseEvent(self, mouseEvent):
624 self._delayPopupTimer.stop()
625 self._popupLocation = None
627 lastSelection = self._selectionIndex
629 lastMousePos = mouseEvent.pos()
630 if self._mousePosition is None:
632 self._update_selection(lastMousePos)
635 self._update_selection(
636 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
639 self._mousePosition = None
641 self._activate_at(self._selectionIndex)
644 @misc_utils.log_exception(_moduleLogger)
645 def keyPressEvent(self, keyEvent):
646 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
647 self._popup_child(QtGui.QCursor.pos())
648 if self._selectionIndex != len(self._filing) - 1:
649 nextSelection = self._selectionIndex + 1
652 self._select_at(nextSelection)
653 self._display.selectAt(self._selectionIndex)
654 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
655 self._popup_child(QtGui.QCursor.pos())
656 if 0 < self._selectionIndex:
657 nextSelection = self._selectionIndex - 1
659 nextSelection = len(self._filing) - 1
660 self._select_at(nextSelection)
661 self._display.selectAt(self._selectionIndex)
662 elif keyEvent.key() in [QtCore.Qt.Key_Space]:
663 self._popup_child(QtGui.QCursor.pos())
664 self._select_at(PieFiling.SELECTION_CENTER)
665 self._display.selectAt(self._selectionIndex)
666 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
667 self._delayPopupTimer.stop()
668 self._popupLocation = None
669 self._activate_at(self._selectionIndex)
671 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
672 self._delayPopupTimer.stop()
673 self._popupLocation = None
674 self._activate_at(PieFiling.SELECTION_NONE)
677 QtGui.QWidget.keyPressEvent(self, keyEvent)
679 @misc_utils.log_exception(_moduleLogger)
680 def resizeEvent(self, resizeEvent):
681 self.setButtonRadius(min(resizeEvent.size().width(), resizeEvent.size().height()) / 2 - 1)
682 QtGui.QWidget.resizeEvent(self, resizeEvent)
684 @misc_utils.log_exception(_moduleLogger)
685 def showEvent(self, showEvent):
686 self._buttonArtist.show(self.palette())
687 self._cachedCenterPosition = self.rect().center()
689 QtGui.QWidget.showEvent(self, showEvent)
691 @misc_utils.log_exception(_moduleLogger)
692 def hideEvent(self, hideEvent):
694 self._select_at(PieFiling.SELECTION_NONE)
695 QtGui.QWidget.hideEvent(self, hideEvent)
697 @misc_utils.log_exception(_moduleLogger)
698 def paintEvent(self, paintEvent):
699 self.setButtonRadius(min(self.rect().width(), self.rect().height()) / 2 - 1)
701 canvas = self._buttonArtist.paint(PieFiling.SELECTION_CENTER)
703 canvas = self._buttonArtist.paint(PieFiling.SELECTION_NONE)
704 offset = (self.size() - canvas.size()) / 2
706 screen = QtGui.QPainter(self)
707 screen.drawPixmap(QtCore.QPoint(offset.width(), offset.height()), canvas)
709 QtGui.QWidget.paintEvent(self, paintEvent)
712 return iter(self._filing)
715 return len(self._filing)
717 def _popup_child(self, position):
718 self._poppedUp = True
719 self.aboutToShow.emit()
721 self._delayPopupTimer.stop()
722 self._popupLocation = None
724 position = position - QtCore.QPoint(self._filing.outerRadius(), self._filing.outerRadius())
725 self._display.move(position)
730 def _hide_child(self):
731 self._poppedUp = False
732 self.aboutToHide.emit()
736 def _select_at(self, index):
737 self._selectionIndex = index
739 def _update_selection(self, lastMousePos, ignoreOuter = False):
740 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
741 if radius < self._filing.innerRadius():
742 self._select_at(PieFiling.SELECTION_CENTER)
743 elif radius <= self._filing.outerRadius() or ignoreOuter:
744 self._select_at(self.indexAt(lastMousePos))
746 self._select_at(PieFiling.SELECTION_NONE)
748 def _activate_at(self, index):
749 if index == PieFiling.SELECTION_NONE:
752 elif index == PieFiling.SELECTION_CENTER:
753 child = self._filing.center()
755 child = self.itemAt(index)
757 if child.action().isEnabled():
758 child.action().trigger()
759 self.activated.emit(index)
764 class QPieMenu(QtGui.QWidget):
766 activated = QtCore.pyqtSignal(int)
767 highlighted = QtCore.pyqtSignal(int)
768 canceled = QtCore.pyqtSignal()
769 aboutToShow = QtCore.pyqtSignal()
770 aboutToHide = QtCore.pyqtSignal()
772 def __init__(self, parent = None):
773 QtGui.QWidget.__init__(self, parent)
774 self._cachedCenterPosition = self.rect().center()
776 self._filing = PieFiling()
777 self._artist = PieArtist(self._filing)
778 self._selectionIndex = PieFiling.SELECTION_NONE
780 self._mousePosition = ()
781 self.setFocusPolicy(QtCore.Qt.StrongFocus)
783 def popup(self, pos):
784 self._update_selection(pos)
787 def insertItem(self, item, index = -1):
788 self._filing.insertItem(item, index)
791 def removeItemAt(self, index):
792 self._filing.removeItemAt(index)
795 def set_center(self, item):
796 self._filing.set_center(item)
803 def itemAt(self, index):
804 return self._filing.itemAt(index)
806 def indexAt(self, point):
807 return self._filing.indexAt(self._cachedCenterPosition, point)
809 def innerRadius(self):
810 return self._filing.innerRadius()
812 def setInnerRadius(self, radius):
813 self._filing.setInnerRadius(radius)
816 def outerRadius(self):
817 return self._filing.outerRadius()
819 def setOuterRadius(self, radius):
820 self._filing.setOuterRadius(radius)
824 return self._artist.pieSize()
826 @misc_utils.log_exception(_moduleLogger)
827 def mousePressEvent(self, mouseEvent):
828 lastSelection = self._selectionIndex
830 lastMousePos = mouseEvent.pos()
831 self._update_selection(lastMousePos)
832 self._mousePosition = lastMousePos
834 if lastSelection != self._selectionIndex:
835 self.highlighted.emit(self._selectionIndex)
838 @misc_utils.log_exception(_moduleLogger)
839 def mouseMoveEvent(self, mouseEvent):
840 lastSelection = self._selectionIndex
842 lastMousePos = mouseEvent.pos()
843 self._update_selection(lastMousePos)
845 if lastSelection != self._selectionIndex:
846 self.highlighted.emit(self._selectionIndex)
849 @misc_utils.log_exception(_moduleLogger)
850 def mouseReleaseEvent(self, mouseEvent):
851 lastSelection = self._selectionIndex
853 lastMousePos = mouseEvent.pos()
854 self._update_selection(lastMousePos)
855 self._mousePosition = ()
857 self._activate_at(self._selectionIndex)
860 @misc_utils.log_exception(_moduleLogger)
861 def keyPressEvent(self, keyEvent):
862 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
863 if self._selectionIndex != len(self._filing) - 1:
864 nextSelection = self._selectionIndex + 1
867 self._select_at(nextSelection)
869 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
870 if 0 < self._selectionIndex:
871 nextSelection = self._selectionIndex - 1
873 nextSelection = len(self._filing) - 1
874 self._select_at(nextSelection)
876 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
877 self._activate_at(self._selectionIndex)
878 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
879 self._activate_at(PieFiling.SELECTION_NONE)
881 QtGui.QWidget.keyPressEvent(self, keyEvent)
883 @misc_utils.log_exception(_moduleLogger)
884 def showEvent(self, showEvent):
885 self.aboutToShow.emit()
886 self._cachedCenterPosition = self.rect().center()
888 mask = self._artist.show(self.palette())
891 lastMousePos = self.mapFromGlobal(QtGui.QCursor.pos())
892 self._update_selection(lastMousePos)
894 QtGui.QWidget.showEvent(self, showEvent)
896 @misc_utils.log_exception(_moduleLogger)
897 def hideEvent(self, hideEvent):
899 self._selectionIndex = PieFiling.SELECTION_NONE
900 QtGui.QWidget.hideEvent(self, hideEvent)
902 @misc_utils.log_exception(_moduleLogger)
903 def paintEvent(self, paintEvent):
904 canvas = self._artist.paint(self._selectionIndex)
906 screen = QtGui.QPainter(self)
907 screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
909 QtGui.QWidget.paintEvent(self, paintEvent)
912 return iter(self._filing)
915 return len(self._filing)
917 def _select_at(self, index):
918 self._selectionIndex = index
920 def _update_selection(self, lastMousePos):
921 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
922 if radius < self._filing.innerRadius():
923 self._selectionIndex = PieFiling.SELECTION_CENTER
924 elif radius <= self._filing.outerRadius():
925 self._select_at(self.indexAt(lastMousePos))
927 self._selectionIndex = PieFiling.SELECTION_NONE
929 def _activate_at(self, index):
930 if index == PieFiling.SELECTION_NONE:
932 self.aboutToHide.emit()
935 elif index == PieFiling.SELECTION_CENTER:
936 child = self._filing.center()
938 child = self.itemAt(index)
940 if child.isEnabled():
941 child.action().trigger()
942 self.activated.emit(index)
945 self.aboutToHide.emit()
950 PieFiling.NULL_CENTER.setEnabled(False)
957 def _on_about_to_hide(app):
961 if __name__ == "__main__":
962 app = QtGui.QApplication([])
970 singleAction = QtGui.QAction(None)
971 singleAction.setText("Boo")
972 singleItem = QActionPieItem(singleAction)
974 spie.insertItem(singleItem)
978 oneAction = QtGui.QAction(None)
979 oneAction.setText("Chew")
980 oneItem = QActionPieItem(oneAction)
981 twoAction = QtGui.QAction(None)
982 twoAction.setText("Foo")
983 twoItem = QActionPieItem(twoAction)
984 iconTextAction = QtGui.QAction(None)
985 iconTextAction.setText("Icon")
986 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
987 iconTextItem = QActionPieItem(iconTextAction)
989 mpie.insertItem(oneItem)
990 mpie.insertItem(twoItem)
991 mpie.insertItem(oneItem)
992 mpie.insertItem(iconTextItem)
996 oneAction = QtGui.QAction(None)
997 oneAction.setText("Chew")
998 oneAction.triggered.connect(lambda: _print("Chew"))
999 oneItem = QActionPieItem(oneAction)
1000 twoAction = QtGui.QAction(None)
1001 twoAction.setText("Foo")
1002 twoAction.triggered.connect(lambda: _print("Foo"))
1003 twoItem = QActionPieItem(twoAction)
1004 iconAction = QtGui.QAction(None)
1005 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1006 iconAction.triggered.connect(lambda: _print("Icon"))
1007 iconItem = QActionPieItem(iconAction)
1008 iconTextAction = QtGui.QAction(None)
1009 iconTextAction.setText("Icon")
1010 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1011 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1012 iconTextItem = QActionPieItem(iconTextAction)
1014 mpie.set_center(iconItem)
1015 mpie.insertItem(oneItem)
1016 mpie.insertItem(twoItem)
1017 mpie.insertItem(oneItem)
1018 mpie.insertItem(iconTextItem)
1020 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1021 mpie.canceled.connect(lambda: _print("Canceled"))
1024 oneAction = QtGui.QAction(None)
1025 oneAction.setText("Chew")
1026 oneAction.triggered.connect(lambda: _print("Chew"))
1027 oneItem = QActionPieItem(oneAction)
1028 twoAction = QtGui.QAction(None)
1029 twoAction.setText("Foo")
1030 twoAction.triggered.connect(lambda: _print("Foo"))
1031 twoItem = QActionPieItem(twoAction)
1032 iconAction = QtGui.QAction(None)
1033 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1034 iconAction.triggered.connect(lambda: _print("Icon"))
1035 iconItem = QActionPieItem(iconAction)
1036 iconTextAction = QtGui.QAction(None)
1037 iconTextAction.setText("Icon")
1038 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1039 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1040 iconTextItem = QActionPieItem(iconTextAction)
1041 pieFiling = PieFiling()
1042 pieFiling.set_center(iconItem)
1043 pieFiling.insertItem(oneItem)
1044 pieFiling.insertItem(twoItem)
1045 pieFiling.insertItem(oneItem)
1046 pieFiling.insertItem(iconTextItem)
1047 mpie = QPieDisplay(pieFiling)
1051 oneAction = QtGui.QAction(None)
1052 oneAction.setText("Chew")
1053 oneAction.triggered.connect(lambda: _print("Chew"))
1054 oneItem = QActionPieItem(oneAction)
1055 twoAction = QtGui.QAction(None)
1056 twoAction.setText("Foo")
1057 twoAction.triggered.connect(lambda: _print("Foo"))
1058 twoItem = QActionPieItem(twoAction)
1059 iconAction = QtGui.QAction(None)
1060 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1061 iconAction.triggered.connect(lambda: _print("Icon"))
1062 iconItem = QActionPieItem(iconAction)
1063 iconTextAction = QtGui.QAction(None)
1064 iconTextAction.setText("Icon")
1065 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1066 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1067 iconTextItem = QActionPieItem(iconTextAction)
1068 mpie = QPieButton(iconItem)
1069 mpie.set_center(iconItem)
1070 mpie.insertItem(oneItem)
1071 mpie.insertItem(twoItem)
1072 mpie.insertItem(oneItem)
1073 mpie.insertItem(iconTextItem)
1075 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1076 mpie.canceled.connect(lambda: _print("Canceled"))