Advertisement
Guest User

PyQt AccordionWidget

a guest
May 16th, 2012
1,397
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 19.69 KB | None | 0 0
  1. # Modification of Blur's Accordion Widget to include a Maya style.  Also got rid of the need for a pixmap
  2. # and used QPolygon instead.
  3.  
  4. from PyQt4.QtCore import Qt, QRect, QMimeData, pyqtSignal, pyqtProperty, QEvent, QPoint
  5. from PyQt4.QtGui import QDrag, QPixmap, QScrollArea, QGroupBox, QVBoxLayout, QPainter, QPainterPath, QPalette, QPen
  6. from PyQt4.QtGui import QWidget, QMatrix, QCursor, QApplication, QDialog, QColor, QPolygon, QBrush
  7. import os.path
  8.  
  9. class AccordionItem(QGroupBox):
  10.     def __init__( self, accordion, title, widget ):
  11.         QGroupBox.__init__(self, accordion)
  12.  
  13.         # create the layout
  14.         layout = QVBoxLayout()
  15.         layout.setContentsMargins(6, 6, 6, 6)
  16.         layout.setSpacing(0)
  17.         layout.addWidget(widget)
  18.  
  19.         self._accordianWidget = accordion
  20.         self._rolloutStyle = 2
  21.         self._dragDropMode = 0
  22.  
  23.         self.setAcceptDrops(True)
  24.         self.setLayout(layout)
  25.         self.setContextMenuPolicy(Qt.CustomContextMenu)
  26.         self.customContextMenuRequested.connect(self.showMenu)
  27.  
  28.         # create custom properties
  29.         self._widget = widget
  30.         self._collapsed = False
  31.         self._collapsible = True
  32.         self._clicked = False
  33.         self._customData = {}
  34.  
  35.         # set common properties
  36.         self.setTitle(title)
  37.  
  38.  
  39.     def accordionWidget( self ):
  40.         """
  41.             \remarks    grabs the parent item for the accordian widget
  42.             \return     <blurdev.gui.widgets.accordianwidget.AccordianWidget>
  43.        """
  44.         return self._accordianWidget
  45.  
  46.  
  47.     def customData( self, key, default=None ):
  48.         """
  49.             \remarks    return a custom pointer to information stored with this item
  50.             \param      key         <str>
  51.             \param      default     <variant>   default value to return if the key was not found
  52.             \return     <variant> data
  53.        """
  54.         return self._customData.get(str(key), default)
  55.  
  56.  
  57.     def dragEnterEvent( self, event ):
  58.         if not self._dragDropMode:
  59.             return
  60.  
  61.         source = event.source()
  62.         if source != self and source.parent() == self.parent() and isinstance(source, AccordionItem):
  63.             event.acceptProposedAction()
  64.  
  65.  
  66.     def dragDropRect( self ):
  67.         return QRect(25, 7, 10, 6)
  68.  
  69.  
  70.     def dragDropMode( self ):
  71.         return self._dragDropMode
  72.  
  73.  
  74.     def dragMoveEvent( self, event ):
  75.         if not self._dragDropMode:
  76.             return
  77.  
  78.         source = event.source()
  79.         if source != self and source.parent() == self.parent() and isinstance(source, AccordionItem):
  80.             event.acceptProposedAction()
  81.  
  82.  
  83.     def dropEvent( self, event ):
  84.         widget = event.source()
  85.         layout = self.parent().layout()
  86.         layout.insertWidget(layout.indexOf(self), widget)
  87.         self._accordianWidget.emitItemsReordered()
  88.  
  89.  
  90.     def expandCollapseRect( self ):
  91.         return QRect(0, 0, self.width(), 20)
  92.  
  93.  
  94.     def enterEvent( self, event ):
  95.         self.accordionWidget().leaveEvent(event)
  96.         event.accept()
  97.  
  98.  
  99.     def leaveEvent( self, event ):
  100.         self.accordionWidget().enterEvent(event)
  101.         event.accept()
  102.  
  103.  
  104.     def mouseReleaseEvent( self, event ):
  105.         if self._clicked and self.expandCollapseRect().contains(event.pos()):
  106.             self.toggleCollapsed()
  107.             event.accept()
  108.         else:
  109.             event.ignore()
  110.  
  111.         self._clicked = False
  112.  
  113.  
  114.     def mouseMoveEvent( self, event ):
  115.         event.ignore()
  116.  
  117.  
  118.     def mousePressEvent( self, event ):
  119.         # handle an internal move
  120.  
  121.         # start a drag event
  122.         if event.button() == Qt.LeftButton and self.dragDropRect().contains(event.pos()):
  123.             # create the pixmap
  124.             pixmap = QPixmap.grabWidget(self, self.rect())
  125.  
  126.             # create the mimedata
  127.             mimeData = QMimeData()
  128.             mimeData.setText('ItemTitle::%s' % (self.title()))
  129.  
  130.             # create the drag
  131.             drag = QDrag(self)
  132.             drag.setMimeData(mimeData)
  133.             drag.setPixmap(pixmap)
  134.             drag.setHotSpot(event.pos())
  135.  
  136.             if not drag.exec_():
  137.                 self._accordianWidget.emitItemDragFailed(self)
  138.  
  139.             event.accept()
  140.  
  141.         # determine if the expand/collapse should occur
  142.         elif event.button() == Qt.LeftButton and self.expandCollapseRect().contains(event.pos()):
  143.             self._clicked = True
  144.             event.accept()
  145.  
  146.         else:
  147.             event.ignore()
  148.  
  149.  
  150.     def isCollapsed( self ):
  151.         return self._collapsed
  152.  
  153.  
  154.     def isCollapsible( self ):
  155.         return self._collapsible
  156.  
  157.  
  158.     def __drawTriangle(self, painter, x, y):
  159.         brush = QBrush(QColor(255, 255, 255, 160), Qt.SolidPattern)
  160.         if not self.isCollapsed():
  161.             tl, tr, tp = QPoint(x + 9, y + 8), QPoint(x + 19, y + 8), QPoint(x + 14, y + 13.0)
  162.             points = [tl, tr, tp]
  163.             triangle = QPolygon(points)
  164.         else:
  165.             tl, tr, tp = QPoint(x + 11, y + 6), QPoint(x + 16, y + 11), QPoint(x + 11, y + 16.0)
  166.             points = [tl, tr, tp]
  167.             triangle = QPolygon(points)
  168.         currentBrush = painter.brush()
  169.         painter.setBrush(brush)
  170.         painter.drawPolygon(triangle)
  171.         painter.setBrush(currentBrush)
  172.  
  173.  
  174.     def paintEvent( self, event ):
  175.         painter = QPainter()
  176.         painter.begin(self)
  177.         painter.setRenderHint(painter.Antialiasing)
  178.         font = painter.font()
  179.         font.setBold(True)
  180.         painter.setFont(font)
  181.  
  182.         x = self.rect().x()
  183.         y = self.rect().y()
  184.         w = self.rect().width() - 1
  185.         h = self.rect().height() - 1
  186.         r = 8
  187.  
  188.         # draw a rounded style
  189.         if self._rolloutStyle == 2:
  190.             # draw the text
  191.             painter.drawText(x + 33, y + 3, w, 16, Qt.AlignLeft | Qt.AlignTop, self.title())
  192.  
  193.             # draw the triangle
  194.             self.__drawTriangle(painter, x, y)
  195.  
  196.             # draw the borders
  197.             pen = QPen(self.palette().color(QPalette.Light))
  198.             pen.setWidthF(0.6)
  199.             painter.setPen(pen)
  200.  
  201.             painter.drawRoundedRect(x + 1, y + 1, w - 1, h - 1, r, r)
  202.  
  203.             pen.setColor(self.palette().color(QPalette.Shadow))
  204.             painter.setPen(pen)
  205.  
  206.             painter.drawRoundedRect(x, y, w - 1, h - 1, r, r)
  207.  
  208.         # draw a square style
  209.         if self._rolloutStyle == 3:
  210.             # draw the text
  211.             painter.drawText(x + 33, y + 3, w, 16, Qt.AlignLeft | Qt.AlignTop, self.title())
  212.  
  213.             self.__drawTriangle(painter, x, y)
  214.  
  215.             # draw the borders
  216.             pen = QPen(self.palette().color(QPalette.Light))
  217.             pen.setWidthF(0.6)
  218.             painter.setPen(pen)
  219.  
  220.             painter.drawRect(x + 1, y + 1, w - 1, h - 1)
  221.  
  222.             pen.setColor(self.palette().color(QPalette.Shadow))
  223.             painter.setPen(pen)
  224.  
  225.             painter.drawRect(x, y, w - 1, h - 1)
  226.  
  227.         # draw a Maya style
  228.         if self._rolloutStyle == 4:
  229.             # draw the text
  230.             painter.drawText(x + 33, y + 3, w, 16, Qt.AlignLeft | Qt.AlignTop, self.title())
  231.  
  232.             painter.setRenderHint(QPainter.Antialiasing, False)
  233.  
  234.             self.__drawTriangle(painter, x, y)
  235.  
  236.             # draw the borders - top
  237.             headerHeight = 20
  238.  
  239.             headerRect = QRect(x + 1, y + 1, w - 1, headerHeight)
  240.             headerRectShadow = QRect(x - 1, y - 1, w + 1, headerHeight + 2)
  241.  
  242.             # Highlight
  243.             pen = QPen(self.palette().color(QPalette.Light))
  244.             pen.setWidthF(0.4)
  245.             painter.setPen(pen)
  246.  
  247.             painter.drawRect(headerRect)
  248.             painter.fillRect(headerRect, QColor(255, 255, 255, 18))
  249.  
  250.             # Shadow
  251.             pen.setColor(self.palette().color(QPalette.Dark))
  252.             painter.setPen(pen)
  253.             painter.drawRect(headerRectShadow)
  254.  
  255.             if not self.isCollapsed():
  256.                 # draw the lover border
  257.                 pen = QPen(self.palette().color(QPalette.Dark))
  258.                 pen.setWidthF(0.8)
  259.                 painter.setPen(pen)
  260.  
  261.                 offSet = headerHeight + 3
  262.                 bodyRect = QRect(x, y + offSet, w, h - offSet)
  263.                 bodyRectShadow = QRect(x + 1, y + offSet, w + 1, h - offSet + 1)
  264.                 painter.drawRect(bodyRect)
  265.  
  266.                 pen.setColor(self.palette().color(QPalette.Light))
  267.                 pen.setWidthF(0.4)
  268.                 painter.setPen(pen)
  269.  
  270.                 painter.drawRect(bodyRectShadow)
  271.  
  272.         # draw a boxed style
  273.         elif self._rolloutStyle == 1:
  274.             if self.isCollapsed():
  275.                 arect = QRect(x + 1, y + 9, w - 1, 4)
  276.                 brect = QRect(x, y + 8, w - 1, 4)
  277.                 text = '+'
  278.             else:
  279.                 arect = QRect(x + 1, y + 9, w - 1, h - 9)
  280.                 brect = QRect(x, y + 8, w - 1, h - 9)
  281.                 text = '-'
  282.  
  283.             # draw the borders
  284.             pen = QPen(self.palette().color(QPalette.Light))
  285.             pen.setWidthF(0.6)
  286.             painter.setPen(pen)
  287.  
  288.             painter.drawRect(arect)
  289.  
  290.             pen.setColor(self.palette().color(QPalette.Shadow))
  291.             painter.setPen(pen)
  292.  
  293.             painter.drawRect(brect)
  294.  
  295.             painter.setRenderHint(painter.Antialiasing, False)
  296.             painter.setBrush(self.palette().color(QPalette.Window).darker(120))
  297.             painter.drawRect(x + 10, y + 1, w - 20, 16)
  298.             painter.drawText(x + 16, y + 1, w - 32, 16, Qt.AlignLeft | Qt.AlignVCenter, text)
  299.             painter.drawText(x + 10, y + 1, w - 20, 16, Qt.AlignCenter, self.title())
  300.  
  301.         if self.dragDropMode():
  302.             rect = self.dragDropRect()
  303.  
  304.             # draw the lines
  305.             l = rect.left()
  306.             r = rect.right()
  307.             cy = rect.center().y()
  308.  
  309.             for y in (cy - 3, cy, cy + 3):
  310.                 painter.drawLine(l, y, r, y)
  311.  
  312.         painter.end()
  313.  
  314.  
  315.     def setCollapsed( self, state=True ):
  316.         if self.isCollapsible():
  317.             accord = self.accordionWidget()
  318.             accord.setUpdatesEnabled(False)
  319.  
  320.             self._collapsed = state
  321.  
  322.             if state:
  323.                 self.setMinimumHeight(22)
  324.                 self.setMaximumHeight(22)
  325.                 self.widget().setVisible(False)
  326.             else:
  327.                 self.setMinimumHeight(0)
  328.                 self.setMaximumHeight(1000000)
  329.                 self.widget().setVisible(True)
  330.  
  331.             self._accordianWidget.emitItemCollapsed(self)
  332.             accord.setUpdatesEnabled(True)
  333.  
  334.  
  335.     def setCollapsible( self, state=True ):
  336.         self._collapsible = state
  337.  
  338.  
  339.     def setCustomData( self, key, value ):
  340.         """
  341.             \remarks    set a custom pointer to information stored on this item
  342.             \param      key     <str>
  343.             \param      value   <variant>
  344.        """
  345.         self._customData[str(key)] = value
  346.  
  347.  
  348.     def setDragDropMode( self, mode ):
  349.         self._dragDropMode = mode
  350.  
  351.  
  352.     def setRolloutStyle( self, style ):
  353.         self._rolloutStyle = style
  354.  
  355.  
  356.     def showMenu( self ):
  357.         if QRect(0, 0, self.width(), 20).contains(self.mapFromGlobal(QCursor.pos())):
  358.             self._accordianWidget.emitItemMenuRequested(self)
  359.  
  360.  
  361.     def rolloutStyle( self ):
  362.         return self._rolloutStyle
  363.  
  364.  
  365.     def toggleCollapsed( self ):
  366.         self.setCollapsed(not self.isCollapsed())
  367.  
  368.  
  369.     def widget( self ):
  370.         return self._widget
  371.  
  372.  
  373. ##
  374. #   \namespace  trax.gui.widgets.accordianwidget
  375. #
  376. #   \remarks    A container widget for creating expandable and collapsible components
  377. #
  378. #   \author     beta@blur.com
  379. #   \author     Blur Studio
  380. #   \date       04/29/10
  381. #
  382. #
  383.  
  384.  
  385.  
  386. class AccordionWidget(QScrollArea):
  387.     itemCollapsed = pyqtSignal(AccordionItem)
  388.     itemMenuRequested = pyqtSignal(AccordionItem)
  389.     itemDragFailed = pyqtSignal(AccordionItem)
  390.     itemsReordered = pyqtSignal()
  391.  
  392.     Boxed = 1
  393.     Rounded = 2
  394.     Square = 3
  395.     Maya = 4
  396.  
  397.     NoDragDrop = 0
  398.     InternalMove = 1
  399.  
  400.  
  401.     def __init__( self, parent ):
  402.         QScrollArea.__init__(self, parent)
  403.  
  404.         self.setFrameShape(QScrollArea.NoFrame)
  405.         self.setAutoFillBackground(False)
  406.         self.setWidgetResizable(True)
  407.         self.setMouseTracking(True)
  408.         #self.verticalScrollBar().setMaximumWidth(10)
  409.  
  410.         widget = QWidget(self)
  411.  
  412.         # define custom properties
  413.         self._rolloutStyle = AccordionWidget.Rounded
  414.         self._dragDropMode = AccordionWidget.NoDragDrop
  415.         self._scrolling = False
  416.         self._scrollInitY = 0
  417.         self._scrollInitVal = 0
  418.         self._itemClass = AccordionItem
  419.  
  420.         layout = QVBoxLayout()
  421.         layout.setContentsMargins(2, 2, 2, 2)
  422.         layout.setSpacing(2)
  423.         layout.addStretch(1)
  424.  
  425.         widget.setLayout(layout)
  426.  
  427.         self.setWidget(widget)
  428.  
  429.     def setSpacing(self, spaceInt):
  430.         self.widget().layout().setSpacing(spaceInt)
  431.  
  432.  
  433.     def addItem( self, title, widget, collapsed=False ):
  434.         self.setUpdatesEnabled(False)
  435.         item = self._itemClass(self, title, widget)
  436.         item.setRolloutStyle(self.rolloutStyle())
  437.         item.setDragDropMode(self.dragDropMode())
  438.         layout = self.widget().layout()
  439.         layout.insertWidget(layout.count() - 1, item)
  440.         layout.setStretchFactor(item, 0)
  441.  
  442.         if collapsed:
  443.             item.setCollapsed(collapsed)
  444.  
  445.         self.setUpdatesEnabled(True)
  446.         return item
  447.  
  448.  
  449.     def clear( self ):
  450.         self.setUpdatesEnabled(False)
  451.         layout = self.widget().layout()
  452.         while layout.count() > 1:
  453.             item = layout.itemAt(0)
  454.  
  455.             # remove the item from the layout
  456.             w = item.widget()
  457.             layout.removeItem(item)
  458.  
  459.             # close the widget and delete it
  460.             w.close()
  461.             w.deleteLater()
  462.  
  463.         self.setUpdatesEnabled(True)
  464.  
  465.  
  466.     def eventFilter( self, object, event ):
  467.         if event.type() == QEvent.MouseButtonPress:
  468.             self.mousePressEvent(event)
  469.             return True
  470.  
  471.         elif event.type() == QEvent.MouseMove:
  472.             self.mouseMoveEvent(event)
  473.             return True
  474.  
  475.         elif event.type() == QEvent.MouseButtonRelease:
  476.             self.mouseReleaseEvent(event)
  477.             return True
  478.  
  479.         return False
  480.  
  481.  
  482.     def canScroll( self ):
  483.         return self.verticalScrollBar().maximum() > 0
  484.  
  485.  
  486.     def count( self ):
  487.         return self.widget().layout().count() - 1
  488.  
  489.  
  490.     def dragDropMode( self ):
  491.         return self._dragDropMode
  492.  
  493.  
  494.     def indexOf(self, widget):
  495.         """
  496.             \remarks    Searches for widget(not including child layouts).
  497.                        Returns the index of widget, or -1 if widget is not found
  498.             \return     <int>
  499.        """
  500.         layout = self.widget().layout()
  501.         for index in range(layout.count()):
  502.             if layout.itemAt(index).widget().widget() == widget:
  503.                 return index
  504.         return -1
  505.  
  506.  
  507.     def isBoxedMode( self ):
  508.         return self._rolloutStyle == AccordionWidget.Boxed
  509.  
  510.  
  511.     def itemClass( self ):
  512.         return self._itemClass
  513.  
  514.  
  515.     def itemAt( self, index ):
  516.         layout = self.widget().layout()
  517.         if 0 <= index and index < layout.count() - 1:
  518.             return layout.itemAt(index).widget()
  519.         return None
  520.  
  521.  
  522.     def emitItemCollapsed( self, item ):
  523.         if not self.signalsBlocked():
  524.             self.itemCollapsed.emit(item)
  525.  
  526.  
  527.     def emitItemDragFailed( self, item ):
  528.         if not self.signalsBlocked():
  529.             self.itemDragFailed.emit(item)
  530.  
  531.  
  532.     def emitItemMenuRequested( self, item ):
  533.         if not self.signalsBlocked():
  534.             self.itemMenuRequested.emit(item)
  535.  
  536.  
  537.     def emitItemsReordered( self ):
  538.         if not self.signalsBlocked():
  539.             self.itemsReordered.emit()
  540.  
  541.  
  542.     def enterEvent( self, event ):
  543.         if self.canScroll():
  544.             QApplication.setOverrideCursor(Qt.OpenHandCursor)
  545.  
  546.  
  547.     def leaveEvent( self, event ):
  548.         if self.canScroll():
  549.             QApplication.restoreOverrideCursor()
  550.  
  551.  
  552.     def mouseMoveEvent( self, event ):
  553.         if self._scrolling:
  554.             sbar = self.verticalScrollBar()
  555.             smax = sbar.maximum()
  556.  
  557.             # calculate the distance moved for the moust point
  558.             dy = event.globalY() - self._scrollInitY
  559.  
  560.             # calculate the percentage that is of the scroll bar
  561.             dval = smax * ( dy / float(sbar.height()) )
  562.  
  563.             # calculate the new value
  564.             sbar.setValue(self._scrollInitVal - dval)
  565.  
  566.         event.accept()
  567.  
  568.  
  569.     def mousePressEvent( self, event ):
  570.         # handle a scroll event
  571.         if event.button() == Qt.LeftButton and self.canScroll():
  572.             self._scrolling = True
  573.             self._scrollInitY = event.globalY()
  574.             self._scrollInitVal = self.verticalScrollBar().value()
  575.  
  576.             QApplication.setOverrideCursor(Qt.ClosedHandCursor)
  577.  
  578.         event.accept()
  579.  
  580.  
  581.     def mouseReleaseEvent( self, event ):
  582.         if self._scrolling:
  583.             QApplication.restoreOverrideCursor()
  584.  
  585.         self._scrolling = False
  586.         self._scrollInitY = 0
  587.         self._scrollInitVal = 0
  588.         event.accept()
  589.  
  590.  
  591.     def moveItemDown(self, index):
  592.         layout = self.widget().layout()
  593.         if (layout.count() - 1) > (index + 1):
  594.             widget = layout.takeAt(index).widget()
  595.             layout.insertWidget(index + 1, widget)
  596.  
  597.  
  598.     def moveItemUp(self, index):
  599.         if index > 0:
  600.             layout = self.widget().layout()
  601.             widget = layout.takeAt(index).widget()
  602.             layout.insertWidget(index - 1, widget)
  603.  
  604.  
  605.     def setBoxedMode( self, state ):
  606.         if state:
  607.             self._rolloutStyle = AccordionWidget.Boxed
  608.         else:
  609.             self._rolloutStyle = AccordionWidget.Rounded
  610.  
  611.  
  612.     def setDragDropMode( self, dragDropMode ):
  613.         self._dragDropMode = dragDropMode
  614.  
  615.         for item in self.findChildren(AccordionItem):
  616.             item.setDragDropMode(self._dragDropMode)
  617.  
  618.  
  619.     def setItemClass( self, itemClass ):
  620.         self._itemClass = itemClass
  621.  
  622.  
  623.     def setRolloutStyle( self, rolloutStyle ):
  624.         self._rolloutStyle = rolloutStyle
  625.  
  626.         for item in self.findChildren(AccordionItem):
  627.             item.setRolloutStyle(self._rolloutStyle)
  628.  
  629.  
  630.     def rolloutStyle( self ):
  631.         return self._rolloutStyle
  632.  
  633.  
  634.     def takeAt( self, index ):
  635.         self.setUpdatesEnabled(False)
  636.         layout = self.widget().layout()
  637.         widget = None
  638.         if 0 <= index and index < layout.count() - 1:
  639.             item = layout.itemAt(index)
  640.             widget = item.widget()
  641.  
  642.             layout.removeItem(item)
  643.             widget.close()
  644.         self.setUpdatesEnabled(True)
  645.         return widget
  646.  
  647.  
  648.     def widgetAt( self, index ):
  649.         item = self.itemAt(index)
  650.         if item:
  651.             return item.widget()
  652.         return None
  653.  
  654.  
  655.     pyBoxedMode = pyqtProperty('bool', isBoxedMode, setBoxedMode)
  656.  
  657.  
  658. _gUI = None
  659.  
  660. class Sample(QtGui.QDialog):
  661.     def __init__(self, parent=None):
  662.         super(Sample, self).__init__(parent)
  663.         self.setLayout(QtGui.QVBoxLayout())
  664.         self.accWidget = AccordionWidget(self)
  665.         self.accWidget.addItem("A", self.buildFrame())
  666.         self.accWidget.addItem("B", self.buildFrame())
  667.         self.accWidget.setRolloutStyle(self.accWidget.Maya)
  668.         self.accWidget.setSpacing(0)# More like Maya but I like some padding.
  669.         self.layout().addWidget(self.accWidget)
  670.  
  671.     def buildFrame(self):
  672.         someFrame = QtGui.QFrame(self)
  673.         someFrame.setLayout(QtGui.QVBoxLayout())
  674.         someFrame.layout().addWidget(QtGui.QPushButton("Test"))
  675.         return someFrame
  676.  
  677.     @classmethod
  678.     def display(cls):
  679.         """Demo Display method for Maya"""
  680.         global _gUI
  681.         _gUI = cls()
  682.         _gUI.show()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement