source: orange/Orange/OrangeCanvas/gui/dock.py @ 11414:2f8f070e15f5

Revision 11414:2f8f070e15f5, 9.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

Added a 'expandedChanged' signal to 'CollapsibleDockWidget'.

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