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_()