Source code for orangecanvas.gui.dock
"""
=======================
Collapsible Dock Widget
=======================
A dock widget that can be a collapsed/expanded.
"""
from typing import Optional, Any
from AnyQt.QtWidgets import (
QDockWidget, QAbstractButton, QSizePolicy, QStyle, QWidget, QWIDGETSIZE_MAX
)
from AnyQt.QtGui import QIcon, QTransform
from AnyQt.QtCore import Qt, QEvent, QObject
from AnyQt.QtCore import pyqtProperty as Property, pyqtSignal as Signal
from .stackedwidget import AnimatedStackedWidget
[docs]class CollapsibleDockWidget(QDockWidget):
"""
This :class:`QDockWidget` subclass overrides the `close` header
button to instead collapse to a smaller size. The contents to show
when in each state can be set using the :func:`setExpandedWidget`
and :func:`setCollapsedWidget`.
Note
----
Do not use the base class :func:`QDockWidget.setWidget` method to
set the dock contents. Use :func:`setExpandedWidget` and
:func:`setCollapsedWidget` instead.
"""
#: Emitted when the dock widget's expanded state changes.
expandedChanged = Signal(bool)
def __init__(self, *args, **kwargs):
# type: (Any, Any) -> None
super().__init__(*args, **kwargs)
self.__expandedWidget = None # type: Optional[QWidget]
self.__collapsedWidget = None # type: Optional[QWidget]
self.__expanded = True
self.__trueMinimumWidth = -1
self.setFeatures(QDockWidget.DockWidgetClosable |
QDockWidget.DockWidgetMovable)
self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
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)
# Find the close button an install an event filter or close event
close = self.findChild(QAbstractButton,
name="qt_dockwidget_closebutton")
assert close is not None
close.installEventFilter(self)
self.__closeButton = close
self.__stack = AnimatedStackedWidget()
self.__stack.setSizePolicy(QSizePolicy.Fixed,
QSizePolicy.Expanding)
super().setWidget(self.__stack)
self.__closeButton.setIcon(self.__iconLeft)
[docs] def setExpanded(self, state):
# type: (bool) -> None
"""
Set the widgets `expanded` state.
"""
if self.__expanded != state:
self.__expanded = state
if state and self.__expandedWidget is not None:
self.__stack.setCurrentWidget(self.__expandedWidget)
elif not state and self.__collapsedWidget is not None:
self.__stack.setCurrentWidget(self.__collapsedWidget)
self.__fixIcon()
self.expandedChanged.emit(state)
[docs] def expanded(self):
# type: () -> bool
"""
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):
# type: (QWidget) -> None
"""
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):
# type: () -> Optional[QWidget]
"""
Return the widget previously set with ``setExpandedWidget``,
or ``None`` if no widget has been set.
"""
return self.__expandedWidget
[docs] def setCollapsedWidget(self, widget):
# type: (QWidget) -> None
"""
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):
# type: () -> Optional[QWidget]
"""
Return the widget previously set with ``setCollapsedWidget``,
or ``None`` if no widget has been set.
"""
return self.__collapsedWidget
def setAnimationEnabled(self, animationEnabled):
self.__stack.setAnimationEnabled(animationEnabled)
def animationEnabled(self):
return self.__stack.animationEnabled()
[docs] def currentWidget(self):
# type: () -> Optional[QWidget]
"""
Return the current shown widget depending on the `expanded` state
"""
if self.__expanded:
return self.__expandedWidget
else:
return self.__collapsedWidget
[docs] def expand(self):
# type: () -> None
"""
Expand the dock (same as ``setExpanded(True)``)
"""
self.setExpanded(True)
[docs] def collapse(self):
# type: () -> None
"""
Collapse the dock (same as ``setExpanded(False)``)
"""
self.setExpanded(False)
[docs] def eventFilter(self, obj, event):
# type: (QObject, QEvent) -> bool
"""Reimplemented."""
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):
# type: (QEvent) -> bool
"""Reimplemented."""
if event.type() == QEvent.LayoutRequest:
self.__fixMinimumWidth()
return super().event(event)
def __onDockLocationChanged(self, area):
# type: (Qt.DockWidgetArea) -> None
if area == Qt.LeftDockWidgetArea:
self.setLayoutDirection(Qt.LeftToRight)
else:
self.setLayoutDirection(Qt.RightToLeft)
self.__stack.setLayoutDirection(self.parentWidget().layoutDirection())
self.__fixIcon()
def __fixMinimumWidth(self):
# type: () -> None
# 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():
margins = self.contentsMargins()
width = size.width() + margins.left() + margins.right()
if width < self.minimumSizeHint().width():
if not self.__hasFixedWidth():
self.__trueMinimumWidth = self.minimumSizeHint().width()
self.setFixedWidth(width)
else:
if self.__hasFixedWidth():
if width >= self.__trueMinimumWidth:
self.__trueMinimumWidth = -1
self.setFixedWidth(QWIDGETSIZE_MAX)
self.updateGeometry()
else:
self.setFixedWidth(width)
def __hasFixedWidth(self):
# type: () -> bool
return self.__trueMinimumWidth >= 0
def __fixIcon(self):
# type: () -> None
"""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)