Source code for orangecanvas.gui.dock

"""
=======================
Collapsible Dock Widget
=======================

A dock widget that can be a collapsed/expanded.

"""

import logging

from AnyQt.QtWidgets import (
    QDockWidget, QAbstractButton, QSizePolicy, QStyle, QWIDGETSIZE_MAX
)
from AnyQt.QtGui import QIcon, QTransform
from AnyQt.QtCore import Qt, QEvent
from AnyQt.QtCore import pyqtProperty as Property, pyqtSignal as Signal

from .stackedwidget import AnimatedStackedWidget

log = logging.getLogger(__name__)


[docs]class CollapsibleDockWidget(QDockWidget): """ This :class:`QDockWidget` subclass overrides the `close` header button to instead collapse to a smaller size. The contents contents to show when in each state can be set using the ``setExpandedWidget`` and ``setCollapsedWidget``. .. note:: Do not use the base class ``QDockWidget.setWidget`` method to set the docks contents. Use set[Expanded|Collapsed]Widget instead. """ #: Emitted when the dock widget's expanded state changes. expandedChanged = Signal(bool) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__expandedWidget = None self.__collapsedWidget = None self.__expanded = True self.__trueMinimumWidth = -1 self.setFeatures(QDockWidget.DockWidgetClosable | \ QDockWidget.DockWidgetMovable) self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.featuresChanged.connect(self.__onFeaturesChanged) self.dockLocationChanged.connect(self.__onDockLocationChanged) # Use the toolbar horizontal extension button icon as the default # for the expand/collapse button icon = self.style().standardIcon( QStyle.SP_ToolBarHorizontalExtensionButton) # Mirror the icon transform = QTransform() transform = transform.scale(-1.0, 1.0) icon_rev = QIcon() for s in (8, 12, 14, 16, 18, 24, 32, 48, 64): pm = icon.pixmap(s, s) icon_rev.addPixmap(pm.transformed(transform)) self.__iconRight = QIcon(icon) self.__iconLeft = QIcon(icon_rev) close = self.findChild(QAbstractButton, name="qt_dockwidget_closebutton") close.installEventFilter(self) self.__closeButton = close self.__stack = AnimatedStackedWidget() self.__stack.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) self.__stack.transitionStarted.connect(self.__onTransitionStarted) self.__stack.transitionFinished.connect(self.__onTransitionFinished) super().setWidget(self.__stack) self.__closeButton.setIcon(self.__iconLeft)
[docs] def setExpanded(self, state): """ Set the widgets `expanded` state. """ if self.__expanded != state: self.__expanded = state if state and self.__expandedWidget is not None: log.debug("Dock expanding.") self.__stack.setCurrentWidget(self.__expandedWidget) elif not state and self.__collapsedWidget is not None: log.debug("Dock collapsing.") self.__stack.setCurrentWidget(self.__collapsedWidget) self.__fixIcon() self.expandedChanged.emit(state)
[docs] def expanded(self): """ Is the dock widget in expanded state. If `True` the ``expandedWidget`` will be shown, and ``collapsedWidget`` otherwise. """ return self.__expanded
expanded_ = Property(bool, fset=setExpanded, fget=expanded) def setWidget(self, w): raise NotImplementedError( "Please use the 'setExpandedWidget'/'setCollapsedWidget' " "methods to set the contents of the dock widget." )
[docs] def setExpandedWidget(self, widget): """ Set the widget with contents to show while expanded. """ if widget is self.__expandedWidget: return if self.__expandedWidget is not None: self.__stack.removeWidget(self.__expandedWidget) self.__stack.insertWidget(0, widget) self.__expandedWidget = widget if self.__expanded: self.__stack.setCurrentWidget(widget) self.updateGeometry()
[docs] def expandedWidget(self): """ Return the widget previously set with ``setExpandedWidget``, or ``None`` if no widget has been set. """ return self.__expandedWidget
[docs] def setCollapsedWidget(self, widget): """ Set the widget with contents to show while collapsed. """ if widget is self.__collapsedWidget: return if self.__collapsedWidget is not None: self.__stack.removeWidget(self.__collapsedWidget) self.__stack.insertWidget(1, widget) self.__collapsedWidget = widget if not self.__expanded: self.__stack.setCurrentWidget(widget) self.updateGeometry()
[docs] def collapsedWidget(self): """ Return the widget previously set with ``setCollapsedWidget``, or ``None`` if no widget has been set. """ return self.__collapsedWidget
def setAnimationEnabled(self, animationEnabled): """ Enable/disable the transition animation. """ self.__stack.setAnimationEnabled(animationEnabled) def animationEnabled(self): """ Is transition animation enabled. """ return self.__stack.animationEnabled()
[docs] def currentWidget(self): """ Return the current shown widget depending on the `expanded` state. """ if self.__expanded: return self.__expandedWidget else: return self.__collapsedWidget
[docs] def expand(self): """ Expand the dock (same as ``setExpanded(True)``) """ self.setExpanded(True)
[docs] def collapse(self): """ Collapse the dock (same as ``setExpanded(False)``) """ self.setExpanded(False)
[docs] def eventFilter(self, obj, event): if obj is self.__closeButton: etype = event.type() if etype == QEvent.MouseButtonPress: self.setExpanded(not self.__expanded) return True elif etype == QEvent.MouseButtonDblClick or \ etype == QEvent.MouseButtonRelease: return True # TODO: which other events can trigger the button (is the button # focusable). return super().eventFilter(obj, event)
[docs] def event(self, event): if event.type() == QEvent.LayoutRequest: self.__fixMinimumWidth() return super().event(event)
def __onFeaturesChanged(self, features): pass def __onDockLocationChanged(self, area): if area == Qt.LeftDockWidgetArea: self.setLayoutDirection(Qt.LeftToRight) else: self.setLayoutDirection(Qt.RightToLeft) self.__stack.setLayoutDirection(self.parentWidget().layoutDirection()) self.__fixIcon() def __onTransitionStarted(self): log.debug("Dock transition started.") def __onTransitionFinished(self): log.debug("Dock transition finished (new width %i)", self.size().width()) def __fixMinimumWidth(self): # A workaround for forcing the QDockWidget layout to disregard the # default minimumSize which can be to wide for us (overriding the # minimumSizeHint or setting the minimum size directly does not # seem to have an effect (Qt 4.8.3). size = self.__stack.sizeHint() if size.isValid() and not size.isEmpty(): left, _, right, _ = self.getContentsMargins() width = size.width() + left + right if width < self.minimumSizeHint().width(): if not self.__hasFixedWidth(): log.debug("Overriding default minimum size " "(setFixedWidth(%i))", width) self.__trueMinimumWidth = self.minimumSizeHint().width() self.setFixedWidth(width) else: if self.__hasFixedWidth(): if width >= self.__trueMinimumWidth: # Unset the fixed size. log.debug("Restoring default minimum size " "(setFixedWidth(%i))", QWIDGETSIZE_MAX) self.__trueMinimumWidth = -1 self.setFixedWidth(QWIDGETSIZE_MAX) self.updateGeometry() else: self.setFixedWidth(width) def __hasFixedWidth(self): return self.__trueMinimumWidth >= 0 def __fixIcon(self): """Fix the dock close icon. """ direction = self.layoutDirection() if direction == Qt.LeftToRight: if self.__expanded: icon = self.__iconLeft else: icon = self.__iconRight else: if self.__expanded: icon = self.__iconRight else: icon = self.__iconLeft self.__closeButton.setIcon(icon)