1eda74f10b82497988a8e6f6740de755060ada09
[ejpi] / src / libraries / qtpie.py
1 #!/usr/bin/env python
2
3 import math
4
5 from PyQt4 import QtGui
6 from PyQt4 import QtCore
7
8
9 def _radius_at(center, pos):
10         xDelta = pos.x() - center.x()
11         yDelta = pos.y() - center.y()
12
13         radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
14         return radius
15
16
17 def _angle_at(center, pos):
18         xDelta = pos.x() - center.x()
19         yDelta = pos.y() - center.y()
20
21         radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
22         angle = math.acos(xDelta / radius)
23         if 0 <= yDelta:
24                 angle = 2*math.pi - angle
25
26         return angle
27
28
29 class QActionPieItem(object):
30
31         def __init__(self, action, weight = 1):
32                 self._action = action
33                 self._weight = weight
34
35         def action(self):
36                 return self._action
37
38         def setWeight(self, weight):
39                 self._weight = weight
40
41         def weight(self):
42                 return self._weight
43
44         def setEnabled(self, enabled = True):
45                 self._action.setEnabled(enabled)
46
47         def isEnabled(self):
48                 return self._action.isEnabled()
49
50
51 class PieFiling(object):
52
53         INNER_RADIUS_DEFAULT = 32
54         OUTER_RADIUS_DEFAULT = 128
55
56         SELECTION_CENTER = -1
57         SELECTION_NONE = -2
58
59         NULL_CENTER = QActionPieItem(QtGui.QAction(None))
60
61         def __init__(self):
62                 self._innerRadius = self.INNER_RADIUS_DEFAULT
63                 self._outerRadius = self.OUTER_RADIUS_DEFAULT
64                 self._children = []
65                 self._center = self.NULL_CENTER
66
67         def insertItem(self, item, index = -1):
68                 self._children.insert(index, item)
69
70         def removeItemAt(self, index):
71                 item = self._children.pop(index)
72
73         def set_center(self, item):
74                 if item is None:
75                         item = self.NULL_CENTER
76                 self._center = item
77
78         def center(self):
79                 return self._center
80
81         def clear(self):
82                 del self._children[:]
83
84         def itemAt(self, index):
85                 return self._children[index]
86
87         def indexAt(self, center, point):
88                 return self._angle_to_index(_angle_at(center, point))
89
90         def innerRadius(self):
91                 return self._innerRadius
92
93         def setInnerRadius(self, radius):
94                 self._innerRadius = radius
95
96         def outerRadius(self):
97                 return self._outerRadius
98
99         def setOuterRadius(self, radius):
100                 self._outerRadius = radius
101
102         def __iter__(self):
103                 return iter(self._children)
104
105         def __len__(self):
106                 return len(self._children)
107
108         def __getitem__(self, index):
109                 return self._children[index]
110
111         def _index_to_angle(self, index, isShifted):
112                 index = index % len(self._children)
113
114                 totalWeight = sum(child.weight() for child in self._children)
115                 if totalWeight == 0:
116                         totalWeight = 1
117                 baseAngle = (2 * math.pi) / totalWeight
118
119                 angle = math.pi / 2
120                 if isShifted:
121                         if self._children:
122                                 angle -= (self._children[0].weight() * baseAngle) / 2
123                         else:
124                                 angle -= baseAngle / 2
125                 while angle < 0:
126                         angle += 2*math.pi
127
128                 for i, child in enumerate(self._children):
129                         if index < i:
130                                 break
131                         angle += child.weight() * baseAngle
132                 while (2*math.pi) < angle:
133                         angle -= 2*math.pi
134
135                 return angle
136
137         def _angle_to_index(self, angle):
138                 numChildren = len(self._children)
139                 if numChildren == 0:
140                         return self.SELECTION_CENTER
141
142                 totalWeight = sum(child.weight() for child in self._children)
143                 if totalWeight == 0:
144                         totalWeight = 1
145                 baseAngle = (2 * math.pi) / totalWeight
146
147                 iterAngle = math.pi / 2 - (self.itemAt(0).weight() * baseAngle) / 2
148                 while iterAngle < 0:
149                         iterAngle += 2 * math.pi
150
151                 oldIterAngle = iterAngle
152                 for index, child in enumerate(self._children):
153                         iterAngle += child.weight() * baseAngle
154                         if oldIterAngle < angle and angle <= iterAngle:
155                                 return index - 1 if index != 0 else numChildren - 1
156                         elif oldIterAngle < (angle + 2*math.pi) and (angle + 2*math.pi <= iterAngle):
157                                 return index - 1 if index != 0 else numChildren - 1
158                         oldIterAngle = iterAngle
159
160
161 class PieArtist(object):
162
163         ICON_SIZE_DEFAULT = 32
164
165         def __init__(self, filing):
166                 self._filing = filing
167
168                 self._cachedOuterRadius = self._filing.outerRadius()
169                 self._cachedInnerRadius = self._filing.innerRadius()
170                 canvasSize = self._cachedOuterRadius * 2 + 1
171                 self._canvas = QtGui.QPixmap(canvasSize, canvasSize)
172                 self._mask = None
173                 self.palette = None
174
175         def pieSize(self):
176                 diameter = self._filing.outerRadius() * 2 + 1
177                 return QtCore.QSize(diameter, diameter)
178
179         def centerSize(self):
180                 painter = QtGui.QPainter(self._canvas)
181                 text = self._filing.center().action().text()
182                 fontMetrics = painter.fontMetrics()
183                 if text:
184                         textBoundingRect = fontMetrics.boundingRect(text)
185                 else:
186                         textBoundingRect = QtCore.QRect()
187                 textWidth = textBoundingRect.width()
188                 textHeight = textBoundingRect.height()
189
190                 return QtCore.QSize(
191                         textWidth + self.ICON_SIZE_DEFAULT,
192                         max(textHeight, self.ICON_SIZE_DEFAULT),
193                 )
194
195         def show(self, palette):
196                 self.palette = palette
197
198                 if (
199                         self._cachedOuterRadius != self._filing.outerRadius() or
200                         self._cachedInnerRadius != self._filing.innerRadius()
201                 ):
202                         self._cachedOuterRadius = self._filing.outerRadius()
203                         self._cachedInnerRadius = self._filing.innerRadius()
204                         self._canvas = self._canvas.scaled(self.pieSize())
205
206                 if self._mask is None:
207                         self._mask = QtGui.QBitmap(self._canvas.size())
208                         self._mask.fill(QtCore.Qt.color0)
209                         self._generate_mask(self._mask)
210                         self._canvas.setMask(self._mask)
211                 return self._mask
212
213         def hide(self):
214                 self.palette = None
215
216         def paint(self, selectionIndex):
217                 painter = QtGui.QPainter(self._canvas)
218                 painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
219
220                 adjustmentRect = self._canvas.rect().adjusted(0, 0, -1, -1)
221
222                 numChildren = len(self._filing)
223                 if numChildren == 0:
224                         if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
225                                 painter.setBrush(self.palette.highlight())
226                         else:
227                                 painter.setBrush(self.palette.background())
228
229                         painter.fillRect(self._canvas.rect(), painter.brush())
230                         self._paint_center_foreground(painter, selectionIndex)
231                         return self._canvas
232                 elif numChildren == 1:
233                         if selectionIndex == 0 and self._filing[0].isEnabled():
234                                 painter.setBrush(self.palette.highlight())
235                         else:
236                                 painter.setBrush(self.palette.background())
237
238                         painter.fillRect(self._canvas.rect(), painter.brush())
239                 else:
240                         for i in xrange(len(self._filing)):
241                                 self._paint_slice_background(painter, adjustmentRect, i, selectionIndex)
242
243                 self._paint_center_background(painter, adjustmentRect, selectionIndex)
244                 self._paint_center_foreground(painter, selectionIndex)
245
246                 for i in xrange(len(self._filing)):
247                         self._paint_slice_foreground(painter, i, selectionIndex)
248
249                 return self._canvas
250
251         def _generate_mask(self, mask):
252                 """
253                 Specifies on the mask the shape of the pie menu
254                 """
255                 painter = QtGui.QPainter(mask)
256                 painter.setPen(QtCore.Qt.color1)
257                 painter.setBrush(QtCore.Qt.color1)
258                 painter.drawEllipse(mask.rect().adjusted(0, 0, -1, -1))
259
260         def _paint_slice_background(self, painter, adjustmentRect, i, selectionIndex):
261                 if i == selectionIndex and self._filing[i].isEnabled():
262                         painter.setBrush(self.palette.highlight())
263                 else:
264                         painter.setBrush(self.palette.background())
265                 painter.setPen(self.palette.mid().color())
266
267                 a = self._filing._index_to_angle(i, True)
268                 b = self._filing._index_to_angle(i + 1, True)
269                 if b < a:
270                         b += 2*math.pi
271                 size = b - a
272                 if size < 0:
273                         size += 2*math.pi
274
275                 startAngleInDeg = (a * 360 * 16) / (2*math.pi)
276                 sizeInDeg = (size * 360 * 16) / (2*math.pi)
277                 painter.drawPie(adjustmentRect, int(startAngleInDeg), int(sizeInDeg))
278
279         def _paint_slice_foreground(self, painter, i, selectionIndex):
280                 child = self._filing[i]
281
282                 a = self._filing._index_to_angle(i, True)
283                 b = self._filing._index_to_angle(i + 1, True)
284                 if b < a:
285                         b += 2*math.pi
286                 middleAngle = (a + b) / 2
287                 averageRadius = (self._cachedInnerRadius + self._cachedOuterRadius) / 2
288
289                 sliceX = averageRadius * math.cos(middleAngle)
290                 sliceY = - averageRadius * math.sin(middleAngle)
291
292                 pieX = self._canvas.rect().center().x()
293                 pieY = self._canvas.rect().center().y()
294                 self._paint_label(
295                         painter, child.action(), i == selectionIndex, pieX+sliceX, pieY+sliceY
296                 )
297
298         def _paint_label(self, painter, action, isSelected, x, y):
299                 text = action.text()
300                 fontMetrics = painter.fontMetrics()
301                 if text:
302                         textBoundingRect = fontMetrics.boundingRect(text)
303                 else:
304                         textBoundingRect = QtCore.QRect()
305                 textWidth = textBoundingRect.width()
306                 textHeight = textBoundingRect.height()
307
308                 icon = action.icon().pixmap(
309                         QtCore.QSize(self.ICON_SIZE_DEFAULT, self.ICON_SIZE_DEFAULT),
310                         QtGui.QIcon.Normal,
311                         QtGui.QIcon.On,
312                 )
313                 averageWidth = (icon.width() + textWidth)/2
314                 if not icon.isNull():
315                         iconRect = QtCore.QRect(
316                                 x - averageWidth,
317                                 y - icon.height()/2,
318                                 icon.width(),
319                                 icon.height(),
320                         )
321
322                         painter.drawPixmap(iconRect, icon)
323
324                 if text:
325                         if isSelected:
326                                 if action.isEnabled():
327                                         pen = self.palette.highlightedText()
328                                         brush = self.palette.highlight()
329                                 else:
330                                         pen = self.palette.mid()
331                                         brush = self.palette.background()
332                         else:
333                                 if action.isEnabled():
334                                         pen = self.palette.text()
335                                 else:
336                                         pen = self.palette.mid()
337                                 brush = self.palette.background()
338
339                         leftX = x - averageWidth + icon.width()
340                         topY = y + textHeight/2
341                         painter.setPen(pen.color())
342                         painter.setBrush(brush)
343                         painter.drawText(leftX, topY, text)
344
345         def _paint_center_background(self, painter, adjustmentRect, selectionIndex):
346                 dark = self.palette.dark().color()
347                 light = self.palette.light().color()
348                 if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
349                         background = self.palette.highlight().color()
350                 else:
351                         background = self.palette.background().color()
352
353                 innerRadius = self._cachedInnerRadius
354                 innerRect = QtCore.QRect(
355                         adjustmentRect.center().x() - innerRadius,
356                         adjustmentRect.center().y() - innerRadius,
357                         innerRadius * 2 + 1,
358                         innerRadius * 2 + 1,
359                 )
360
361                 painter.setPen(QtCore.Qt.NoPen)
362                 painter.setBrush(background)
363                 painter.drawPie(innerRect, 0, 360 * 16)
364
365                 painter.setPen(QtGui.QPen(dark, 1))
366                 painter.setBrush(QtCore.Qt.NoBrush)
367                 painter.drawEllipse(innerRect)
368
369                 painter.setPen(QtGui.QPen(dark, 1))
370                 painter.setBrush(QtCore.Qt.NoBrush)
371                 painter.drawEllipse(adjustmentRect)
372
373                 r = QtCore.QRect(innerRect)
374                 innerRect.setLeft(r.center().x() + ((r.left() - r.center().x()) / 3) * 1)
375                 innerRect.setRight(r.center().x() + ((r.right() - r.center().x()) / 3) * 1)
376                 innerRect.setTop(r.center().y() + ((r.top() - r.center().y()) / 3) * 1)
377                 innerRect.setBottom(r.center().y() + ((r.bottom() - r.center().y()) / 3) * 1)
378
379         def _paint_center_foreground(self, painter, selectionIndex):
380                 pieX = self._canvas.rect().center().x()
381                 pieY = self._canvas.rect().center().y()
382
383                 x = pieX
384                 y = pieY
385
386                 self._paint_label(
387                         painter,
388                         self._filing.center().action(),
389                         selectionIndex == PieFiling.SELECTION_CENTER,
390                         x, y
391                 )
392
393
394 class QPieDisplay(QtGui.QWidget):
395
396         def __init__(self, filing, parent = None, flags = 0):
397                 QtGui.QWidget.__init__(self, parent, flags)
398                 self._filing = filing
399                 self._artist = PieArtist(self._filing)
400                 self._selectionIndex = PieFiling.SELECTION_NONE
401
402         def popup(self, pos):
403                 self._update_selection(pos)
404                 self.show()
405
406         def sizeHint(self):
407                 return self._artist.pieSize()
408
409         def showEvent(self, showEvent):
410                 mask = self._artist.show(self.palette())
411                 self.setMask(mask)
412
413                 QtGui.QWidget.showEvent(self, showEvent)
414
415         def hideEvent(self, hideEvent):
416                 self._artist.hide()
417                 self._selectionIndex = PieFiling.SELECTION_NONE
418                 QtGui.QWidget.hideEvent(self, hideEvent)
419
420         def paintEvent(self, paintEvent):
421                 canvas = self._artist.paint(self._selectionIndex)
422
423                 screen = QtGui.QPainter(self)
424                 screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
425
426                 QtGui.QWidget.paintEvent(self, paintEvent)
427
428         def selectAt(self, index):
429                 self._selectionIndex = index
430                 self.update()
431
432
433 class QPieButton(QtGui.QWidget):
434
435         activated = QtCore.pyqtSignal(int)
436         highlighted = QtCore.pyqtSignal(int)
437         canceled = QtCore.pyqtSignal()
438         aboutToShow = QtCore.pyqtSignal()
439         aboutToHide = QtCore.pyqtSignal()
440
441         def __init__(self, buttonSlice, parent = None):
442                 QtGui.QWidget.__init__(self, parent)
443                 self._filing = PieFiling()
444                 self._display = QPieDisplay(self._filing, None, QtCore.Qt.SplashScreen)
445                 self._selectionIndex = PieFiling.SELECTION_NONE
446
447                 self._buttonFiling = PieFiling()
448                 self._buttonFiling.set_center(buttonSlice)
449                 self._buttonArtist = PieArtist(self._buttonFiling)
450                 centerSize = self._buttonArtist.centerSize()
451                 self._buttonFiling.setOuterRadius(max(centerSize.width(), centerSize.height()))
452                 self._poppedUp = False
453
454                 self._mousePosition = None
455                 self.setFocusPolicy(QtCore.Qt.StrongFocus)
456
457         def insertItem(self, item, index = -1):
458                 self._filing.insertItem(item, index)
459
460         def removeItemAt(self, index):
461                 self._filing.removeItemAt(index)
462
463         def set_center(self, item):
464                 self._filing.set_center(item)
465
466         def set_button(self, item):
467                 self.update()
468
469         def clear(self):
470                 self._filing.clear()
471
472         def itemAt(self, index):
473                 return self._filing.itemAt(index)
474
475         def indexAt(self, point):
476                 return self._filing.indexAt(self.rect().center(), point)
477
478         def innerRadius(self):
479                 return self._filing.innerRadius()
480
481         def setInnerRadius(self, radius):
482                 self._filing.setInnerRadius(radius)
483
484         def outerRadius(self):
485                 return self._filing.outerRadius()
486
487         def setOuterRadius(self, radius):
488                 self._filing.setOuterRadius(radius)
489
490         def sizeHint(self):
491                 return self._buttonArtist.pieSize()
492
493         def mousePressEvent(self, mouseEvent):
494                 self._popup_child(mouseEvent.globalPos())
495                 lastSelection = self._selectionIndex
496
497                 lastMousePos = mouseEvent.pos()
498                 self._mousePosition = lastMousePos
499                 self._update_selection(self.rect().center())
500
501                 if lastSelection != self._selectionIndex:
502                         self.highlighted.emit(self._selectionIndex)
503                         self._display.selectAt(self._selectionIndex)
504
505         def mouseMoveEvent(self, mouseEvent):
506                 lastSelection = self._selectionIndex
507
508                 lastMousePos = mouseEvent.pos()
509                 if self._mousePosition is None:
510                         # Absolute
511                         self._update_selection(lastMousePos)
512                 else:
513                         # Relative
514                         self._update_selection(self.rect().center() + (lastMousePos - self._mousePosition))
515
516                 if lastSelection != self._selectionIndex:
517                         self.highlighted.emit(self._selectionIndex)
518                         self._display.selectAt(self._selectionIndex)
519
520         def mouseReleaseEvent(self, mouseEvent):
521                 lastSelection = self._selectionIndex
522
523                 lastMousePos = mouseEvent.pos()
524                 if self._mousePosition is None:
525                         # Absolute
526                         self._update_selection(lastMousePos)
527                 else:
528                         # Relative
529                         self._update_selection(self.rect().center() + (lastMousePos - self._mousePosition))
530                 self._mousePosition = None
531
532                 self._activate_at(self._selectionIndex)
533                 self._hide_child()
534
535         def keyPressEvent(self, keyEvent):
536                 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
537                         self._popup_child(QtGui.QCursor.pos())
538                         if self._selectionIndex != len(self._filing) - 1:
539                                 nextSelection = self._selectionIndex + 1
540                         else:
541                                 nextSelection = 0
542                         self._select_at(nextSelection)
543                         self._display.selectAt(self._selectionIndex)
544                 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
545                         self._popup_child(QtGui.QCursor.pos())
546                         if 0 < self._selectionIndex:
547                                 nextSelection = self._selectionIndex - 1
548                         else:
549                                 nextSelection = len(self._filing) - 1
550                         self._select_at(nextSelection)
551                         self._display.selectAt(self._selectionIndex)
552                 elif keyEvent.key() in [QtCore.Qt.Key_Space]:
553                         self._popup_child(QtGui.QCursor.pos())
554                         self._select_at(PieFiling.SELECTION_CENTER)
555                         self._display.selectAt(self._selectionIndex)
556                 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
557                         self._activate_at(self._selectionIndex)
558                         self._hide_child()
559                 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
560                         self._activate_at(PieFiling.SELECTION_NONE)
561                         self._hide_child()
562                 else:
563                         QtGui.QWidget.keyPressEvent(self, keyEvent)
564
565         def showEvent(self, showEvent):
566                 self._buttonArtist.show(self.palette())
567
568                 QtGui.QWidget.showEvent(self, showEvent)
569
570         def hideEvent(self, hideEvent):
571                 self._display.hide()
572                 self._select_at(PieFiling.SELECTION_NONE)
573                 QtGui.QWidget.hideEvent(self, hideEvent)
574
575         def paintEvent(self, paintEvent):
576                 if self._poppedUp:
577                         canvas = self._buttonArtist.paint(PieFiling.SELECTION_CENTER)
578                 else:
579                         canvas = self._buttonArtist.paint(PieFiling.SELECTION_NONE)
580
581                 screen = QtGui.QPainter(self)
582                 screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
583
584                 QtGui.QWidget.paintEvent(self, paintEvent)
585
586         def _popup_child(self, position):
587                 self._poppedUp = True
588                 self.aboutToShow.emit()
589
590                 position = position - QtCore.QPoint(self._filing.outerRadius(), self._filing.outerRadius())
591                 self._display.move(position)
592                 self._display.show()
593
594                 self.update()
595
596         def _hide_child(self):
597                 self._poppedUp = False
598                 self.aboutToHide.emit()
599                 self._display.hide()
600                 self.update()
601
602         def _select_at(self, index):
603                 self._selectionIndex = index
604
605         def _update_selection(self, lastMousePos):
606                 radius = _radius_at(self.rect().center(), lastMousePos)
607                 if radius < self._filing.innerRadius():
608                         self._select_at(PieFiling.SELECTION_CENTER)
609                 elif radius <= self._filing.outerRadius():
610                         self._select_at(self.indexAt(lastMousePos))
611                 else:
612                         self._select_at(PieFiling.SELECTION_NONE)
613
614         def _activate_at(self, index):
615                 if index == PieFiling.SELECTION_NONE:
616                         self.canceled.emit()
617                         return
618                 elif index == PieFiling.SELECTION_CENTER:
619                         child = self._filing.center()
620                 else:
621                         child = self.itemAt(index)
622
623                 if child.action().isEnabled():
624                         child.action().trigger()
625                         self.activated.emit(index)
626                 else:
627                         self.canceled.emit()
628
629
630 class QPieMenu(QtGui.QWidget):
631
632         activated = QtCore.pyqtSignal(int)
633         highlighted = QtCore.pyqtSignal(int)
634         canceled = QtCore.pyqtSignal()
635         aboutToShow = QtCore.pyqtSignal()
636         aboutToHide = QtCore.pyqtSignal()
637
638         def __init__(self, parent = None):
639                 QtGui.QWidget.__init__(self, parent)
640                 self._filing = PieFiling()
641                 self._artist = PieArtist(self._filing)
642                 self._selectionIndex = PieFiling.SELECTION_NONE
643
644                 self._mousePosition = ()
645                 self.setFocusPolicy(QtCore.Qt.StrongFocus)
646
647         def popup(self, pos):
648                 self._update_selection(pos)
649                 self.show()
650
651         def insertItem(self, item, index = -1):
652                 self._filing.insertItem(item, index)
653                 self.update()
654
655         def removeItemAt(self, index):
656                 self._filing.removeItemAt(index)
657                 self.update()
658
659         def set_center(self, item):
660                 self._filing.set_center(item)
661                 self.update()
662
663         def clear(self):
664                 self._filing.clear()
665                 self.update()
666
667         def itemAt(self, index):
668                 return self._filing.itemAt(index)
669
670         def indexAt(self, point):
671                 return self._filing.indexAt(self.rect().center(), point)
672
673         def innerRadius(self):
674                 return self._filing.innerRadius()
675
676         def setInnerRadius(self, radius):
677                 self._filing.setInnerRadius(radius)
678                 self.update()
679
680         def outerRadius(self):
681                 return self._filing.outerRadius()
682
683         def setOuterRadius(self, radius):
684                 self._filing.setOuterRadius(radius)
685                 self.update()
686
687         def sizeHint(self):
688                 return self._artist.pieSize()
689
690         def mousePressEvent(self, mouseEvent):
691                 lastSelection = self._selectionIndex
692
693                 lastMousePos = mouseEvent.pos()
694                 self._update_selection(lastMousePos)
695                 self._mousePosition = lastMousePos
696
697                 if lastSelection != self._selectionIndex:
698                         self.highlighted.emit(self._selectionIndex)
699                         self.update()
700
701         def mouseMoveEvent(self, mouseEvent):
702                 lastSelection = self._selectionIndex
703
704                 lastMousePos = mouseEvent.pos()
705                 self._update_selection(lastMousePos)
706
707                 if lastSelection != self._selectionIndex:
708                         self.highlighted.emit(self._selectionIndex)
709                         self.update()
710
711         def mouseReleaseEvent(self, mouseEvent):
712                 lastSelection = self._selectionIndex
713
714                 lastMousePos = mouseEvent.pos()
715                 self._update_selection(lastMousePos)
716                 self._mousePosition = ()
717
718                 self._activate_at(self._selectionIndex)
719                 self.update()
720
721         def keyPressEvent(self, keyEvent):
722                 if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
723                         self._select_at(self._selectionIndex + 1)
724                         self.update()
725                 elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
726                         self._select_at(self._selectionIndex - 1)
727                         self.update()
728                 elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
729                         self._activate_at(self._selectionIndex)
730                 elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
731                         self._activate_at(PieFiling.SELECTION_NONE)
732                 else:
733                         QtGui.QWidget.keyPressEvent(self, keyEvent)
734
735         def showEvent(self, showEvent):
736                 self.aboutToShow.emit()
737
738                 mask = self._artist.show(self.palette())
739                 self.setMask(mask)
740
741                 lastMousePos = self.mapFromGlobal(QtGui.QCursor.pos())
742                 self._update_selection(lastMousePos)
743
744                 QtGui.QWidget.showEvent(self, showEvent)
745
746         def hideEvent(self, hideEvent):
747                 self._artist.hide()
748                 self._selectionIndex = PieFiling.SELECTION_NONE
749                 QtGui.QWidget.hideEvent(self, hideEvent)
750
751         def paintEvent(self, paintEvent):
752                 canvas = self._artist.paint(self._selectionIndex)
753
754                 screen = QtGui.QPainter(self)
755                 screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
756
757                 QtGui.QWidget.paintEvent(self, paintEvent)
758
759         def _select_at(self, index):
760                 self._selectionIndex = index
761
762                 numChildren = len(self._filing)
763                 loopDelta = max(numChildren, 1)
764                 while self._selectionIndex < 0:
765                         self._selectionIndex += loopDelta
766                 while numChildren <= self._selectionIndex:
767                         self._selectionIndex -= loopDelta
768
769         def _update_selection(self, lastMousePos):
770                 radius = _radius_at(self.rect().center(), lastMousePos)
771                 if radius < self._filing.innerRadius():
772                         self._selectionIndex = PieFiling.SELECTION_CENTER
773                 elif radius <= self._filing.outerRadius():
774                         self._select_at(self.indexAt(lastMousePos))
775                 else:
776                         self._selectionIndex = PieFiling.SELECTION_NONE
777
778         def _activate_at(self, index):
779                 if index == PieFiling.SELECTION_NONE:
780                         self.canceled.emit()
781                         self.aboutToHide.emit()
782                         self.hide()
783                         return
784                 elif index == PieFiling.SELECTION_CENTER:
785                         child = self._filing.center()
786                 else:
787                         child = self.itemAt(index)
788
789                 if child.isEnabled():
790                         child.action().trigger()
791                         self.activated.emit(index)
792                 else:
793                         self.canceled.emit()
794                 self.aboutToHide.emit()
795                 self.hide()
796
797
798 def _print(msg):
799         print msg
800
801
802 def _on_about_to_hide(app):
803         app.exit()
804
805
806 if __name__ == "__main__":
807         app = QtGui.QApplication([])
808         PieFiling.NULL_CENTER.setEnabled(False)
809
810         if False:
811                 pie = QPieMenu()
812                 pie.show()
813
814         if False:
815                 singleAction = QtGui.QAction(None)
816                 singleAction.setText("Boo")
817                 singleItem = QActionPieItem(singleAction)
818                 spie = QPieMenu()
819                 spie.insertItem(singleItem)
820                 spie.show()
821
822         if False:
823                 oneAction = QtGui.QAction(None)
824                 oneAction.setText("Chew")
825                 oneItem = QActionPieItem(oneAction)
826                 twoAction = QtGui.QAction(None)
827                 twoAction.setText("Foo")
828                 twoItem = QActionPieItem(twoAction)
829                 iconTextAction = QtGui.QAction(None)
830                 iconTextAction.setText("Icon")
831                 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
832                 iconTextItem = QActionPieItem(iconTextAction)
833                 mpie = QPieMenu()
834                 mpie.insertItem(oneItem)
835                 mpie.insertItem(twoItem)
836                 mpie.insertItem(oneItem)
837                 mpie.insertItem(iconTextItem)
838                 mpie.show()
839
840         if False:
841                 oneAction = QtGui.QAction(None)
842                 oneAction.setText("Chew")
843                 oneAction.triggered.connect(lambda: _print("Chew"))
844                 oneItem = QActionPieItem(oneAction)
845                 twoAction = QtGui.QAction(None)
846                 twoAction.setText("Foo")
847                 twoAction.triggered.connect(lambda: _print("Foo"))
848                 twoItem = QActionPieItem(twoAction)
849                 iconAction = QtGui.QAction(None)
850                 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
851                 iconAction.triggered.connect(lambda: _print("Icon"))
852                 iconItem = QActionPieItem(iconAction)
853                 iconTextAction = QtGui.QAction(None)
854                 iconTextAction.setText("Icon")
855                 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
856                 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
857                 iconTextItem = QActionPieItem(iconTextAction)
858                 mpie = QPieMenu()
859                 mpie.set_center(iconItem)
860                 mpie.insertItem(oneItem)
861                 mpie.insertItem(twoItem)
862                 mpie.insertItem(oneItem)
863                 mpie.insertItem(iconTextItem)
864                 mpie.show()
865                 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
866                 mpie.canceled.connect(lambda: _print("Canceled"))
867
868         if False:
869                 oneAction = QtGui.QAction(None)
870                 oneAction.setText("Chew")
871                 oneAction.triggered.connect(lambda: _print("Chew"))
872                 oneItem = QActionPieItem(oneAction)
873                 twoAction = QtGui.QAction(None)
874                 twoAction.setText("Foo")
875                 twoAction.triggered.connect(lambda: _print("Foo"))
876                 twoItem = QActionPieItem(twoAction)
877                 iconAction = QtGui.QAction(None)
878                 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
879                 iconAction.triggered.connect(lambda: _print("Icon"))
880                 iconItem = QActionPieItem(iconAction)
881                 iconTextAction = QtGui.QAction(None)
882                 iconTextAction.setText("Icon")
883                 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
884                 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
885                 iconTextItem = QActionPieItem(iconTextAction)
886                 pieFiling = PieFiling()
887                 pieFiling.set_center(iconItem)
888                 pieFiling.insertItem(oneItem)
889                 pieFiling.insertItem(twoItem)
890                 pieFiling.insertItem(oneItem)
891                 pieFiling.insertItem(iconTextItem)
892                 mpie = QPieDisplay(pieFiling)
893                 mpie.show()
894
895         if True:
896                 oneAction = QtGui.QAction(None)
897                 oneAction.setText("Chew")
898                 oneAction.triggered.connect(lambda: _print("Chew"))
899                 oneItem = QActionPieItem(oneAction)
900                 twoAction = QtGui.QAction(None)
901                 twoAction.setText("Foo")
902                 twoAction.triggered.connect(lambda: _print("Foo"))
903                 twoItem = QActionPieItem(twoAction)
904                 iconAction = QtGui.QAction(None)
905                 iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
906                 iconAction.triggered.connect(lambda: _print("Icon"))
907                 iconItem = QActionPieItem(iconAction)
908                 iconTextAction = QtGui.QAction(None)
909                 iconTextAction.setText("Icon")
910                 iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
911                 iconTextAction.triggered.connect(lambda: _print("Icon and text"))
912                 iconTextItem = QActionPieItem(iconTextAction)
913                 mpie = QPieButton(iconItem)
914                 mpie.set_center(iconItem)
915                 mpie.insertItem(oneItem)
916                 mpie.insertItem(twoItem)
917                 mpie.insertItem(oneItem)
918                 mpie.insertItem(iconTextItem)
919                 mpie.show()
920                 mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
921                 mpie.canceled.connect(lambda: _print("Canceled"))
922
923         app.exec_()