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 self._paint_center_background(painter, adjustmentRect, selectionIndex)
261 self._paint_center_foreground(painter, selectionIndex)
263 elif numChildren == 1:
264 if selectionIndex == 0 and self._filing[0].isEnabled():
265 painter.setBrush(self.palette.highlight())
267 painter.setBrush(self.palette.window())
269 painter.fillRect(self._canvas.rect(), painter.brush())
271 for i in xrange(len(self._filing)):
272 self._paint_slice_background(painter, adjustmentRect, i, selectionIndex)
274 self._paint_center_background(painter, adjustmentRect, selectionIndex)
275 self._paint_center_foreground(painter, selectionIndex)
277 for i in xrange(len(self._filing)):
278 self._paint_slice_foreground(painter, 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 i == selectionIndex and self._filing[i].isEnabled():
308 painter.setBrush(self.palette.highlight())
309 painter.setPen(self.palette.highlight().color())
311 painter.setBrush(self.palette.window())
312 painter.setPen(self.palette.window().color())
314 a = self._filing._index_to_angle(i, True)
315 b = self._filing._index_to_angle(i + 1, True)
322 startAngleInDeg = (a * 360 * 16) / _TWOPI
323 sizeInDeg = (size * 360 * 16) / _TWOPI
324 painter.drawPie(adjustmentRect, int(startAngleInDeg), int(sizeInDeg))
326 def _paint_slice_foreground(self, painter, i, selectionIndex):
327 child = self._filing[i]
329 a = self._filing._index_to_angle(i, True)
330 b = self._filing._index_to_angle(i + 1, True)
333 middleAngle = (a + b) / 2
334 averageRadius = (self._cachedInnerRadius + self._cachedOuterRadius) / 2
336 sliceX = averageRadius * math.cos(middleAngle)
337 sliceY = - averageRadius * math.sin(middleAngle)
339 piePos = self._canvas.rect().center()
343 painter, child.action(), i == selectionIndex, pieX+sliceX, pieY+sliceY
346 def _paint_label(self, painter, action, isSelected, x, y):
348 fontMetrics = painter.fontMetrics()
350 textBoundingRect = fontMetrics.boundingRect(text)
352 textBoundingRect = QtCore.QRect()
353 textWidth = textBoundingRect.width()
354 textHeight = textBoundingRect.height()
356 icon = action.icon().pixmap(
357 QtCore.QSize(self.ICON_SIZE_DEFAULT, self.ICON_SIZE_DEFAULT),
361 iconWidth = icon.width()
362 iconHeight = icon.width()
363 averageWidth = (iconWidth + textWidth)/2
364 if not icon.isNull():
365 iconRect = QtCore.QRect(
372 painter.drawPixmap(iconRect, icon)
376 if action.isEnabled():
377 pen = self.palette.highlightedText()
378 brush = self.palette.highlight()
380 pen = self.palette.mid()
381 brush = self.palette.window()
383 if action.isEnabled():
384 pen = self.palette.windowText()
386 pen = self.palette.mid()
387 brush = self.palette.window()
389 leftX = x - averageWidth + iconWidth
390 topY = y + textHeight/2
391 painter.setPen(pen.color())
392 painter.setBrush(brush)
393 painter.drawText(leftX, topY, text)
395 def _paint_center_background(self, painter, adjustmentRect, selectionIndex):
396 if len(self._filing) == 0:
397 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
398 painter.setBrush(self.palette.highlight())
400 painter.setBrush(self.palette.window())
401 painter.setPen(self.palette.mid().color())
403 painter.drawRect(self._canvas.rect())
405 dark = self.palette.mid().color()
406 light = self.palette.light().color()
407 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
408 background = self.palette.highlight().color()
410 background = self.palette.window().color()
412 innerRadius = self._cachedInnerRadius
413 adjustmentCenterPos = adjustmentRect.center()
414 innerRect = QtCore.QRect(
415 adjustmentCenterPos.x() - innerRadius,
416 adjustmentCenterPos.y() - innerRadius,
421 painter.setPen(QtCore.Qt.NoPen)
422 painter.setBrush(background)
423 painter.drawPie(innerRect, 0, 360 * 16)
425 if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
427 elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
428 painter.setPen(QtGui.QPen(dark, 1))
429 painter.setBrush(QtCore.Qt.NoBrush)
430 painter.drawEllipse(adjustmentRect)
432 raise NotImplementedError(self.DEFAULT_SHAPE)
434 def _paint_center_foreground(self, painter, selectionIndex):
435 centerPos = self._canvas.rect().center()
444 self._filing.center().action(),
445 selectionIndex == PieFiling.SELECTION_CENTER,
450 class QPieDisplay(QtGui.QWidget):
452 def __init__(self, filing, parent = None, flags = QtCore.Qt.Window):
453 QtGui.QWidget.__init__(self, parent, flags)
454 self._filing = filing
455 self._artist = PieArtist(self._filing)
456 self._selectionIndex = PieFiling.SELECTION_NONE
458 def popup(self, pos):
459 self._update_selection(pos)
463 return self._artist.pieSize()
465 @misc_utils.log_exception(_moduleLogger)
466 def showEvent(self, showEvent):
467 mask = self._artist.show(self.palette())
470 QtGui.QWidget.showEvent(self, showEvent)
472 @misc_utils.log_exception(_moduleLogger)
473 def hideEvent(self, hideEvent):
475 self._selectionIndex = PieFiling.SELECTION_NONE
476 QtGui.QWidget.hideEvent(self, hideEvent)
478 @misc_utils.log_exception(_moduleLogger)
479 def paintEvent(self, paintEvent):
480 canvas = self._artist.paint(self._selectionIndex)
481 offset = (self.size() - canvas.size()) / 2
483 screen = QtGui.QPainter(self)
484 screen.drawPixmap(QtCore.QPoint(offset.width(), offset.height()), canvas)
486 QtGui.QWidget.paintEvent(self, paintEvent)
488 def selectAt(self, index):
489 oldIndex = self._selectionIndex
490 self._selectionIndex = index
495 class QPieButton(QtGui.QWidget):
497 activated = QtCore.pyqtSignal(int)
498 highlighted = QtCore.pyqtSignal(int)
499 canceled = QtCore.pyqtSignal()
500 aboutToShow = QtCore.pyqtSignal()
501 aboutToHide = QtCore.pyqtSignal()
506 def __init__(self, buttonSlice, parent = None, buttonSlices = None):
507 # @bug Artifacts on Maemo 5 due to window 3D effects, find way to disable them for just these?
508 # @bug The pie's are being pushed back on screen on Maemo, leading to coordinate issues
509 QtGui.QWidget.__init__(self, parent)
510 self._cachedCenterPosition = self.rect().center()
512 self._filing = PieFiling()
513 self._display = QPieDisplay(self._filing, None, QtCore.Qt.SplashScreen)
514 self._selectionIndex = PieFiling.SELECTION_NONE
516 self._buttonFiling = PieFiling()
517 self._buttonFiling.set_center(buttonSlice)
518 if buttonSlices is not None:
519 for slice in buttonSlices:
520 self._buttonFiling.insertItem(slice)
521 self._buttonFiling.setOuterRadius(self.BUTTON_RADIUS)
522 self._buttonArtist = PieArtist(self._buttonFiling)
523 self._poppedUp = False
525 self._delayPopupTimer = QtCore.QTimer()
526 self._delayPopupTimer.setInterval(self.DELAY)
527 self._delayPopupTimer.setSingleShot(True)
528 self._delayPopupTimer.timeout.connect(self._on_delayed_popup)
529 self._popupLocation = None
531 self._mousePosition = None
532 self.setFocusPolicy(QtCore.Qt.StrongFocus)
535 QtGui.QSizePolicy.MinimumExpanding,
536 QtGui.QSizePolicy.MinimumExpanding,
540 def insertItem(self, item, index = -1):
541 self._filing.insertItem(item, index)
543 def removeItemAt(self, index):
544 self._filing.removeItemAt(index)
546 def set_center(self, item):
547 self._filing.set_center(item)
549 def set_button(self, item):
555 def itemAt(self, index):
556 return self._filing.itemAt(index)
558 def indexAt(self, point):
559 return self._filing.indexAt(self._cachedCenterPosition, point)
561 def innerRadius(self):
562 return self._filing.innerRadius()
564 def setInnerRadius(self, radius):
565 self._filing.setInnerRadius(radius)
567 def outerRadius(self):
568 return self._filing.outerRadius()
570 def setOuterRadius(self, radius):
571 self._filing.setOuterRadius(radius)
573 def buttonRadius(self):
574 return self._buttonFiling.outerRadius()
576 def setButtonRadius(self, radius):
577 self._buttonFiling.setOuterRadius(radius)
578 self._buttonFiling.setInnerRadius(radius / 2)
579 self._buttonArtist.show(self.palette())
581 def minimumSizeHint(self):
582 return self._buttonArtist.centerSize()
584 @misc_utils.log_exception(_moduleLogger)
585 def mousePressEvent(self, mouseEvent):
586 lastSelection = self._selectionIndex
588 lastMousePos = mouseEvent.pos()
589 self._mousePosition = lastMousePos
590 self._update_selection(self._cachedCenterPosition)
592 self.highlighted.emit(self._selectionIndex)
594 self._display.selectAt(self._selectionIndex)
595 self._popupLocation = mouseEvent.globalPos()
596 self._delayPopupTimer.start()
598 @misc_utils.log_exception(_moduleLogger)
599 def _on_delayed_popup(self):
600 assert self._popupLocation is not None, "Widget location abuse"
601 self._popup_child(self._popupLocation)
603 @misc_utils.log_exception(_moduleLogger)
604 def mouseMoveEvent(self, mouseEvent):
605 lastSelection = self._selectionIndex
607 lastMousePos = mouseEvent.pos()
608 if self._mousePosition is None:
610 self._update_selection(lastMousePos)
613 self._update_selection(
614 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
618 if lastSelection != self._selectionIndex:
619 self.highlighted.emit(self._selectionIndex)
620 self._display.selectAt(self._selectionIndex)
622 if self._selectionIndex != PieFiling.SELECTION_CENTER and self._delayPopupTimer.isActive():
623 self._on_delayed_popup()
625 @misc_utils.log_exception(_moduleLogger)
626 def mouseReleaseEvent(self, mouseEvent):
627 self._delayPopupTimer.stop()
628 self._popupLocation = None
630 lastSelection = self._selectionIndex
632 lastMousePos = mouseEvent.pos()
633 if self._mousePosition is None:
635 self._update_selection(lastMousePos)
638 self._update_selection(
639 self._cachedCenterPosition + (lastMousePos - self._mousePosition),
642 self._mousePosition = None
644 self._activate_at(self._selectionIndex)
647 @misc_utils.log_exception(_moduleLogger)
648 def keyPressEvent(self, keyEvent):
649 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
650 self._popup_child(QtGui.QCursor.pos())
651 if self._selectionIndex != len(self._filing) - 1:
652 nextSelection = self._selectionIndex + 1
655 self._select_at(nextSelection)
656 self._display.selectAt(self._selectionIndex)
657 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
658 self._popup_child(QtGui.QCursor.pos())
659 if 0 < self._selectionIndex:
660 nextSelection = self._selectionIndex - 1
662 nextSelection = len(self._filing) - 1
663 self._select_at(nextSelection)
664 self._display.selectAt(self._selectionIndex)
665 elif keyEvent.key() in [QtCore.Qt.Key_Space]:
666 self._popup_child(QtGui.QCursor.pos())
667 self._select_at(PieFiling.SELECTION_CENTER)
668 self._display.selectAt(self._selectionIndex)
669 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
670 self._delayPopupTimer.stop()
671 self._popupLocation = None
672 self._activate_at(self._selectionIndex)
674 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
675 self._delayPopupTimer.stop()
676 self._popupLocation = None
677 self._activate_at(PieFiling.SELECTION_NONE)
680 QtGui.QWidget.keyPressEvent(self, keyEvent)
682 @misc_utils.log_exception(_moduleLogger)
683 def resizeEvent(self, resizeEvent):
684 self.setButtonRadius(min(resizeEvent.size().width(), resizeEvent.size().height()) / 2 - 1)
685 QtGui.QWidget.resizeEvent(self, resizeEvent)
687 @misc_utils.log_exception(_moduleLogger)
688 def showEvent(self, showEvent):
689 self._buttonArtist.show(self.palette())
690 self._cachedCenterPosition = self.rect().center()
692 QtGui.QWidget.showEvent(self, showEvent)
694 @misc_utils.log_exception(_moduleLogger)
695 def hideEvent(self, hideEvent):
697 self._select_at(PieFiling.SELECTION_NONE)
698 QtGui.QWidget.hideEvent(self, hideEvent)
700 @misc_utils.log_exception(_moduleLogger)
701 def paintEvent(self, paintEvent):
702 self.setButtonRadius(min(self.rect().width(), self.rect().height()) / 2 - 1)
704 canvas = self._buttonArtist.paint(PieFiling.SELECTION_CENTER)
706 canvas = self._buttonArtist.paint(PieFiling.SELECTION_NONE)
707 offset = (self.size() - canvas.size()) / 2
709 screen = QtGui.QPainter(self)
710 screen.drawPixmap(QtCore.QPoint(offset.width(), offset.height()), canvas)
712 QtGui.QWidget.paintEvent(self, paintEvent)
715 return iter(self._filing)
718 return len(self._filing)
720 def _popup_child(self, position):
721 self._poppedUp = True
722 self.aboutToShow.emit()
724 self._delayPopupTimer.stop()
725 self._popupLocation = None
727 position = position - QtCore.QPoint(self._filing.outerRadius(), self._filing.outerRadius())
728 self._display.move(position)
733 def _hide_child(self):
734 self._poppedUp = False
735 self.aboutToHide.emit()
739 def _select_at(self, index):
740 self._selectionIndex = index
742 def _update_selection(self, lastMousePos, ignoreOuter = False):
743 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
744 if radius < self._filing.innerRadius():
745 self._select_at(PieFiling.SELECTION_CENTER)
746 elif radius <= self._filing.outerRadius() or ignoreOuter:
747 self._select_at(self.indexAt(lastMousePos))
749 self._select_at(PieFiling.SELECTION_NONE)
751 def _activate_at(self, index):
752 if index == PieFiling.SELECTION_NONE:
755 elif index == PieFiling.SELECTION_CENTER:
756 child = self._filing.center()
758 child = self.itemAt(index)
760 if child.action().isEnabled():
761 child.action().trigger()
762 self.activated.emit(index)
767 class QPieMenu(QtGui.QWidget):
769 activated = QtCore.pyqtSignal(int)
770 highlighted = QtCore.pyqtSignal(int)
771 canceled = QtCore.pyqtSignal()
772 aboutToShow = QtCore.pyqtSignal()
773 aboutToHide = QtCore.pyqtSignal()
775 def __init__(self, parent = None):
776 QtGui.QWidget.__init__(self, parent)
777 self._cachedCenterPosition = self.rect().center()
779 self._filing = PieFiling()
780 self._artist = PieArtist(self._filing)
781 self._selectionIndex = PieFiling.SELECTION_NONE
783 self._mousePosition = ()
784 self.setFocusPolicy(QtCore.Qt.StrongFocus)
786 def popup(self, pos):
787 self._update_selection(pos)
790 def insertItem(self, item, index = -1):
791 self._filing.insertItem(item, index)
794 def removeItemAt(self, index):
795 self._filing.removeItemAt(index)
798 def set_center(self, item):
799 self._filing.set_center(item)
806 def itemAt(self, index):
807 return self._filing.itemAt(index)
809 def indexAt(self, point):
810 return self._filing.indexAt(self._cachedCenterPosition, point)
812 def innerRadius(self):
813 return self._filing.innerRadius()
815 def setInnerRadius(self, radius):
816 self._filing.setInnerRadius(radius)
819 def outerRadius(self):
820 return self._filing.outerRadius()
822 def setOuterRadius(self, radius):
823 self._filing.setOuterRadius(radius)
827 return self._artist.pieSize()
829 @misc_utils.log_exception(_moduleLogger)
830 def mousePressEvent(self, mouseEvent):
831 lastSelection = self._selectionIndex
833 lastMousePos = mouseEvent.pos()
834 self._update_selection(lastMousePos)
835 self._mousePosition = lastMousePos
837 if lastSelection != self._selectionIndex:
838 self.highlighted.emit(self._selectionIndex)
841 @misc_utils.log_exception(_moduleLogger)
842 def mouseMoveEvent(self, mouseEvent):
843 lastSelection = self._selectionIndex
845 lastMousePos = mouseEvent.pos()
846 self._update_selection(lastMousePos)
848 if lastSelection != self._selectionIndex:
849 self.highlighted.emit(self._selectionIndex)
852 @misc_utils.log_exception(_moduleLogger)
853 def mouseReleaseEvent(self, mouseEvent):
854 lastSelection = self._selectionIndex
856 lastMousePos = mouseEvent.pos()
857 self._update_selection(lastMousePos)
858 self._mousePosition = ()
860 self._activate_at(self._selectionIndex)
863 @misc_utils.log_exception(_moduleLogger)
864 def keyPressEvent(self, keyEvent):
865 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
866 if self._selectionIndex != len(self._filing) - 1:
867 nextSelection = self._selectionIndex + 1
870 self._select_at(nextSelection)
872 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
873 if 0 < self._selectionIndex:
874 nextSelection = self._selectionIndex - 1
876 nextSelection = len(self._filing) - 1
877 self._select_at(nextSelection)
879 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
880 self._activate_at(self._selectionIndex)
881 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
882 self._activate_at(PieFiling.SELECTION_NONE)
884 QtGui.QWidget.keyPressEvent(self, keyEvent)
886 @misc_utils.log_exception(_moduleLogger)
887 def showEvent(self, showEvent):
888 self.aboutToShow.emit()
889 self._cachedCenterPosition = self.rect().center()
891 mask = self._artist.show(self.palette())
894 lastMousePos = self.mapFromGlobal(QtGui.QCursor.pos())
895 self._update_selection(lastMousePos)
897 QtGui.QWidget.showEvent(self, showEvent)
899 @misc_utils.log_exception(_moduleLogger)
900 def hideEvent(self, hideEvent):
902 self._selectionIndex = PieFiling.SELECTION_NONE
903 QtGui.QWidget.hideEvent(self, hideEvent)
905 @misc_utils.log_exception(_moduleLogger)
906 def paintEvent(self, paintEvent):
907 canvas = self._artist.paint(self._selectionIndex)
909 screen = QtGui.QPainter(self)
910 screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
912 QtGui.QWidget.paintEvent(self, paintEvent)
915 return iter(self._filing)
918 return len(self._filing)
920 def _select_at(self, index):
921 self._selectionIndex = index
923 def _update_selection(self, lastMousePos):
924 radius = _radius_at(self._cachedCenterPosition, lastMousePos)
925 if radius < self._filing.innerRadius():
926 self._selectionIndex = PieFiling.SELECTION_CENTER
927 elif radius <= self._filing.outerRadius():
928 self._select_at(self.indexAt(lastMousePos))
930 self._selectionIndex = PieFiling.SELECTION_NONE
932 def _activate_at(self, index):
933 if index == PieFiling.SELECTION_NONE:
935 self.aboutToHide.emit()
938 elif index == PieFiling.SELECTION_CENTER:
939 child = self._filing.center()
941 child = self.itemAt(index)
943 if child.isEnabled():
944 child.action().trigger()
945 self.activated.emit(index)
948 self.aboutToHide.emit()
953 PieFiling.NULL_CENTER.setEnabled(False)
960 def _on_about_to_hide(app):
964 if __name__ == "__main__":
965 app = QtGui.QApplication([])
973 singleAction = QtGui.QAction(None)
974 singleAction.setText("Boo")
975 singleItem = QActionPieItem(singleAction)
977 spie.insertItem(singleItem)
981 oneAction = QtGui.QAction(None)
982 oneAction.setText("Chew")
983 oneItem = QActionPieItem(oneAction)
984 twoAction = QtGui.QAction(None)
985 twoAction.setText("Foo")
986 twoItem = QActionPieItem(twoAction)
987 iconTextAction = QtGui.QAction(None)
988 iconTextAction.setText("Icon")
989 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
990 iconTextItem = QActionPieItem(iconTextAction)
992 mpie.insertItem(oneItem)
993 mpie.insertItem(twoItem)
994 mpie.insertItem(oneItem)
995 mpie.insertItem(iconTextItem)
999 oneAction = QtGui.QAction(None)
1000 oneAction.setText("Chew")
1001 oneAction.triggered.connect(lambda: _print("Chew"))
1002 oneItem = QActionPieItem(oneAction)
1003 twoAction = QtGui.QAction(None)
1004 twoAction.setText("Foo")
1005 twoAction.triggered.connect(lambda: _print("Foo"))
1006 twoItem = QActionPieItem(twoAction)
1007 iconAction = QtGui.QAction(None)
1008 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1009 iconAction.triggered.connect(lambda: _print("Icon"))
1010 iconItem = QActionPieItem(iconAction)
1011 iconTextAction = QtGui.QAction(None)
1012 iconTextAction.setText("Icon")
1013 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1014 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1015 iconTextItem = QActionPieItem(iconTextAction)
1017 mpie.set_center(iconItem)
1018 mpie.insertItem(oneItem)
1019 mpie.insertItem(twoItem)
1020 mpie.insertItem(oneItem)
1021 mpie.insertItem(iconTextItem)
1023 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1024 mpie.canceled.connect(lambda: _print("Canceled"))
1027 oneAction = QtGui.QAction(None)
1028 oneAction.setText("Chew")
1029 oneAction.triggered.connect(lambda: _print("Chew"))
1030 oneItem = QActionPieItem(oneAction)
1031 twoAction = QtGui.QAction(None)
1032 twoAction.setText("Foo")
1033 twoAction.triggered.connect(lambda: _print("Foo"))
1034 twoItem = QActionPieItem(twoAction)
1035 iconAction = QtGui.QAction(None)
1036 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1037 iconAction.triggered.connect(lambda: _print("Icon"))
1038 iconItem = QActionPieItem(iconAction)
1039 iconTextAction = QtGui.QAction(None)
1040 iconTextAction.setText("Icon")
1041 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1042 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1043 iconTextItem = QActionPieItem(iconTextAction)
1044 pieFiling = PieFiling()
1045 pieFiling.set_center(iconItem)
1046 pieFiling.insertItem(oneItem)
1047 pieFiling.insertItem(twoItem)
1048 pieFiling.insertItem(oneItem)
1049 pieFiling.insertItem(iconTextItem)
1050 mpie = QPieDisplay(pieFiling)
1054 oneAction = QtGui.QAction(None)
1055 oneAction.setText("Chew")
1056 oneAction.triggered.connect(lambda: _print("Chew"))
1057 oneItem = QActionPieItem(oneAction)
1058 twoAction = QtGui.QAction(None)
1059 twoAction.setText("Foo")
1060 twoAction.triggered.connect(lambda: _print("Foo"))
1061 twoItem = QActionPieItem(twoAction)
1062 iconAction = QtGui.QAction(None)
1063 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
1064 iconAction.triggered.connect(lambda: _print("Icon"))
1065 iconItem = QActionPieItem(iconAction)
1066 iconTextAction = QtGui.QAction(None)
1067 iconTextAction.setText("Icon")
1068 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
1069 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
1070 iconTextItem = QActionPieItem(iconTextAction)
1071 mpie = QPieButton(iconItem)
1072 mpie.set_center(iconItem)
1073 mpie.insertItem(oneItem)
1074 mpie.insertItem(twoItem)
1075 mpie.insertItem(oneItem)
1076 mpie.insertItem(iconTextItem)
1078 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
1079 mpie.canceled.connect(lambda: _print("Canceled"))