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