class QDockBarItemHandle(QFrame): """ A frame which provides a resize border for a QDockBarItem. """ #: A signal emitted when the handle is moved. The payload is a #: QPoint which represents the delta drag distance. handleMoved = Signal(QPoint) def __init__(self, parent=None): """ Initialize a QDockBarItemHandle. Parameters ---------- parent : QWidget, optional The parent widget of the handle. """ super(QDockBarItemHandle, self).__init__(parent) policy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.setSizePolicy(policy) self._press_pos = QPoint() self._size_hint = QSize(5, 5) def sizeHint(self): """ Get the size hint for the widget. """ return self._size_hint def mousePressEvent(self, event): """ Handle the mouse press event for the widget. """ event.ignore() if event.button() == Qt.LeftButton: self._press_pos = event.pos() event.accept() def mouseReleaseEvent(self, event): """ Handle the mouse release event for the widget. """ event.ignore() if event.button() == Qt.LeftButton: self._press_pos = QPoint() event.accept() def mouseMoveEvent(self, event): """ Handle the mouse move event for the widget. """ event.ignore() if not self._press_pos.isNull(): self.handleMoved.emit(event.pos() - self._press_pos) event.accept()
class QDockTitleBar(QFrame, IDockTitleBar): """ A concrete implementation of IDockTitleBar. This class serves as the default title bar for a QDockItem. """ #: A signal emitted when the maximize button is clicked. maximizeButtonClicked = Signal(bool) #: A signal emitted when the restore button is clicked. restoreButtonClicked = Signal(bool) #: A signal emitted when the close button is clicked. closeButtonClicked = Signal(bool) #: A signal emitted when the link button is toggled. linkButtonToggled = Signal(bool) #: A signal emitted when the pin button is toggled. pinButtonToggled = Signal(bool) #: A signal emitted when the title is edited by the user. titleEdited = Signal(str) #: A signal emitted when the empty area is left double clicked. leftDoubleClicked = Signal(QPoint) #: A signal emitted when the empty area is right clicked. rightClicked = Signal(QPoint) def __init__(self, parent=None): """ Initialize a QDockTitleBar. Parameters ---------- parent : QWidget or None The parent of the title bar. """ super(QDockTitleBar, self).__init__(parent) self._buttons = self.CloseButton | self.MaximizeButton | self.PinButton self._is_editable = False self._force_hidden = False self._last_visible = True self._line_edit = None title_icon = self._title_icon = QIconWidget(self) title_icon.setVisible(False) title_label = self._title_label = QTextLabel(self) spacer = self._spacer = QWidget(self) policy = spacer.sizePolicy() policy.setHorizontalPolicy(QSizePolicy.Expanding) spacer.setSizePolicy(policy) btn_size = QSize(14, 13) max_button = self._max_button = QBitmapButton(self) max_button.setObjectName('docktitlebar-maximize-button') max_button.setBitmap(MAXIMIZE_BUTTON.toBitmap()) max_button.setIconSize(btn_size) max_button.setVisible(self._buttons & self.MaximizeButton) max_button.setToolTip('Maximize') restore_button = self._restore_button = QBitmapButton(self) restore_button.setObjectName('docktitlebar-restore-button') restore_button.setBitmap(RESTORE_BUTTON.toBitmap()) restore_button.setIconSize(btn_size) restore_button.setVisible(self._buttons & self.RestoreButton) restore_button.setToolTip('Restore Down') close_button = self._close_button = QBitmapButton(self) close_button.setObjectName('docktitlebar-close-button') close_button.setBitmap(CLOSE_BUTTON.toBitmap()) close_button.setIconSize(btn_size) close_button.setVisible(self._buttons & self.CloseButton) close_button.setToolTip('Close') link_button = self._link_button = QCheckedBitmapButton(self) link_button.setObjectName('docktitlebar-link-button') link_button.setBitmap(UNLINKED_BUTTON.toBitmap()) link_button.setCheckedBitmap(LINKED_BUTTON.toBitmap()) link_button.setIconSize(btn_size) link_button.setVisible(self._buttons & self.LinkButton) link_button.setToolTip('Link Window') link_button.setCheckedToolTip('Unlink Window') pin_button = self._pin_button = QCheckedBitmapButton(self) pin_button.setObjectName('docktitlebar-pin-button') pin_button.setBitmap(PIN_BUTTON.toBitmap()) pin_button.setCheckedBitmap(UNPIN_BUTTON.toBitmap()) pin_button.setIconSize(QSize(13, 13)) pin_button.setVisible(self._buttons & self.PinButton) pin_button.setToolTip('Pin Window') pin_button.setCheckedToolTip('Unpin Window') layout = QHBoxLayout() layout.setContentsMargins(QMargins(5, 2, 5, 2)) layout.setSpacing(1) layout.addWidget(title_icon) layout.addSpacing(0) layout.addWidget(title_label) layout.addWidget(spacer) layout.addSpacing(4) layout.addWidget(pin_button) layout.addWidget(link_button) layout.addWidget(max_button) layout.addWidget(restore_button) layout.addWidget(close_button) self.setLayout(layout) max_button.clicked.connect(self.maximizeButtonClicked) restore_button.clicked.connect(self.restoreButtonClicked) close_button.clicked.connect(self.closeButtonClicked) link_button.toggled.connect(self.linkButtonToggled) pin_button.toggled.connect(self.pinButtonToggled) #-------------------------------------------------------------------------- # Event Handlers #-------------------------------------------------------------------------- def mouseDoubleClickEvent(self, event): """ Handle the mouse double click event for the title bar. """ event.ignore() if event.button() == Qt.LeftButton: pos = event.pos() is_editable = self._is_editable if self._adjustedLabelGeometry().contains(pos) and is_editable: self._showTitleLineEdit() event.accept() return if self._clickableGeometry().contains(pos): self.leftDoubleClicked.emit(event.globalPos()) event.accept() return def mousePressEvent(self, event): """ Handle the mouse press event for the title bar. """ event.ignore() if event.button() == Qt.RightButton: if self._clickableGeometry().contains(event.pos()): self.rightClicked.emit(event.globalPos()) event.accept() return #-------------------------------------------------------------------------- # Overrides #-------------------------------------------------------------------------- def setVisible(self, visible): """ An overridden virtual visibility setter. This handler enforces the force-hidden setting. """ self._last_visible = visible if visible and self._force_hidden: return super(QDockTitleBar, self).setVisible(visible) #-------------------------------------------------------------------------- # Private API #-------------------------------------------------------------------------- def _adjustedLabelGeometry(self): """ Get the adjust label geometry. Returns ------- result : QRect A rectangle representing the label geometry which has been adjusted for potentially empty text. This rect can be used for a usable hit-testing rect for the label text. """ label = self._title_label label_geo = label.geometry() if not label.text(): label_geo = label_geo.adjusted(0, 0, 10, 0) return label_geo def _clickableGeometry(self): """ Get the geometry rect which represents clickable area. Returns ------- result : QRect A rectangle adjusted for the clickable geometry. """ rect = self.rect().adjusted(5, 2, -5, -2) rect.setRight(self._spacer.geometry().right()) return rect def _showTitleLineEdit(self): """ Setup the line edit widget for editing the title. """ old_line_edit = self._line_edit if old_line_edit is not None: old_line_edit.hide() old_line_edit.deleteLater() line_edit = self._line_edit = QLineEdit(self) line_edit.setFrame(False) line_edit.setText(self._title_label.text()) line_edit.selectAll() h = self._title_label.height() line_edit.setMinimumHeight(h) line_edit.setMaximumHeight(h) line_edit.editingFinished.connect(self._onEditingFinished) layout = self.layout() idx = layout.indexOf(self._spacer) layout.insertWidget(idx, line_edit) self._spacer.hide() self._title_label.hide() line_edit.show() line_edit.setFocus(Qt.MouseFocusReason) def _onEditingFinished(self): """ Handle the 'editingFinished' signal for title line edit. """ line_edit = self._line_edit if line_edit is not None: text = line_edit.text() line_edit.hide() line_edit.deleteLater() self._line_edit = None changed = self._title_label.text() != text if changed: self._title_label.setText(text) self._title_label.show() self._spacer.show() if changed: self.titleEdited.emit(text) #-------------------------------------------------------------------------- # IDockItemTitleBar API #-------------------------------------------------------------------------- def buttons(self): """ Get the buttons to show in the title bar. Returns ------- result : int An or'd combination of the buttons to show. """ return self._buttons def setButtons(self, buttons): """ Set the buttons to show in the title bar. Parameters ---------- buttons : int An or'd combination of the buttons to show. """ self._buttons = buttons self._max_button.setVisible(buttons & self.MaximizeButton) self._restore_button.setVisible(buttons & self.RestoreButton) self._close_button.setVisible(buttons & self.CloseButton) self._link_button.setVisible(buttons & self.LinkButton) self._pin_button.setVisible(buttons & self.PinButton) def title(self): """ Get the title string of the title bar. Returns ------- result : unicode The unicode title string for the title bar. """ return self._title_label.text() def setTitle(self, title): """ Set the title string of the title bar. Parameters ---------- title : unicode The unicode string to use for the title bar. """ self._title_label.setText(title) def label(self): """ Get the label which holds the title string. Returns ------- result : QTextLabel The label widget which holds the title string. """ return self._title_label def icon(self): """ Get the icon for the title bar. Returns ------- result : QIcon The icon set for the title bar. """ return self._title_icon.icon() def setIcon(self, icon): """ Set the icon for the title bar. Parameters ---------- icon : QIcon The icon to use for the title bar. """ visible, spacing = (False, 0) if icon.isNull() else (True, 4) title_icon = self._title_icon title_icon.setIcon(icon) title_icon.setVisible(visible) layout = self.layout() layout.takeAt(1) layout.insertSpacing(1, spacing) def iconSize(self): """ Get the icon size for the title bar. Returns ------- result : QSize The size to use for the icons in the title bar. """ return self._title_icon.iconSize() def setIconSize(self, size): """ Set the icon size for the title bar. Parameters ---------- icon : QSize The icon size to use for the title bar. Icons smaller than this size will not be scaled up. """ self._title_icon.setIconSize(size) def isLinked(self): """ Get whether the link button is checked. Returns ------- result : bool True if the link button is checked, False otherwise. """ return self._link_button.isChecked() def setLinked(self, linked): """ Set whether or not the link button is checked. Parameters ---------- linked : bool True if the link button should be checked, False otherwise. """ self._link_button.setChecked(linked) def isPinned(self): """ Get whether the pin button is checked. Returns ------- result : bool True if the pin button is checked, False otherwise. """ return self._pin_button.isChecked() def setPinned(self, pinned, quiet=False): """ Set whether or not the pin button is checked. Parameters ---------- pinned : bool True if the pin button should be checked, False otherwise. quiet : bool, optional True if the state should be set without emitted the toggled signal. The default is False. """ old = self._pin_button.blockSignals(quiet) self._pin_button.setChecked(pinned) self._pin_button.blockSignals(old) def isEditable(self): """ Get whether the title is user editable. Returns ------- result : bool True if the title is user editable, False otherwise. """ return self._is_editable def setEditable(self, editable): """ Set whether or not the title is user editable. Parameters ---------- editable : bool True if the title is user editable, False otherwise. """ self._is_editable = editable def isForceHidden(self): """ Get whether or not the title bar is force hidden. Returns ------- result : bool Whether or not the title bar is always hidden. """ return self._force_hidden def setForceHidden(self, hidden): """ Set the force hidden state of the title bar. Parameters ---------- hidden : bool True if the title bar should be hidden, False otherwise. """ self._force_hidden = hidden if not hidden and self._last_visible: super(QDockTitleBar, self).setVisible(True) elif hidden: super(QDockTitleBar, self).setVisible(False)
class IDockTitleBar(QWidget): """ An interface class for defining a title bar. """ #: A signal emitted when the maximize button is clicked. maximizeButtonClicked = Signal(bool) #: A signal emitted when the restore button is clicked. restoreButtonClicked = Signal(bool) #: A signal emitted when the close button is clicked. closeButtonClicked = Signal(bool) #: A signal emitted when the link button is toggled. linkButtonToggled = Signal(bool) #: A signal emitted when the pin button is toggled. pinButtonToggled = Signal(bool) #: A signal emitted when the title is edited by the user. titleEdited = Signal(str) #: A signal emitted when the title bar is left double clicked. leftDoubleClicked = Signal(QPoint) #: A signal emitted when the title bar is right clicked. rightClicked = Signal(QPoint) #: Do not show any buttons in the title bar. NoButtons = 0x0 #: Show the maximize button in the title bar. MaximizeButton = 0x1 #: Show the restore button in the title bar. RestoreButton = 0x2 #: Show the close button in the title bar. CloseButton = 0x4 #: Show the link button in the title bar. LinkButton = 0x8 #: Show the pin button in the title bar. PinButton = 0x10 def buttons(self): """ Get the buttons to show in the title bar. Returns ------- result : int An or'd combination of the buttons to show. """ raise NotImplementedError def setButtons(self, buttons): """ Set the buttons to show in the title bar. Parameters ---------- buttons : int An or'd combination of the buttons to show. """ raise NotImplementedError def title(self): """ Get the title string of the title bar. Returns ------- result : unicode The unicode title string for the title bar. """ raise NotImplementedError def setTitle(self, title): """ Set the title string of the title bar. Parameters ---------- title : unicode The unicode string to use for the title bar. """ raise NotImplementedError def label(self): """ Get the label for the title bar. Returns ------- result : QTextLabel The label for the title bar. """ raise NotImplementedError def icon(self): """ Get the icon for the title bar. Returns ------- result : QIcon The icon set for the title bar. """ raise NotImplementedError def setIcon(self, icon): """ Set the icon for the title bar. Parameters ---------- icon : QIcon The icon to use for the title bar. """ raise NotImplementedError def iconSize(self): """ Get the icon size for the title bar. Returns ------- result : QSize The size to use for the icons in the title bar. """ raise NotImplementedError def setIconSize(self, size): """ Set the icon size for the title bar. Parameters ---------- icon : QSize The icon size to use for the title bar. Icons smaller than this size will not be scaled up. """ raise NotImplementedError def isLinked(self): """ Get whether the link button is checked. Returns ------- result : bool True if the link button is checked, False otherwise. """ raise NotImplementedError def setLinked(self, linked): """ Set whether or not the link button is checked. Parameters ---------- linked : bool True if the link button should be checked, False otherwise. """ raise NotImplementedError def isPinned(self): """ Get whether the pin button is checked. Returns ------- result : bool True if the pin button is checked, False otherwise. """ raise NotImplementedError def setPinned(self, pinned, quiet=False): """ Set whether or not the pin button is checked. Parameters ---------- pinned : bool True if the pin button should be checked, False otherwise. quiet : bool, optional True if the state should be set without emitted the toggled signal. The default is False. """ raise NotImplementedError def isEditable(self): """ Get whether the title is user editable. Returns ------- result : bool True if the title is user editable, False otherwise. """ raise NotImplementedError def setEditable(self, editable): """ Set whether or not the title is user editable. Parameters ---------- editable : bool True if the title is user editable, False otherwise. """ raise NotImplementedError def isForceHidden(self): """ Get whether or not the title bar is force hidden. Returns ------- result : bool Whether or not the title bar is force hidden. """ raise NotImplementedError def setForceHidden(self, hidden): """ Set the force hidden state of the title bar. Parameters ---------- hidden : bool True if the title bar should be hidden, False otherwise. """ raise NotImplementedError
class QDockFrame(QFrame): """ A QFrame base class for creating dock frames. """ #: No resize border. NoBorder = 0 #: Resize the window vertically from the north edge. NorthBorder = 1 #: Resize the window horizontally from the east edge. EastBorder = 2 #: Resize the window vertically from the south edge. SouthBorder = 3 #: Resize the window horizontally from the west edge. WestBorder = 4 #: Resize the window diagonally from the northeast edge. NorthEastBorder = 5 #: Resize the window diagonally from the northwest edge. NorthWestBorder = 6 #: Resize the window diagonally from the southeast edge. SouthEastBorder = 7 #: Resize the window diagonally from the southwest edge. SouthWestBorder = 8 #: The cursors to use for a given resize border. ResizeCursors = { NorthBorder: Qt.SizeVerCursor, SouthBorder: Qt.SizeVerCursor, EastBorder: Qt.SizeHorCursor, WestBorder: Qt.SizeHorCursor, NorthEastBorder: Qt.SizeBDiagCursor, SouthWestBorder: Qt.SizeBDiagCursor, NorthWestBorder: Qt.SizeFDiagCursor, SouthEastBorder: Qt.SizeFDiagCursor, } #: The handlers to use for resizing the frame. ResizeHandlers = { NorthBorder: '_resizeNorth', SouthBorder: '_resizeSouth', EastBorder: '_resizeEast', WestBorder: '_resizeWest', NorthEastBorder: '_resizeNortheast', SouthWestBorder: '_resizeSouthwest', NorthWestBorder: '_resizeNorthwest', SouthEastBorder: '_resizeSoutheast', } #: The size of the extra space for hit testing a resize corner. ResizeCornerExtra = 8 #: A signal emitted when the linked button is toggled. This should #: be emitted at the appropriate times by a subclass. linkButtonToggled = Signal(bool) class FrameState(Atom): """ A private class for tracking dock frame state. """ #: Whether the title bar is consuming the mouse events. mouse_title = Bool(False) #: The resize border based on the mouse hover position. resize_border = Int(0) #: The last size of the frame before a resize. last_size = Typed(QSize) #: The offset point of the cursor during a resize press. resize_offset = Typed(QPoint) def __init__(self, manager, parent=None): """ Initialize a QDockFrame. Parameters ---------- manager : DockManager The manager which owns the frame. parent : QWidget or None The parent of the QDockFrame. """ super(QDockFrame, self).__init__(parent) self.frame_state = self.FrameState() self._manager = manager def manager(self): """ Get a reference to the manager which owns the frame. Returns ------- result : DockManager The dock manager which owns this dock frame. """ return self._manager def raiseFrame(self): """ Raise this frame to the top of the dock manager Z-order. """ manager = self._manager if manager is not None: manager.raise_frame(self) def titleBarGeometry(self): """ Get the geometry rect for the title bar. Returns ------- result : QRect The geometry rect for the title bar, expressed in frame coordinates. An invalid rect should be returned if title bar should not be active. """ return QRect() def resizeMargins(self): """ Get the margins to use for resizing the frame. Returns ------- result : QMargins The margins to use for frame resizing when the frame is a top-level window. """ return QMargins() def isLinked(self): """ Get whether or not the frame is linked. This method should be reimplemented by a subclass. Returns ------- result : bool True if the frame is considered linked, False otherwise. """ return False def setLinked(self, linked): """ Set whether or not the frame is linked. This method should be reimplemented by a subclass. Parameters ---------- linked : bool True if the frame is considered linked, False otherwise. """ pass #-------------------------------------------------------------------------- # Event Handlers #-------------------------------------------------------------------------- def event(self, event): """ Handle the generic events for the frame. This handler maintains proper Z-order of the frames within the manager's frame list and exposes some custom event handlers appropriate for dock frames. """ if event.type() == QEvent.HoverMove: self.hoverMoveEvent(event) return event.isAccepted() if event.type() == QEvent.WindowActivate and self.isWindow(): self.raiseFrame() return super(QDockFrame, self).event(event) def mousePressEvent(self, event): """ Handle the mouse press event for the dock frame. """ event.ignore() state = self.frame_state geo = self.titleBarGeometry() if geo.isValid() and geo.contains(event.pos()): if self.titleBarMousePressEvent(event): if self.isWindow(): self.activateWindow() self.raise_() event.accept() state.mouse_title = True return if self.isWindow() and event.button() == Qt.LeftButton: border, offset = self._resizeBorderTest(event.pos()) if border != self.NoBorder: state.resize_border = border state.resize_offset = offset state.last_size = self.size() event.accept() def mouseMoveEvent(self, event): """ Handle the mouse move event for the dock frame. """ event.ignore() state = self.frame_state if state.mouse_title: if self.titleBarMouseMoveEvent(event): event.accept() return if self.isWindow() and state.resize_border != self.NoBorder: border = state.resize_border handler = getattr(self, self.ResizeHandlers[border]) handler(event.pos(), state.resize_offset) event.accept() def mouseReleaseEvent(self, event): """ Handle the mouse release event for the dock frame. """ event.ignore() state = self.frame_state self._refreshCursor(event.pos()) if state.mouse_title: if self.titleBarMouseReleaseEvent(event): event.accept() state.mouse_title = False return if self.isWindow() and event.button() == Qt.LeftButton: state.resize_border = self.NoBorder state.resize_offset = None if state.last_size is not None: if state.last_size != self.size(): self.manager().frame_resized(self) del state.last_size event.accept() def hoverMoveEvent(self, event): """ Handle the hover move event for the frame. """ event.ignore() if not self.isWindow() or self.isMaximized(): return if QApplication.mouseButtons() != Qt.NoButton: return state = self.frame_state if state.mouse_title: return if state.resize_border != self.NoBorder: return self._refreshCursor(event.pos()) event.accept() def titleBarMousePressEvent(self, event): """ Handle a mouse press event on the title bar. Returns ------- result : bool True if the event is handled, False otherwise. """ return False def titleBarMouseMoveEvent(self, event): """ Handle a mouse move event on the title bar. Returns ------- result : bool True if the event is handled, False otherwise. """ return False def titleBarMouseReleaseEvent(self, event): """ Handle a mouse release event on the title bar. Returns ------- result : bool True if the event is handled, False otherwise. """ return False #-------------------------------------------------------------------------- # Resize Handling #-------------------------------------------------------------------------- def _refreshCursor(self, pos): """ Refresh the resize cursor for the given position. Parameters ---------- pos : QPoint The point of interest, expressed in local coordinates. """ border, ignored = self._resizeBorderTest(pos) cursor = self.ResizeCursors.get(border) if cursor is None: self.unsetCursor() else: self.setCursor(cursor) def _resizeBorderTest(self, pos): """ Hit test the frame for resizing. Parameters ---------- pos : QPoint The point of interest, expressed in local coordinates. Returns ------- result : tuple A 2-tuple of (int, QPoint) representing the resize border and offset for the border. """ rect = self.rect() if not rect.contains(pos): return (self.NoBorder, QPoint()) x = pos.x() y = pos.y() width = rect.width() height = rect.height() margins = self.resizeMargins() extra = self.ResizeCornerExtra if x < margins.left(): if y < margins.top() + extra: mode = self.NorthWestBorder offset = QPoint(x, y) elif y > height - (margins.bottom() + extra): mode = self.SouthWestBorder offset = QPoint(x, height - y) else: mode = self.WestBorder offset = QPoint(x, 0) elif y < margins.top(): if x < margins.left() + extra: mode = self.NorthWestBorder offset = QPoint(x, y) elif x > width - (margins.right() + extra): mode = self.NorthEastBorder offset = QPoint(width - x, y) else: mode = self.NorthBorder offset = QPoint(0, y) elif x > width - margins.right(): if y < margins.top() + extra: mode = self.NorthEastBorder offset = QPoint(width - x, y) elif y > height - (margins.bottom() + extra): mode = self.SouthEastBorder offset = QPoint(width - x, height - y) else: mode = self.EastBorder offset = QPoint(width - x, 0) elif y > height - margins.bottom(): if x < margins.left() + extra: mode = self.SouthWestBorder offset = QPoint(x, height - y) elif x > width - (margins.right() + extra): mode = self.SouthEastBorder offset = QPoint(width - x, height - y) else: mode = self.SouthBorder offset = QPoint(0, height - y) else: mode = self.NoBorder offset = QPoint() return mode, offset def _resizeNorth(self, pos, offset): """ A resize handler for north resizing. """ dh = pos.y() - offset.y() height = self.height() min_height = self.minimumSizeHint().height() if height - dh < min_height: dh = height - min_height rect = self.geometry() rect.setY(rect.y() + dh) self.setGeometry(rect) def _resizeSouth(self, pos, offset): """ A resize handler for south resizing. """ dh = pos.y() - self.height() + offset.y() size = self.size() size.setHeight(size.height() + dh) self.resize(size) def _resizeEast(self, pos, offset): """ A resize handler for east resizing. """ dw = pos.x() - self.width() + offset.x() size = self.size() size.setWidth(size.width() + dw) self.resize(size) def _resizeWest(self, pos, offset): """ A resize handler for west resizing. """ dw = pos.x() - offset.x() width = self.width() min_width = self.minimumSizeHint().width() if width - dw < min_width: dw = width - min_width rect = self.geometry() rect.setX(rect.x() + dw) self.setGeometry(rect) def _resizeNortheast(self, pos, offset): """ A resize handler for northeast resizing. """ dw = pos.x() - self.width() + offset.x() dh = pos.y() - offset.y() size = self.size() min_size = self.minimumSizeHint() if size.height() - dh < min_size.height(): dh = size.height() - min_size.height() rect = self.geometry() rect.setWidth(rect.width() + dw) rect.setY(rect.y() + dh) self.setGeometry(rect) def _resizeNorthwest(self, pos, offset): """ A resize handler for northwest resizing. """ dw = pos.x() - offset.x() dh = pos.y() - offset.y() size = self.size() min_size = self.minimumSizeHint() if size.width() - dw < min_size.width(): dw = size.width() - min_size.width() if size.height() - dh < min_size.height(): dh = size.height() - min_size.height() rect = self.geometry() rect.setX(rect.x() + dw) rect.setY(rect.y() + dh) self.setGeometry(rect) def _resizeSouthwest(self, pos, offset): """ A resize handler for southwest resizing. """ dw = pos.x() - offset.x() dh = pos.y() - self.height() + offset.y() size = self.size() min_size = self.minimumSizeHint() if size.width() - dw < min_size.width(): dw = size.width() - min_size.width() rect = self.geometry() rect.setX(rect.x() + dw) rect.setHeight(rect.height() + dh) self.setGeometry(rect) def _resizeSoutheast(self, pos, offset): """ A resize handler for southeast resizing. """ dw = pos.x() - self.width() + offset.x() dh = pos.y() - self.height() + offset.y() size = self.size() size.setWidth(size.width() + dw) size.setHeight(size.height() + dh) self.resize(size)
class QDockWindowButtons(QFrame): """ A custom QFrame which provides the buttons for a QDockWindow. """ #: A signal emitted when the maximize button is clicked. maximizeButtonClicked = Signal(bool) #: A signal emitted when the restore button is clicked. restoreButtonClicked = Signal(bool) #: A signal emitted when the close button is closed. closeButtonClicked = Signal(bool) #: A signal emitted when the link button is toggled. linkButtonToggled = Signal(bool) #: Do not show any buttons in the widget. NoButtons = 0x0 #: Show the maximize button in the widget. MaximizeButton = 0x1 #: Show the restore button in the widget. RestoreButton = 0x2 #: Show the close button in the widget. CloseButton = 0x4 #: Show the link button in the widget. LinkButton = 0x8 def __init__(self, parent=None): """ Initialize a QDockWindowButtons instance. Parameters ---------- parent : QWidget, optional The parent of the window buttons. """ super(QDockWindowButtons, self).__init__(parent) self._buttons = (self.CloseButton | self.MaximizeButton | self.LinkButton) max_button = self._max_button = QBitmapButton(self) max_button.setObjectName('dockwindow-maximize-button') max_button.setBitmap(MAXIMIZE_BUTTON.toBitmap()) max_button.setIconSize(QSize(20, 15)) max_button.setVisible(self._buttons & self.MaximizeButton) max_button.setToolTip('Maximize') restore_button = self._restore_button = QBitmapButton(self) restore_button.setObjectName('dockwindow-restore-button') restore_button.setBitmap(RESTORE_BUTTON.toBitmap()) restore_button.setIconSize(QSize(20, 15)) restore_button.setVisible(self._buttons & self.RestoreButton) restore_button.setToolTip('Restore Down') close_button = self._close_button = QBitmapButton(self) close_button.setObjectName('dockwindow-close-button') close_button.setBitmap(CLOSE_BUTTON.toBitmap()) close_button.setIconSize(QSize(34, 15)) close_button.setVisible(self._buttons & self.CloseButton) close_button.setToolTip('Close') link_button = self._link_button = QCheckedBitmapButton(self) link_button.setObjectName('dockwindow-link-button') link_button.setBitmap(UNLINKED_BUTTON.toBitmap()) link_button.setCheckedBitmap(LINKED_BUTTON.toBitmap()) link_button.setIconSize(QSize(20, 15)) link_button.setVisible(self._buttons & self.LinkButton) link_button.setToolTip('Link Window') link_button.setCheckedToolTip('Unlink Window') layout = QHBoxLayout() layout.setContentsMargins(QMargins(0, 0, 0, 0)) layout.setSpacing(1) layout.addWidget(link_button) layout.addWidget(max_button) layout.addWidget(restore_button) layout.addWidget(close_button) self.setLayout(layout) max_button.clicked.connect(self.maximizeButtonClicked) restore_button.clicked.connect(self.restoreButtonClicked) close_button.clicked.connect(self.closeButtonClicked) link_button.toggled.connect(self.linkButtonToggled) #-------------------------------------------------------------------------- # Public API #-------------------------------------------------------------------------- def buttons(self): """ Get the buttons to show in the title bar. Returns ------- result : int An or'd combination of the buttons to show. """ return self._buttons def setButtons(self, buttons): """ Set the buttons to show in the title bar. Parameters ---------- buttons : int An or'd combination of the buttons to show. """ self._buttons = buttons self._max_button.setVisible(buttons & self.MaximizeButton) self._restore_button.setVisible(buttons & self.RestoreButton) self._close_button.setVisible(buttons & self.CloseButton) self._link_button.setVisible(buttons & self.LinkButton) def isLinked(self): """ Get whether the link button is checked. Returns ------- result : bool True if the link button is checked, False otherwise. """ return self._link_button.isChecked() def setLinked(self, linked): """ Set whether or not the link button is checked. Parameters ---------- linked : bool True if the link button should be checked, False otherwise. """ self._link_button.setChecked(linked)
class QDockContainer(QDockFrame): """ A QDockFrame which holds a QDockItem instance. A QDockContainer has a dynamic boolean property 'floating' which can be used to apply custom stylesheet styling when the container is a floating top level window versus docked in a dock area. """ #: A signal emitted when the container changes its toplevel state. topLevelChanged = Signal(bool) #: A signal emitted when the container is alerted. alerted = Signal(str) class FrameState(QDockFrame.FrameState): """ A private class for managing container drag state. """ #: The original title bar press position. press_pos = Typed(QPoint) #: The position of the frame when first moved. start_pos = Typed(QPoint) #: Whether or not the dock item is being dragged. dragging = Bool(False) #: Whether the frame was maximized before moving. frame_was_maximized = Bool(False) #: Whether the dock item is maximized in the dock area. item_is_maximized = Bool(False) #: Whether or not the container is stored in a dock bar. This #: value is manipulated directly by the QDockBarManager. in_dock_bar = Bool(False) def __init__(self, manager, parent=None): """ Initialize a QDockContainer. Parameters ---------- manager : DockManager The manager which owns the container. parent : QWidget or None The parent of the QDockContainer. """ super(QDockContainer, self).__init__(manager, parent) layout = QDockContainerLayout() layout.setSizeConstraint(QLayout.SetMinAndMaxSize) self.setLayout(layout) self.setProperty('floating', False) self.alerted.connect(self.onAlerted) self._dock_item = None def titleBarGeometry(self): """ Get the geometry rect for the title bar. Returns ------- result : QRect The geometry rect for the title bar, expressed in frame coordinates. An invalid rect is returned if title bar should not be active. """ title_bar = self.dockItem().titleBarWidget() if title_bar.isHidden(): return QRect() pt = title_bar.mapTo(self, QPoint(0, 0)) return QRect(pt, title_bar.size()) def resizeMargins(self): """ Get the margins to use for resizing the container. Returns ------- result : QMargins The margins to use for container resizing when the container is a top-level window. """ if self.isMaximized(): return QMargins() return self.layout().contentsMargins() def showMaximized(self): """ Handle the show maximized request for the dock container. """ def update_buttons(bar, link=False, pin=False): buttons = bar.buttons() buttons |= bar.RestoreButton buttons &= ~bar.MaximizeButton if link: buttons &= ~bar.LinkButton if pin: buttons &= ~bar.PinButton bar.setButtons(buttons) if self.isWindow(): super(QDockContainer, self).showMaximized() self.setLinked(False) update_buttons(self.dockItem().titleBarWidget(), link=True) else: area = self.parentDockArea() if area is not None: item = self.dockItem() update_buttons(item.titleBarWidget(), pin=True) area.setMaximizedWidget(item) self.frame_state.item_is_maximized = True item.installEventFilter(self) def showNormal(self): """ Handle the show normal request for the dock container. """ def update_buttons(bar, link=False, pin=False): buttons = bar.buttons() buttons |= bar.MaximizeButton buttons &= ~bar.RestoreButton if link: buttons |= bar.LinkButton if pin: buttons |= bar.PinButton bar.setButtons(buttons) if self.isWindow(): super(QDockContainer, self).showNormal() self.setLinked(False) update_buttons(self.dockItem().titleBarWidget(), link=True) elif self.frame_state.item_is_maximized: item = self.dockItem() update_buttons(item.titleBarWidget(), pin=True) self.layout().setWidget(item) self.frame_state.item_is_maximized = False item.removeEventFilter(self) #-------------------------------------------------------------------------- # Framework API #-------------------------------------------------------------------------- def dockItem(self): """ Get the dock item installed on the container. Returns ------- result : QDockItem or None The dock item installed in the container, or None. """ return self._dock_item def setDockItem(self, dock_item): """ Set the dock item for the container. Parameters ---------- dock_item : QDockItem The dock item to use in the container. """ layout = self.layout() old = layout.getWidget() if old is not None: old.maximizeButtonClicked.disconnect(self.showMaximized) old.restoreButtonClicked.disconnect(self.showNormal) old.closeButtonClicked.disconnect(self.close) old.linkButtonToggled.disconnect(self.linkButtonToggled) old.pinButtonToggled.disconnect(self.onPinButtonToggled) old.titleBarLeftDoubleClicked.disconnect(self.toggleMaximized) old.alerted.disconnect(self.alerted) if dock_item is not None: dock_item.maximizeButtonClicked.connect(self.showMaximized) dock_item.restoreButtonClicked.connect(self.showNormal) dock_item.closeButtonClicked.connect(self.close) dock_item.linkButtonToggled.connect(self.linkButtonToggled) dock_item.pinButtonToggled.connect(self.onPinButtonToggled) dock_item.titleBarLeftDoubleClicked.connect(self.toggleMaximized) dock_item.alerted.connect(self.alerted) layout.setWidget(dock_item) self._dock_item = dock_item def title(self): """ Get the title for the container. This proxies the call to the underlying dock item. """ item = self.dockItem() if item is not None: return item.title() return u'' def icon(self): """ Get the icon for the container. This proxies the call to the underlying dock item. """ item = self.dockItem() if item is not None: return item.icon() return QIcon() def closable(self): """ Get whether or not the container is closable. This proxies the call to the underlying dock item. """ item = self.dockItem() if item is not None: return item.closable() return True def isLinked(self): """ Get whether or not the container is linked. This proxies the call to the underlying dock item. """ item = self.dockItem() if item is not None: return item.isLinked() return False def setLinked(self, linked): """ Set whether or not the container should be linked. This proxies the call to the underlying dock item. """ item = self.dockItem() if item is not None: item.setLinked(linked) def isPinned(self): """ Get whether or not the container is pinned. This proxies the call to the underlying dock item. """ item = self.dockItem() if item is not None: return item.isPinned() return False def setPinned(self, pinned, quiet=False): """ Set whether or not the container should be pinned. This proxies the call to the underlying dock item. """ item = self.dockItem() if item is not None: item.setPinned(pinned, quiet) def showTitleBar(self): """ Show the title bar for the container. This proxies the call to the underlying dock item. """ item = self.dockItem() if item is not None: item.titleBarWidget().show() def hideTitleBar(self): """ Hide the title bar for the container. This proxies the call to the underlying dock item. """ item = self.dockItem() if item is not None: item.titleBarWidget().hide() def showLinkButton(self): """ Show the link button on the title bar. """ item = self.dockItem() if item is not None: bar = item.titleBarWidget() bar.setButtons(bar.buttons() | bar.LinkButton) def hideLinkButton(self): """ Hide the link button on the title bar. """ item = self.dockItem() if item is not None: bar = item.titleBarWidget() bar.setButtons(bar.buttons() & ~bar.LinkButton) def showPinButton(self): """ Show the pin button on the title bar. """ item = self.dockItem() if item is not None: bar = item.titleBarWidget() bar.setButtons(bar.buttons() | bar.PinButton) def hidePinButton(self): """ Hide the pin button on the title bar. """ item = self.dockItem() if item is not None: bar = item.titleBarWidget() bar.setButtons(bar.buttons() & ~bar.PinButton) def toggleMaximized(self): """ Toggle the maximized state of the container. """ is_win = self.isWindow() is_maxed = self.isMaximized() item_maxed = self.frame_state.item_is_maximized if is_win and is_maxed or item_maxed: self.showNormal() else: self.showMaximized() def reset(self): """ Reset the container to the initial pre-docked state. """ state = self.frame_state state.dragging = False state.press_pos = None state.start_pos = None state.frame_was_maximized = False state.in_dock_bar = False self.showNormal() self.unfloat() self.hideLinkButton() self.setLinked(False) self.showTitleBar() self.setAttribute(Qt.WA_WState_ExplicitShowHide, False) self.setAttribute(Qt.WA_WState_Hidden, False) def float(self): """ Set the window state to be a toplevel floating window. """ self.hide() self.setAttribute(Qt.WA_Hover, True) flags = Qt.Tool | Qt.FramelessWindowHint self.setParent(self.manager().dock_area(), flags) self.layout().setContentsMargins(QMargins(5, 5, 5, 5)) self.setProperty('floating', True) self.setLinked(False) self.showLinkButton() self.hidePinButton() repolish(self) repolish(self.dockItem()) self.topLevelChanged.emit(True) def unfloat(self): """ Set the window state to be non-floating window. """ self.hide() self.setAttribute(Qt.WA_Hover, False) self.setParent(self.manager().dock_area(), Qt.Widget) self.layout().setContentsMargins(QMargins(0, 0, 0, 0)) self.unsetCursor() self.setProperty('floating', False) self.setLinked(False) self.hideLinkButton() self.showPinButton() repolish(self) repolish(self.dockItem()) self.topLevelChanged.emit(False) def parentDockArea(self): """ Get the parent dock area of the container. Returns ------- result : QDockArea or None The nearest ancestor which is an instance of QDockArea, or None if no such ancestor exists. """ parent = self.parent() while parent is not None: if isinstance(parent, QDockArea): return parent parent = parent.parent() def parentDockTabWidget(self): """ Get the parent dock tab of the container. Returns ------- result : QDockTabWidget or None The nearest ancestor which is an instance of QDockTabWidget, or None if no such ancestor exists. """ parent = self.parent() parent_area = self.parentDockArea() while parent is not None: if isinstance(parent, QDockTabWidget): return parent elif parent is parent_area: return None parent = parent.parent() def unplug(self): """ Unplug the container from its containing dock area. This method is invoked by the framework when appropriate. It should not need to be called by user code. Returns ------- result : bool True if the container was unplugged, False otherwise. """ dock_area = self.parentDockArea() if dock_area is None: return False if self.frame_state.in_dock_bar: dock_area.removeFromDockBar(self) return True # avoid a circular import from .layout_handling import unplug_container return unplug_container(dock_area, self) def untab(self, pos): """ Unplug the container from a tab control. This method is invoked by the QDockTabBar when the container should be torn out. It synthesizes the appropriate internal state so that the item can continue to be dock dragged. This method should not be called by user code. Parameters ---------- pos : QPoint The global mouse position. Returns ------- result : bool True on success, False otherwise. """ if not self.unplug(): return self.postUndockedEvent() state = self.frame_state state.mouse_title = True state.dragging = True state.frame_was_maximized = False self.float() self.raiseFrame() title_bar = self.dockItem().titleBarWidget() title_pos = QPoint(title_bar.width() / 2, title_bar.height() / 2) margins = self.layout().contentsMargins() offset = QPoint(margins.left(), margins.top()) state.press_pos = title_bar.mapTo(self, title_pos) + offset state.start_pos = pos - state.press_pos self.move(state.start_pos) self.show() self.grabMouse() self.activateWindow() self.raise_() def postUndockedEvent(self): """ Post a DockItemUndocked event to the root dock area. """ root_area = self.manager().dock_area() if root_area.dockEventsEnabled(): event = QDockItemEvent(DockItemUndocked, self.objectName()) QApplication.postEvent(root_area, event) #-------------------------------------------------------------------------- # Signal Handlers #-------------------------------------------------------------------------- def onPinButtonToggled(self, pinned): """ The signal handler for the 'pinButtonToggled' signal. This handler will pin or unpin the container in response to the user toggling the pin button. """ area = self.parentDockArea() if area is not None: if pinned: if not self.frame_state.in_dock_bar: position = _closestDockBar(self) self.unplug() area.addToDockBar(self, position) else: position = area.dockBarPosition(self) if position is not None: # avoid a circular import from .layout_handling import plug_frame area.removeFromDockBar(self) if area.centralWidget() is None: guide = QGuideRose.Guide.AreaCenter else: guide = (QGuideRose.Guide.BorderNorth, QGuideRose.Guide.BorderEast, QGuideRose.Guide.BorderSouth, QGuideRose.Guide.BorderWest)[position] plug_frame(area, None, self, guide) def onAlerted(self, level): """ A signal handler for the 'alerted' signal. """ self.setProperty('alert', level or None) repolish(self) #-------------------------------------------------------------------------- # Event Handlers #-------------------------------------------------------------------------- def eventFilter(self, obj, event): """ Filter the events for the dock item. This filter will proxy out the mouse events for the dock item. This event filter will only be activated when the dock item is set to maximzed mode. """ if obj is not self._dock_item: return False if event.type() == QEvent.MouseButtonPress: if event.button() == Qt.LeftButton: self._dock_item.clearAlert( ) # likely a no-op, but just in case return self.filteredMousePressEvent(event) elif event.type() == QEvent.MouseMove: return self.filteredMouseMoveEvent(event) elif event.type() == QEvent.MouseButtonRelease: return self.filteredMouseReleaseEvent(event) return False def filteredMousePressEvent(self, event): """ Handle the filtered mouse press event for the dock item. """ bar = self.dockItem().titleBarWidget() if bar.isVisible() and bar.geometry().contains(event.pos()): self.frame_state.mouse_title = True return self.titleBarMousePressEvent(event) return False def filteredMouseMoveEvent(self, event): """ Handle the filtered mouse move event for the dock item. """ if self.frame_state.mouse_title: return self.titleBarMouseMoveEvent(event) return False def filteredMouseReleaseEvent(self, event): """ Handle the filtered mouse release event for the dock item. """ if self.frame_state.mouse_title: self.frame_state.mouse_title = False return self.titleBarMouseReleaseEvent(event) return False def closeEvent(self, event): """ Handle the close event for the dock container. """ self.manager().close_container(self, event) def keyPressEvent(self, event): """ Handle the key press event for the dock container. If the Escape key is pressed while dragging a floating container, the container will be released. If it is not released over a dock target, it will be moved back to its starting position. """ super(QDockContainer, self).keyPressEvent(event) state = self.frame_state if state.dragging and event.key() == Qt.Key_Escape: pos = state.start_pos self._releaseFrame() if self.isWindow() and pos is not None: self.move(pos) if state.frame_was_maximized: self.showMaximized() def titleBarMousePressEvent(self, event): """ Handle a mouse press event on the title bar. Returns ------- result : bool True if the event is handled, False otherwise. """ if event.button() == Qt.LeftButton: state = self.frame_state if state.press_pos is None: state.press_pos = event.pos() state.start_pos = self.pos() return True return False def titleBarMouseMoveEvent(self, event): """ Handle a mouse move event on the title bar. Returns ------- result : bool True if the event is handled, False otherwise. """ state = self.frame_state if state.press_pos is None: return False # If dragging and floating, move the container's position and # notify the manager of that the container was mouse moved. If # the container is maximized, it is first restored before. global_pos = event.globalPos() if state.dragging: if self.isWindow(): target_pos = global_pos - state.press_pos self.manager().drag_move_frame(self, target_pos, global_pos) return True # Ensure the drag has crossed the app drag threshold. dist = (event.pos() - state.press_pos).manhattanLength() if dist <= QApplication.startDragDistance(): return True # If the container is already floating, ensure that it is shown # normal size. The next move event will move the window. state.dragging = True if self.isWindow(): state.frame_was_maximized = self.isMaximized() if state.frame_was_maximized: coeff = state.press_pos.x() / float(self.width()) self.showNormal() state.press_pos = _computePressPos(self, coeff) return True # Restore a maximized dock item before unplugging. if state.item_is_maximized: bar = self.dockItem().titleBarWidget() coeff = state.press_pos.x() / float(bar.width()) self.showNormal() state.press_pos = _computePressPos(self, coeff) # Unplug the container from the layout before floating so # that layout widgets can clean themselves up when empty. if not self.unplug(): return False self.postUndockedEvent() # Make the container a toplevel frame, update it's Z-order, # and grab the mouse to continue processing drag events. self.float() self.raiseFrame() margins = self.layout().contentsMargins() state.press_pos += QPoint(0, margins.top()) state.start_pos = global_pos - state.press_pos self.move(state.start_pos) self.show() self.grabMouse() self.activateWindow() self.raise_() return True def _releaseFrame(self): """ A helper method which releases the frame grab. Returns ------- result : bool True if the frame was released, False otherwise. """ state = self.frame_state if state.press_pos is not None: self.releaseMouse() if self.isWindow(): self.manager().drag_release_frame(self, QCursor.pos()) state.dragging = False state.press_pos = None state.start_pos = None return True return False def titleBarMouseReleaseEvent(self, event): """ Handle a mouse release event on the title bar. Returns ------- result : bool True if the event is handled, False otherwise. """ if event.button() == Qt.LeftButton: return self._releaseFrame() return False
class QDockItem(QFrame): """ A QFrame subclass which acts as an item QDockArea. """ #: A signal emitted when the maximize button is clicked. This #: signal is proxied from the current dock item title bar. maximizeButtonClicked = Signal(bool) #: A signal emitted when the restore button is clicked. This #: signal is proxied from the current dock item title bar. restoreButtonClicked = Signal(bool) #: A signal emitted when the close button is clicked. This #: signal is proxied from the current dock item title bar. closeButtonClicked = Signal(bool) #: A signal emitted when the link button is toggled. This #: signal is proxied from the current dock item title bar. linkButtonToggled = Signal(bool) #: A signal emitted when the pin button is toggled. This #: signal is proxied from the current dock item title bar. pinButtonToggled = Signal(bool) #: A signal emitted when the title is edited by the user. This #: signal is proxied from the current dock item title bar. titleEdited = Signal(str) #: A signal emitted when the empty area is left double clicked. #: This signal is proxied from the current dock item title bar. titleBarLeftDoubleClicked = Signal(QPoint) #: A signal emitted when the empty area is right clicked. This #: signal is proxied from the current dock item title bar. titleBarRightClicked = Signal(QPoint) #: A signal emitted when the item is alerted. The payload is the #: new alert level. An empty string indicates no alert. alerted = Signal(str) def __init__(self, parent=None): """ Initialize a QDockItem. Parameters ---------- parent : QWidget, optional The parent of the dock item. """ super(QDockItem, self).__init__(parent) layout = QDockItemLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSizeConstraint(QLayout.SetMinAndMaxSize) self.setLayout(layout) self.setTitleBarWidget(QDockTitleBar()) self.alerted.connect(self._onAlerted) self._manager = None # Set and cleared by the DockManager self._alert_data = None self._vis_changed = None self._closable = True self._closing = False #-------------------------------------------------------------------------- # Reimplementations #-------------------------------------------------------------------------- def close(self): """ Handle the close request for the dock item. """ self._closing = True try: super(QDockItem, self).close() finally: self._closing = False def closeEvent(self, event): """ Handle the close event for the dock item. This handler will reject the event if the item is not closable. """ event.ignore() if self._closable: event.accept() area = self.rootDockArea() if area is not None and area.dockEventsEnabled(): event = QDockItemEvent(DockItemClosed, self.objectName()) QApplication.postEvent(area, event) def showEvent(self, event): """ Handle the show event for the container. This handler posts a visibility change event. """ super(QDockItem, self).showEvent(event) self._postVisibilityChange(True) def hideEvent(self, event): """ Handle the hide event for the container. This handler posts a visibility change event. """ super(QDockItem, self).hideEvent(event) # Don't post when closing; A closed event is posted instead. if not self._closing: self._postVisibilityChange(False) def mousePressEvent(self, event): """ Handle the mouse press event for the dock item. This handler will clear any alert level on a left click. """ if event.button() == Qt.LeftButton: self.clearAlert() super(QDockItem, self).mousePressEvent(event) #-------------------------------------------------------------------------- # Public API #-------------------------------------------------------------------------- def manager(self): """ Get the dock manager for this dock item. Returns ------- result : DockManager or None The dock manager which is managing this item. """ return self._manager def rootDockArea(self): """ Get the root dock area for this dock item. Returns ------- result : QDockArea or None The root dock area for this dock item. """ manager = self._manager if manager is not None: return manager.dock_area() def title(self): """ Get the title for the dock item. Returns ------- result : unicode The unicode title for the dock item. """ return self.titleBarWidget().title() def setTitle(self, title): """ Set the title for the dock item. Parameters ---------- title : unicode The unicode title to use for the dock item. """ self.titleBarWidget().setTitle(title) # A concession to practicality: walk the ancestry and update # the tab title if this item lives in a dock tab. container = self.parent() if container is not None: stacked = container.parent() if stacked is not None: tabs = stacked.parent() if isinstance(tabs, QDockTabWidget): index = tabs.indexOf(container) tabs.setTabText(index, title) def icon(self): """ Get the icon for the dock item. Returns ------- result : QIcon The icon in use for the dock item. """ return self.titleBarWidget().icon() def setIcon(self, icon): """ Set the icon for the dock item. Parameters ---------- icon : QIcon The icon to use for the dock item. """ self.titleBarWidget().setIcon(icon) # A concession to practicality: walk the ancestry and update # the tab icon if this item lives in a dock tab. container = self.parent() if container is not None: stacked = container.parent() if stacked is not None: tabs = stacked.parent() if isinstance(tabs, QDockTabWidget): index = tabs.indexOf(container) tabs.setTabIcon(index, icon) def iconSize(self): """ Get the icon size for the title bar. Returns ------- result : QSize The size to use for the icons in the title bar. """ return self.titleBarWidget().iconSize() def setIconSize(self, size): """ Set the icon size for the title bar. Parameters ---------- icon : QSize The icon size to use for the title bar. Icons smaller than this size will not be scaled up. """ self.titleBarWidget().setIconSize(size) def isLinked(self): """ Get whether or not this dock item is linked. Returns ------- result : bool True if the item is linked, False otherwise. """ return self.titleBarWidget().isLinked() def setLinked(self, linked): """ Set whether or not the dock item is linked. Parameters ---------- linked : bool True if the dock item should be linked, False otherwise. """ self.titleBarWidget().setLinked(linked) def isPinned(self): """ Get whether or not this dock item is pinned. Returns ------- result : bool True if the item is pinned, False otherwise. """ return self.titleBarWidget().isPinned() def setPinned(self, pinned, quiet=False): """ Set whether or not the dock item is pinned. Parameters ---------- pinned : bool True if the dock item should be pinned, False otherwise. quiet : bool, optional True if the state should be set without emitted the toggled signal. The default is False. """ self.titleBarWidget().setPinned(pinned, quiet) def isFloating(self): """ Get whether the dock item is free floating. """ container = self.parent() if container is not None: return container.isWindow() return self.isWindow() def titleEditable(self): """ Get whether the title is user editable. Returns ------- result : bool True if the title is user editable, False otherwise. """ return self.titleBarWidget().isEditable() def setTitleEditable(self, editable): """ Set whether or not the title is user editable. Parameters ---------- editable : bool True if the title is user editable, False otherwise. """ self.titleBarWidget().setEditable(editable) def titleBarForceHidden(self): """ Get whether or not the title bar is force hidden. Returns ------- result : bool Whether or not the title bar is force hidden. """ return self.titleBarWidget().isForceHidden() def setTitleBarForceHidden(self, hidden): """ Set the force hidden state of the title bar. Parameters ---------- hidden : bool True if the title bar should be hidden, False otherwise. """ self.titleBarWidget().setForceHidden(hidden) def closable(self): """ Get whether or not the dock item is closable. Returns ------- result : bool True if the dock item is closable, False otherwise. """ return self._closable def setClosable(self, closable): """ Set whether or not the dock item is closable. Parameters ---------- closable : bool True if the dock item is closable, False otherwise. """ if closable != self._closable: self._closable = closable bar = self.titleBarWidget() buttons = bar.buttons() if closable: buttons |= bar.CloseButton else: buttons &= ~bar.CloseButton bar.setButtons(buttons) # A concession to practicality: walk the ancestry and update # the tab close button if this item lives in a dock tab. container = self.parent() if container is not None: stacked = container.parent() if stacked is not None: tabs = stacked.parent() if isinstance(tabs, QDockTabWidget): index = tabs.indexOf(container) tabs.setCloseButtonVisible(index, closable) def titleBarWidget(self): """ Get the title bar widget for the dock item. If a custom title bar has not been assigned, a default title bar will be returned. To prevent showing a title bar, set the visibility on the returned title bar to False. Returns ------- result : IDockItemTitleBar An implementation of IDockItemTitleBar. This will never be None. """ layout = self.layout() bar = layout.titleBarWidget() if bar is None: bar = QDockTitleBar() self.setTitleBarWidget(bar) return bar def setTitleBarWidget(self, title_bar): """ Set the title bar widget for the dock item. Parameters ---------- title_bar : IDockItemTitleBar or None A custom implementation of IDockItemTitleBar, or None. If None, then the default title bar will be restored. """ layout = self.layout() old = layout.titleBarWidget() if old is not None: old.maximizeButtonClicked.disconnect(self.maximizeButtonClicked) old.restoreButtonClicked.disconnect(self.restoreButtonClicked) old.closeButtonClicked.disconnect(self.closeButtonClicked) old.linkButtonToggled.disconnect(self.linkButtonToggled) old.pinButtonToggled.disconnect(self.pinButtonToggled) old.titleEdited.disconnect(self.titleEdited) old.leftDoubleClicked.disconnect(self.titleBarLeftDoubleClicked) old.rightClicked.disconnect(self.titleBarRightClicked) title_bar = title_bar or QDockTitleBar() title_bar.maximizeButtonClicked.connect(self.maximizeButtonClicked) title_bar.restoreButtonClicked.connect(self.restoreButtonClicked) title_bar.closeButtonClicked.connect(self.closeButtonClicked) title_bar.linkButtonToggled.connect(self.linkButtonToggled) title_bar.pinButtonToggled.connect(self.pinButtonToggled) title_bar.titleEdited.connect(self.titleEdited) title_bar.leftDoubleClicked.connect(self.titleBarLeftDoubleClicked) title_bar.rightClicked.connect(self.titleBarRightClicked) layout.setTitleBarWidget(title_bar) def dockWidget(self): """ Get the dock widget for this dock item. Returns ------- result : QWidget or None The dock widget being managed by this item. """ return self.layout().dockWidget() def setDockWidget(self, widget): """ Set the dock widget for this dock item. Parameters ---------- widget : QWidget The QWidget to use as the dock widget in this item. """ self.layout().setDockWidget(widget) def alert(self, level, on=250, off=250, repeat=4, persist=False): """ Set the alert level on the dock item. This will override any currently applied alert level. Parameters ---------- level : unicode The alert level token to apply to the dock item. on : int The duration of the 'on' cycle, in ms. A value of -1 means always on. off : int The duration of the 'off' cycle, in ms. If 'on' is -1, this value is ignored. repeat : int The number of times to repeat the on-off cycle. If 'on' is -1, this value is ignored. persist : bool Whether to leave the alert in the 'on' state when the cycles finish. If 'on' is -1, this value is ignored. """ if self._alert_data is not None: self.clearAlert() app = QApplication.instance() app.focusChanged.connect(self._onAppFocusChanged) timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(self._onAlertTimer) on, off, repeat = max(-1, on), max(0, off), max(1, repeat) self._alert_data = _AlertData(timer, level, on, off, repeat, persist) if on < 0: self.alerted.emit(level) else: self._onAlertTimer() def clearAlert(self): """ Clear the current alert level, if any. """ if self._alert_data is not None: self._alert_data.timer.stop() self._alert_data = None app = QApplication.instance() app.focusChanged.disconnect(self._onAppFocusChanged) self.alerted.emit(u'') #-------------------------------------------------------------------------- # Private API #-------------------------------------------------------------------------- def _onAlertTimer(self): """ Handle the alert data timer timeout. This handler will refresh the alert level for the current tick, or clear|persist the alert level if the ticks have expired. """ data = self._alert_data if data is not None: if not data.active: data.active = True data.timer.start(data.on) self.alerted.emit(data.level) else: data.active = False data.remaining -= 1 if data.remaining > 0: data.timer.start(data.off) self.alerted.emit(u'') elif data.persist: data.timer.stop() self.alerted.emit(data.level) else: self.clearAlert() def _onAlerted(self, level): """ A signal handler for the 'alerted' signal. This handler will set the 'alert' dynamic property on the dock item, the title bar, and the title bar label, and then repolish all three items. """ level = level or None title_bar = self.titleBarWidget() label = title_bar.label() self.setProperty(u'alert', level) title_bar.setProperty(u'alert', level) label.setProperty(u'alert', level) repolish(label) repolish(title_bar) repolish(self) def _onAppFocusChanged(self, old, new): """ A signal handler for the 'focusChanged' app signal This handler will clear the alert if one of the descendant widgets or the item itself gains focus. """ while new is not None: if new is self: self.clearAlert() break new = new.parent() def _onVisibilityTimer(self): """ Handle the visibility timer timeout. This handler will post the dock item visibility event to the root dock area. """ area = self.rootDockArea() if area is not None and area.dockEventsEnabled(): timer, visible = self._vis_changed evt_type = DockItemShown if visible else DockItemHidden event = QDockItemEvent(evt_type, self.objectName()) QApplication.postEvent(area, event) self._vis_changed = None def _postVisibilityChange(self, visible): """ Post a visibility changed event for the dock item. This method collapses the post on a timer and will not emit the event when the visibility temporarily toggles bettwen states. Parameters ---------- visible : bool True if the item was show, False if the item was hidden. """ area = self.rootDockArea() if area is not None and area.dockEventsEnabled(): vis_changed = self._vis_changed if vis_changed is None: timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(self._onVisibilityTimer) self._vis_changed = (timer, visible) timer.start() else: timer, old_visible = vis_changed if old_visible != visible: self._vis_changed = None timer.stop()
class QDockItem(QFrame): """ A QFrame subclass which acts as an item QDockArea. """ #: A signal emitted when the maximize button is clicked. This #: signal is proxied from the current dock item title bar. maximizeButtonClicked = Signal(bool) #: A signal emitted when the restore button is clicked. This #: signal is proxied from the current dock item title bar. restoreButtonClicked = Signal(bool) #: A signal emitted when the close button is clicked. This #: signal is proxied from the current dock item title bar. closeButtonClicked = Signal(bool) #: A signal emitted when the link button is toggled. This #: signal is proxied from the current dock item title bar. linkButtonToggled = Signal(bool) #: A signal emitted when the pin button is toggled. This #: signal is proxied from the current dock item title bar. pinButtonToggled = Signal(bool) #: A signal emitted when the title is edited by the user. This #: signal is proxied from the current dock item title bar. titleEdited = Signal(unicode) #: A signal emitted when the empty area is left double clicked. #: This signal is proxied from the current dock item title bar. titleBarLeftDoubleClicked = Signal(QPoint) #: A signal emitted when the empty area is right clicked. This #: signal is proxied from the current dock item title bar. titleBarRightClicked = Signal(QPoint) def __init__(self, parent=None): """ Initialize a QDockItem. Parameters ---------- parent : QWidget, optional The parent of the dock item. """ super(QDockItem, self).__init__(parent) layout = QDockItemLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSizeConstraint(QLayout.SetMinAndMaxSize) self.setLayout(layout) self.setTitleBarWidget(QDockTitleBar()) self._manager = None # Set and cleared by the DockManager self._vis_changed = None self._closable = True self._closing = False #-------------------------------------------------------------------------- # Reimplementations #-------------------------------------------------------------------------- def close(self): """ Handle the close request for the dock item. """ self._closing = True try: super(QDockItem, self).close() finally: self._closing = False def closeEvent(self, event): """ Handle the close event for the dock item. This handler will reject the event if the item is not closable. """ event.ignore() if self._closable: event.accept() area = self.rootDockArea() if area is not None and area.dockEventsEnabled(): event = QDockItemEvent(DockItemClosed, self.objectName()) QApplication.postEvent(area, event) def showEvent(self, event): """ Handle the show event for the container. This handler posts a visibility change event. """ super(QDockItem, self).showEvent(event) self._postVisibilityChange(True) def hideEvent(self, event): """ Handle the hide event for the container. This handler posts a visibility change event. """ super(QDockItem, self).hideEvent(event) # Don't post when closing; A closed event is posted instead. if not self._closing: self._postVisibilityChange(False) #-------------------------------------------------------------------------- # Public API #-------------------------------------------------------------------------- def manager(self): """ Get the dock manager for this dock item. Returns ------- result : DockManager or None The dock manager which is managing this item. """ return self._manager def rootDockArea(self): """ Get the root dock area for this dock item. Returns ------- result : QDockArea or None The root dock area for this dock item. """ manager = self._manager if manager is not None: return manager.dock_area() def title(self): """ Get the title for the dock item. Returns ------- result : unicode The unicode title for the dock item. """ return self.titleBarWidget().title() def setTitle(self, title): """ Set the title for the dock item. Parameters ---------- title : unicode The unicode title to use for the dock item. """ self.titleBarWidget().setTitle(title) # A concession to practicality: walk the ancestry and update # the tab title if this item lives in a dock tab. container = self.parent() if container is not None: stacked = container.parent() if stacked is not None: tabs = stacked.parent() if isinstance(tabs, QDockTabWidget): index = tabs.indexOf(container) tabs.setTabText(index, title) def icon(self): """ Get the icon for the dock item. Returns ------- result : QIcon The icon in use for the dock item. """ return self.titleBarWidget().icon() def setIcon(self, icon): """ Set the icon for the dock item. Parameters ---------- icon : QIcon The icon to use for the dock item. """ self.titleBarWidget().setIcon(icon) # A concession to practicality: walk the ancestry and update # the tab icon if this item lives in a dock tab. container = self.parent() if container is not None: stacked = container.parent() if stacked is not None: tabs = stacked.parent() if isinstance(tabs, QDockTabWidget): index = tabs.indexOf(container) tabs.setTabIcon(index, icon) def iconSize(self): """ Get the icon size for the title bar. Returns ------- result : QSize The size to use for the icons in the title bar. """ return self.titleBarWidget().iconSize() def setIconSize(self, size): """ Set the icon size for the title bar. Parameters ---------- icon : QSize The icon size to use for the title bar. Icons smaller than this size will not be scaled up. """ self.titleBarWidget().setIconSize(size) def isLinked(self): """ Get whether or not this dock item is linked. Returns ------- result : bool True if the item is linked, False otherwise. """ return self.titleBarWidget().isLinked() def setLinked(self, linked): """ Set whether or not the dock item is linked. Parameters ---------- linked : bool True if the dock item should be linked, False otherwise. """ self.titleBarWidget().setLinked(linked) def isPinned(self): """ Get whether or not this dock item is pinned. Returns ------- result : bool True if the item is pinned, False otherwise. """ return self.titleBarWidget().isPinned() def setPinned(self, pinned, quiet=False): """ Set whether or not the dock item is pinned. Parameters ---------- pinned : bool True if the dock item should be pinned, False otherwise. quiet : bool, optional True if the state should be set without emitted the toggled signal. The default is False. """ self.titleBarWidget().setPinned(pinned, quiet) def titleEditable(self): """ Get whether the title is user editable. Returns ------- result : bool True if the title is user editable, False otherwise. """ return self.titleBarWidget().isEditable() def setTitleEditable(self, editable): """ Set whether or not the title is user editable. Parameters ---------- editable : bool True if the title is user editable, False otherwise. """ self.titleBarWidget().setEditable(editable) def titleBarForceHidden(self): """ Get whether or not the title bar is force hidden. Returns ------- result : bool Whether or not the title bar is force hidden. """ return self.titleBarWidget().isForceHidden() def setTitleBarForceHidden(self, hidden): """ Set the force hidden state of the title bar. Parameters ---------- hidden : bool True if the title bar should be hidden, False otherwise. """ self.titleBarWidget().setForceHidden(hidden) def closable(self): """ Get whether or not the dock item is closable. Returns ------- result : bool True if the dock item is closable, False otherwise. """ return self._closable def setClosable(self, closable): """ Set whether or not the dock item is closable. Parameters ---------- closable : bool True if the dock item is closable, False otherwise. """ if closable != self._closable: self._closable = closable bar = self.titleBarWidget() buttons = bar.buttons() if closable: buttons |= bar.CloseButton else: buttons &= ~bar.CloseButton bar.setButtons(buttons) # A concession to practicality: walk the ancestry and update # the tab close button if this item lives in a dock tab. container = self.parent() if container is not None: stacked = container.parent() if stacked is not None: tabs = stacked.parent() if isinstance(tabs, QDockTabWidget): index = tabs.indexOf(container) tabs.setCloseButtonVisible(index, closable) def titleBarWidget(self): """ Get the title bar widget for the dock item. If a custom title bar has not been assigned, a default title bar will be returned. To prevent showing a title bar, set the visibility on the returned title bar to False. Returns ------- result : IDockItemTitleBar An implementation of IDockItemTitleBar. This will never be None. """ layout = self.layout() bar = layout.titleBarWidget() if bar is None: bar = QDockTitleBar() self.setTitleBarWidget(bar) return bar def setTitleBarWidget(self, title_bar): """ Set the title bar widget for the dock item. Parameters ---------- title_bar : IDockItemTitleBar or None A custom implementation of IDockItemTitleBar, or None. If None, then the default title bar will be restored. """ layout = self.layout() old = layout.titleBarWidget() if old is not None: old.maximizeButtonClicked.disconnect(self.maximizeButtonClicked) old.restoreButtonClicked.disconnect(self.restoreButtonClicked) old.closeButtonClicked.disconnect(self.closeButtonClicked) old.linkButtonToggled.disconnect(self.linkButtonToggled) old.pinButtonToggled.disconnect(self.pinButtonToggled) old.titleEdited.disconnect(self.titleEdited) old.leftDoubleClicked.disconnect(self.titleBarLeftDoubleClicked) old.rightClicked.disconnect(self.titleBarRightClicked) title_bar = title_bar or QDockTitleBar() title_bar.maximizeButtonClicked.connect(self.maximizeButtonClicked) title_bar.restoreButtonClicked.connect(self.restoreButtonClicked) title_bar.closeButtonClicked.connect(self.closeButtonClicked) title_bar.linkButtonToggled.connect(self.linkButtonToggled) title_bar.pinButtonToggled.connect(self.pinButtonToggled) title_bar.titleEdited.connect(self.titleEdited) title_bar.leftDoubleClicked.connect(self.titleBarLeftDoubleClicked) title_bar.rightClicked.connect(self.titleBarRightClicked) layout.setTitleBarWidget(title_bar) def dockWidget(self): """ Get the dock widget for this dock item. Returns ------- result : QWidget or None The dock widget being managed by this item. """ return self.layout().dockWidget() def setDockWidget(self, widget): """ Set the dock widget for this dock item. Parameters ---------- widget : QWidget The QWidget to use as the dock widget in this item. """ self.layout().setDockWidget(widget) #-------------------------------------------------------------------------- # Private API #-------------------------------------------------------------------------- def _onVisibilityTimer(self): """ Handle the visibility timer timeout. This handler will post the dock item visibility event to the root dock area. """ area = self.rootDockArea() if area is not None and area.dockEventsEnabled(): timer, visible = self._vis_changed evt_type = DockItemShown if visible else DockItemHidden event = QDockItemEvent(evt_type, self.objectName()) QApplication.postEvent(area, event) self._vis_changed = None def _postVisibilityChange(self, visible): """ Post a visibility changed event for the dock item. This method collapses the post on a timer and will not emit the event when the visibility temporarily toggles bettwen states. Parameters ---------- visible : bool True if the item was show, False if the item was hidden. """ area = self.rootDockArea() if area is not None and area.dockEventsEnabled(): vis_changed = self._vis_changed if vis_changed is None: timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(self._onVisibilityTimer) self._vis_changed = (timer, visible) timer.start() else: timer, old_visible = vis_changed if old_visible != visible: self._vis_changed = None timer.stop()