source: orange/Orange/OrangeCanvas/gui/dock.py @ 11232:c56bdc4a90a6

Revision 11232:c56bdc4a90a6, 7.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 16 months ago (diff)

Workaround for QDockWidget's minimumSize.

Line 
1"""
2=======================
3Collapsible Dock Widget
4=======================
5
6A dock widget with a header that can be a collapsed/expanded.
7
8"""
9
10import logging
11
12from PyQt4.QtGui import (
13    QDockWidget, QAbstractButton, QSizePolicy, QStyle, QIcon, QTransform
14)
15
16from PyQt4.QtCore import Qt, QTimer, QEvent
17
18from PyQt4.QtCore import pyqtProperty as Property
19
20from .stackedwidget import AnimatedStackedWidget
21from .utils import QWIDGETSIZE_MAX
22
23log = logging.getLogger(__name__)
24
25
26class CollapsibleDockWidget(QDockWidget):
27    """A Dock widget for which the close action collapses the widget
28    to a smaller size.
29
30    """
31    def __init__(self, *args, **kwargs):
32        QDockWidget.__init__(self, *args, **kwargs)
33
34        self.__expandedWidget = None
35        self.__collapsedWidget = None
36        self.__expanded = True
37
38        self.__trueMinimumWidth = -1
39
40        self.setFeatures(QDockWidget.DockWidgetClosable | \
41                         QDockWidget.DockWidgetMovable)
42        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
43
44        self.featuresChanged.connect(self.__onFeaturesChanged)
45        self.dockLocationChanged.connect(self.__onDockLocationChanged)
46
47        # Use the toolbar horizontal extension button icon as the default
48        # for the expand/collapse button
49        pm = self.style().standardPixmap(
50            QStyle.SP_ToolBarHorizontalExtensionButton
51        )
52
53        # Rotate the icon
54        transform = QTransform()
55        transform.rotate(180)
56
57        pm_rev = pm.transformed(transform)
58
59        self.__iconRight = QIcon(pm)
60        self.__iconLeft = QIcon(pm_rev)
61
62        close = self.findChild(QAbstractButton,
63                               name="qt_dockwidget_closebutton")
64
65        close.installEventFilter(self)
66        self.__closeButton = close
67
68        self.__stack = AnimatedStackedWidget()
69
70        self.__stack.setSizePolicy(QSizePolicy.Fixed,
71                                   QSizePolicy.Expanding)
72
73        self.__stack.transitionStarted.connect(self.__onTransitionStarted)
74        self.__stack.transitionFinished.connect(self.__onTransitionFinished)
75
76        QDockWidget.setWidget(self, self.__stack)
77
78        self.__closeButton.setIcon(self.__iconLeft)
79
80    def setExpanded(self, state):
81        """Set the expanded state.
82        """
83        if self.__expanded != state:
84            self.__expanded = state
85            if state and self.__expandedWidget is not None:
86                log.debug("Dock expanding.")
87                self.__stack.setCurrentWidget(self.__expandedWidget)
88            elif not state and self.__collapsedWidget is not None:
89                log.debug("Dock collapsing.")
90                self.__stack.setCurrentWidget(self.__collapsedWidget)
91            self.__fixIcon()
92
93    def expanded(self):
94        """Is the dock widget in expanded state
95        """
96        return self.__expanded
97
98    expanded_ = Property(bool, fset=setExpanded, fget=expanded)
99
100    def setWidget(self, w):
101        raise NotImplementedError(
102                "Please use the setExpandedWidget/setCollapsedWidget method."
103              )
104
105    def setExpandedWidget(self, widget):
106        """Set the widget with contents to show while expanded.
107        """
108        if widget is self.__expandedWidget:
109            return
110
111        if self.__expandedWidget is not None:
112            self.__stack.removeWidget(self.__expandedWidget)
113
114        self.__stack.insertWidget(0, widget)
115        self.__expandedWidget = widget
116
117        if self.__expanded:
118            self.__stack.setCurrentWidget(widget)
119            self.updateGeometry()
120
121    def setCollapsedWidget(self, widget):
122        """Set the widget with contents to show while collapsed.
123        """
124        if widget is self.__collapsedWidget:
125            return
126
127        if self.__collapsedWidget is not None:
128            self.__stack.removeWidget(self.__collapsedWidget)
129
130        self.__stack.insertWidget(1, widget)
131        self.__collapsedWidget = widget
132
133        if not self.__expanded:
134            self.__stack.setCurrentWidget(widget)
135            self.updateGeometry()
136
137    def setAnimationEnabled(self, animationEnabled):
138        """Enable/disable the transition animation.
139        """
140        self.__stack.setAnimationEnabled(animationEnabled)
141
142    def animationEnabled(self):
143        return self.__stack.animationEnabled()
144
145    def currentWidget(self):
146        """Return the current widget.
147        """
148        if self.__expanded:
149            return self.__expandedWidget
150        else:
151            return self.__collapsedWidget
152
153    def expand(self):
154        """Expand the dock (same as `setExpanded(True)`)
155        """
156        self.setExpanded(True)
157
158    def collapse(self):
159        """Collapse the dock (same as `setExpanded(False)`)
160        """
161        self.setExpanded(False)
162
163    def eventFilter(self, obj, event):
164        if obj is self.__closeButton:
165            etype = event.type()
166            if etype == QEvent.MouseButtonPress:
167                self.setExpanded(not self.__expanded)
168                return True
169            elif etype == QEvent.MouseButtonDblClick or \
170                    etype == QEvent.MouseButtonRelease:
171                return True
172            # TODO: which other events can trigger the button (is the button
173            # focusable).
174
175        return QDockWidget.eventFilter(self, obj, event)
176
177    def event(self, event):
178        if event.type() == QEvent.LayoutRequest:
179            self.__fixMinimumWidth()
180
181        return QDockWidget.event(self, event)
182
183    def __onFeaturesChanged(self, features):
184        pass
185
186    def __onDockLocationChanged(self, area):
187        if area == Qt.LeftDockWidgetArea:
188            self.setLayoutDirection(Qt.LeftToRight)
189        else:
190            self.setLayoutDirection(Qt.RightToLeft)
191
192        self.__stack.setLayoutDirection(self.parentWidget().layoutDirection())
193        self.__fixIcon()
194
195    def __onTransitionStarted(self):
196        log.debug("Dock transition started.")
197
198    def __onTransitionFinished(self):
199        log.debug("Dock transition finished (new width %i)",
200                  self.size().width())
201
202    def __fixMinimumWidth(self):
203        # A workaround for forcing the QDockWidget layout to disregard the
204        # default minimumSize which can be to wide for us (overriding the
205        # minimumSizeHint or setting the minimum size directly does not
206        # seem to have an effect (Qt 4.8.3).
207        size = self.__stack.sizeHint()
208        if size.isValid() and not size.isEmpty():
209            left, _, right, _ = self.getContentsMargins()
210            width = size.width() + left + right
211
212            if width < self.minimumSizeHint().width():
213                if not self.__hasFixedWidth():
214                    log.debug("Overriding default minimum size "
215                              "(setFixedWidth(%i))", width)
216                    self.__trueMinimumWidth = self.minimumSizeHint().width()
217                self.setFixedWidth(width)
218            else:
219                if self.__hasFixedWidth():
220                    if width >= self.__trueMinimumWidth:
221                        # Unset the fixed size.
222                        log.debug("Restoring default minimum size "
223                                  "(setFixedWidth(%i))", QWIDGETSIZE_MAX)
224                        self.__trueMinimumWidth = -1
225                        self.setFixedWidth(QWIDGETSIZE_MAX)
226                        self.updateGeometry()
227                    else:
228                        self.setFixedWidth(width)
229
230    def __hasFixedWidth(self):
231        return self.__trueMinimumWidth >= 0
232
233    def __fixIcon(self):
234        """Fix the dock close icon.
235        """
236        direction = self.layoutDirection()
237        if direction == Qt.LeftToRight:
238            if self.__expanded:
239                icon = self.__iconLeft
240            else:
241                icon = self.__iconRight
242        else:
243            if self.__expanded:
244                icon = self.__iconRight
245            else:
246                icon = self.__iconLeft
247
248        self.__closeButton.setIcon(icon)
Note: See TracBrowser for help on using the repository browser.