File indexing completed on 2024-05-12 05:46:31

0001 # Ported from KoDockWidgetTitleBar.cpp which is part of KOffice
0002 # Copyright (c) 2007 Marijn Kruisselbrink <m.kruisselbrink@student.tue.nl>
0003 # Copyright (C) 2007 Thomas Zander <zander@kde.org>
0004 # The code is distributed under GPL 2 or any later version
0005 import os
0006 
0007 from PyQt5.QtCore import QPoint, QSize, Qt, QRect, QTimer
0008 from PyQt5.QtGui import (QIcon, QPainter)
0009 from PyQt5.QtWidgets import (QAbstractButton, QApplication, QComboBox,
0010                              QDockWidget, QHBoxLayout, QLayout, QMainWindow,
0011                              QPushButton, QStyle, QStyleOptionDockWidget,
0012                              QStyleOptionToolButton, QStylePainter, QWidget)
0013 
0014 
0015 import dockwidget_icons
0016 
0017 
0018 def hasFeature(dockwidget, feature):
0019     return dockwidget.features() & feature == feature
0020 
0021 
0022 class DockWidgetTitleBarButton(QAbstractButton):
0023 
0024     def __init__(self, titlebar):
0025         QAbstractButton.__init__(self, titlebar)
0026         self.setFocusPolicy(Qt.NoFocus)
0027 
0028     def sizeHint(self):
0029         self.ensurePolished()
0030         margin = self.style().pixelMetric(QStyle.PM_DockWidgetTitleBarButtonMargin, None, self)
0031         if self.icon().isNull():
0032             return QSize(margin, margin)
0033         iconSize = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self)
0034         pm = self.icon().pixmap(iconSize)
0035         return QSize(pm.width() + margin, pm.height() + margin)
0036 
0037     def enterEvent(self, event):
0038         if self.isEnabled():
0039             self.update()
0040         QAbstractButton.enterEvent(self, event)
0041 
0042     def leaveEvent(self, event):
0043         if self.isEnabled():
0044             self.update()
0045         QAbstractButton.leaveEvent(self, event)
0046 
0047     def paintEvent(self, event):
0048         p = QPainter(self)
0049         r = self.rect()
0050         opt = QStyleOptionToolButton()
0051         opt.init(self)
0052         opt.state |= QStyle.State_AutoRaise
0053         if self.isEnabled() and self.underMouse() and \
0054            not self.isChecked() and not self.isDown():
0055             opt.state |= QStyle.State_Raised
0056         if self.isChecked():
0057             opt.state |= QStyle.State_On
0058         if self.isDown():
0059             opt.state |= QStyle.State_Sunken
0060         self.style().drawPrimitive(
0061             QStyle.PE_PanelButtonTool, opt, p, self)
0062         opt.icon = self.icon()
0063         opt.subControls = QStyle.SubControls()
0064         opt.activeSubControls = QStyle.SubControls()
0065         opt.features = QStyleOptionToolButton.None
0066         opt.arrowType = Qt.NoArrow
0067         size = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self)
0068         opt.iconSize = QSize(size, size)
0069         self.style().drawComplexControl(QStyle.CC_ToolButton, opt, p, self)
0070 
0071 
0072 class DockWidgetTitleBar(QWidget):
0073     # XXX: support QDockWidget.DockWidgetVerticalTitleBar feature
0074 
0075     def __init__(self, dockWidget):
0076         QWidget.__init__(self, dockWidget)
0077         self.openIcon = QIcon(":arrow-down.png")
0078         self.closeIcon = QIcon(":arrow-right.png")
0079         self.pinIcon = QIcon(":pin.png")
0080         q = dockWidget
0081         self.floatButton = DockWidgetTitleBarButton(self)
0082         self.floatButton.setIcon(q.style().standardIcon(
0083             QStyle.SP_TitleBarNormalButton, None, q))
0084         self.floatButton.clicked.connect(self.toggleFloating)
0085         self.floatButton.setVisible(True)
0086         self.closeButton = DockWidgetTitleBarButton(self)
0087         self.closeButton.setIcon(q.style().standardIcon(
0088             QStyle.SP_TitleBarCloseButton, None, q))
0089         self.closeButton.clicked.connect(dockWidget.close)
0090         self.closeButton.setVisible(True)
0091         self.collapseButton = DockWidgetTitleBarButton(self)
0092         self.collapseButton.setIcon(self.openIcon)
0093         self.collapseButton.clicked.connect(self.toggleCollapsed)
0094         self.collapseButton.setVisible(True)
0095         self.pinButton = DockWidgetTitleBarButton(self)
0096         self.pinButton.setIcon(self.pinIcon)
0097         self.pinButton.setCheckable(True)
0098         self.pinButton.setChecked(True)
0099         self.pinButton.clicked.connect(self.togglePinned)
0100         self.pinButton.setVisible(True)
0101         dockWidget.featuresChanged.connect(self.featuresChanged)
0102         self.featuresChanged(0)
0103 
0104     def minimumSizeHint(self):
0105         return self.sizeHint()
0106 
0107     def sizeHint(self):
0108         q = self.parentWidget()
0109         mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q)
0110         fw = q.style().pixelMetric(QStyle.PM_DockWidgetFrameWidth, None, q)
0111         closeSize = QSize(0, 0)
0112         if self.closeButton:
0113             closeSize = self.closeButton.sizeHint()
0114         floatSize = QSize(0, 0)
0115         if self.floatButton:
0116             floatSize = self.floatButton.sizeHint()
0117         hideSize = QSize(0, 0)
0118         if self.collapseButton:
0119             hideSize = self.collapseButton.sizeHint()
0120         pinSize = QSize(0, 0)
0121         if self.pinButton:
0122             pinSize = self.pinButton.sizeHint()
0123         buttonHeight = max(max(closeSize.height(), floatSize.height()),
0124                            hideSize.height(), pinSize.height()) + 2
0125         buttonWidth = closeSize.width() + floatSize.width() + hideSize.width() + pinSize.width()
0126         titleFontMetrics = q.fontMetrics()
0127         fontHeight = titleFontMetrics.lineSpacing() + 2 * mw
0128         height = max(buttonHeight, fontHeight)
0129         width = buttonWidth + height + 4 * mw + 2 * fw
0130         if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar):
0131             width, height = height, width
0132         return QSize(width, height)
0133 
0134     def paintEvent(self, event):
0135         p = QStylePainter(self)
0136         q = self.parentWidget()
0137         if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar):
0138             fw = 1 or q.isFloating() and q.style().pixelMetric(
0139                 QStyle.PM_DockWidgetFrameWidth, None, q) or 0
0140             mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q)
0141             titleOpt = QStyleOptionDockWidget()
0142             titleOpt.initFrom(q)
0143             titleOpt.verticalTitleBar = True
0144             titleOpt.rect = QRect(
0145                 QPoint(fw, fw + mw +
0146                        self.collapseButton.size().height() + self.pinButton.size().height()),
0147                 QSize(
0148                     self.geometry().width() - (fw * 2),
0149                     self.geometry().height() - (fw * 2) -
0150                     mw - self.collapseButton.size().height() - self.pinButton.size().height()))
0151             titleOpt.title = q.windowTitle()
0152             titleOpt.closable = hasFeature(q, QDockWidget.DockWidgetClosable)
0153             titleOpt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable)
0154             p.drawControl(QStyle.CE_DockWidgetTitle, titleOpt)
0155         else:
0156             fw = q.isFloating() and q.style().pixelMetric(
0157                 QStyle.PM_DockWidgetFrameWidth, None, q) or 0
0158             mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q)
0159             titleOpt = QStyleOptionDockWidget()
0160             titleOpt.initFrom(q)
0161             titleOpt.rect = QRect(
0162                 QPoint(fw + mw +
0163                        self.collapseButton.size().width() + self.pinButton.size().width(), fw),
0164                 QSize(
0165                     self.geometry().width() - (fw * 2) -
0166                     mw - self.collapseButton.size().width() - self.pinButton.size().width(),
0167                     self.geometry().height() - (fw * 2)))
0168             titleOpt.title = q.windowTitle()
0169             titleOpt.closable = hasFeature(q, QDockWidget.DockWidgetClosable)
0170             titleOpt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable)
0171             p.drawControl(QStyle.CE_DockWidgetTitle, titleOpt)
0172 
0173     def resizeEvent(self, event):
0174         q = self.parentWidget()
0175         if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar):
0176             fh = q.isFloating() and q.style().pixelMetric(
0177                 QStyle.PM_DockWidgetFrameWidth, None, q) or 0
0178             opt = QStyleOptionDockWidget()
0179             opt.initFrom(q)
0180             opt.verticalTitleBar = True
0181             opt.rect = QRect(
0182                 QPoint(fh, 40),  # self.geometry().height() - (fh * 3)),
0183                 QSize(
0184                     self.geometry().width() - (fh * 2),
0185                     fh * 2))
0186             opt.title = q.windowTitle()
0187             opt.closable = hasFeature(q, QDockWidget.DockWidgetClosable)
0188             opt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable)
0189             floatRect = q.style().subElementRect(
0190                 QStyle.SE_DockWidgetFloatButton, opt, q)
0191             if not floatRect.isNull():
0192                 self.floatButton.setGeometry(floatRect)
0193             closeRect = q.style().subElementRect(
0194                 QStyle.SE_DockWidgetCloseButton, opt, q)
0195             if not closeRect.isNull():
0196                 self.closeButton.setGeometry(closeRect)
0197             top = fh
0198             if not floatRect.isNull():
0199                 top = floatRect.x()
0200             elif not closeRect.isNull():
0201                 top = closeRect.x()
0202             size = self.collapseButton.size()
0203             if not closeRect.isNull():
0204                 size = self.closeButton.size()
0205             elif not floatRect.isNull():
0206                 size = self.floatButton.size()
0207             collapseRect = QRect(QPoint(top, fh), size)
0208             self.collapseButton.setGeometry(collapseRect)
0209             pinRect = QRect(QPoint(top, fh + collapseRect.height() + 1), size)
0210             self.pinButton.setGeometry(pinRect)
0211         else:
0212             fw = q.isFloating() and q.style().pixelMetric(
0213                 QStyle.PM_DockWidgetFrameWidth, None, q) or 0
0214             opt = QStyleOptionDockWidget()
0215             opt.initFrom(q)
0216             opt.rect = QRect(
0217                 QPoint(fw, fw),
0218                 QSize(
0219                     self.geometry().width() - (fw * 2),
0220                     self.geometry().height() - (fw * 2)))
0221             opt.title = q.windowTitle()
0222             opt.closable = hasFeature(q, QDockWidget.DockWidgetClosable)
0223             opt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable)
0224             floatRect = q.style().subElementRect(
0225                 QStyle.SE_DockWidgetFloatButton, opt, q)
0226             if not floatRect.isNull():
0227                 self.floatButton.setGeometry(floatRect)
0228             closeRect = q.style().subElementRect(
0229                 QStyle.SE_DockWidgetCloseButton, opt, q)
0230             if not closeRect.isNull():
0231                 self.closeButton.setGeometry(closeRect)
0232             top = fw
0233             if not floatRect.isNull():
0234                 top = floatRect.y()
0235             elif not closeRect.isNull():
0236                 top = closeRect.y()
0237             size = self.collapseButton.size()
0238             if not closeRect.isNull():
0239                 size = self.closeButton.size()
0240             elif not floatRect.isNull():
0241                 size = self.floatButton.size()
0242             collapseRect = QRect(QPoint(fw, top), size)
0243             self.collapseButton.setGeometry(collapseRect)
0244             pinRect = QRect(QPoint(fw + collapseRect.width() + 1, top), size)
0245             self.pinButton.setGeometry(pinRect)
0246 
0247     def setCollapsed(self, collapsed):
0248         q = self.parentWidget()
0249         if q and q.widget() and q.widget().isHidden() != collapsed:
0250             self.toggleCollapsed()
0251 
0252     def toggleFloating(self):
0253         q = self.parentWidget()
0254         q.setFloating(not q.isFloating())
0255 
0256     def toggleCollapsed(self):
0257         q = self.parentWidget()
0258         if not q:
0259             return
0260         q.toggleCollapsed()
0261         self.setCollapsedIcon(q.isCollapsed())
0262 
0263     def setCollapsedIcon(self, flag):
0264         self.collapseButton.setIcon(flag and self.openIcon or self.closeIcon)
0265 
0266     def togglePinned(self, checked):
0267         self.parent().setPinned(checked)
0268 
0269     def featuresChanged(self, features):
0270         q = self.parentWidget()
0271         self.closeButton.setVisible(hasFeature(q, QDockWidget.DockWidgetClosable))
0272         self.floatButton.setVisible(hasFeature(q, QDockWidget.DockWidgetFloatable))
0273         # self.resizeEvent(None)
0274 
0275 
0276 class DockMainWidgetWrapper(QWidget):
0277 
0278     def __init__(self, dockwidget):
0279         QWidget.__init__(self, dockwidget)
0280         self.widget = None
0281         self.hlayout = QHBoxLayout(self)
0282         self.hlayout.setSpacing(0)
0283         self.hlayout.setContentsMargins(0, 0, 0, 0)
0284         self.setLayout(self.hlayout)
0285 
0286     def setWidget(self, widget):
0287         self.widget = widget
0288         self.widget_height = widget.height
0289         self.layout().addWidget(widget)
0290 
0291     def isCollapsed(self):
0292         return self.widget.isVisible()
0293 
0294     def setCollapsed(self, flag):
0295         if not flag:
0296             self.old_size = self.size()
0297             self.layout().removeWidget(self.widget)
0298             self.widget.hide()
0299             if hasFeature(self.parent(), QDockWidget.DockWidgetVerticalTitleBar):
0300                 self.parent().setMaximumWidth(self.parent().width() - self.width())
0301             else:
0302                 self.parent().setMaximumHeight(self.parent().height() - self.height())
0303         else:
0304             self.setFixedSize(self.old_size)
0305             self.parent().setMinimumSize(QSize(1, 1))
0306             self.parent().setMaximumSize(QSize(32768, 32768))
0307             self.widget.show()
0308             self.layout().addWidget(self.widget)
0309             self.setMinimumSize(QSize(1, 1))
0310             self.setMaximumSize(QSize(32768, 32768))
0311 
0312 
0313 class DockWidget(QDockWidget):
0314 
0315     def __init__(self, *args):
0316         QDockWidget.__init__(self, *args)
0317         self.titleBar = DockWidgetTitleBar(self)
0318         self.setTitleBarWidget(self.titleBar)
0319         self.mainWidget = None
0320         self.entered = False
0321         self.pinned = True
0322         self.shot = False
0323 
0324     def enterEvent(self, event):
0325         self.entered = True
0326         if not self.shot and not self.isPinned() and not self.isFloating():
0327             self.shot = True
0328             QTimer.singleShot(500, self.autoshow)
0329         return QDockWidget.enterEvent(self, event)
0330 
0331     def leaveEvent(self, event):
0332         self.entered = False
0333         if not self.shot and not self.isPinned() and not self.isFloating():
0334             self.shot = True
0335             QTimer.singleShot(1000, self.autohide)
0336         return QDockWidget.leaveEvent(self, event)
0337 
0338     def autohide(self):
0339         self.shot = False
0340         if not self.entered:
0341             self.setCollapsed(False)
0342 
0343     def autoshow(self):
0344         self.shot = False
0345         if self.entered:
0346             self.setCollapsed(True)
0347 
0348     def isPinned(self):
0349         return self.pinned
0350 
0351     def setPinned(self, flag):
0352         self.pinned = flag
0353 
0354     def setWidget(self, widget):
0355         self.mainWidget = DockMainWidgetWrapper(self)
0356         self.mainWidget.setWidget(widget)
0357         QDockWidget.setWidget(self, self.mainWidget)
0358 
0359     def setCollapsed(self, flag):
0360         self.mainWidget.setCollapsed(flag)
0361         self.titleBarWidget().setCollapsedIcon(flag)
0362 
0363     def isCollapsed(self):
0364         return self.mainWidget.isCollapsed()
0365 
0366     def toggleCollapsed(self):
0367         self.setCollapsed(not self.isCollapsed())
0368 
0369 
0370 if __name__ == "__main__":
0371     import sys
0372     from PyQt5.QtGui import QTextEdit
0373     app = QApplication(sys.argv)
0374     app.setStyle("qtcurve")
0375     win = QMainWindow()
0376     dock1 = DockWidget("1st dockwidget", win)
0377     dock1.setFeatures(dock1.features() | QDockWidget.DockWidgetVerticalTitleBar)
0378     combo = QComboBox(dock1)
0379     dock1.setWidget(combo)
0380     win.addDockWidget(Qt.LeftDockWidgetArea, dock1)
0381     dock2 = DockWidget("2nd dockwidget")
0382     dock2.setFeatures(dock1.features() | QDockWidget.DockWidgetVerticalTitleBar)
0383     button = QPushButton("Hello, world!", dock2)
0384     dock2.setWidget(button)
0385     win.addDockWidget(Qt.RightDockWidgetArea, dock2)
0386     edit = QTextEdit(win)
0387     win.setCentralWidget(edit)
0388     win.resize(640, 480)
0389     win.show()
0390     app.exec_()