I had a solution in pyQt4 to undock-dock a tab from/to a QTabWidget by using QDockWidgets and the code below. After floating a tab, the re-docking was obtained by double clicking the titlebar of the QDockWidget. But it does not work any more in pyQt5 (the double-click does not seem to trigger a topLevelChanged event).
Why?
How to fix it and get back the proper re-docking behavior?
Where is this behavior change explained in the documentation?
Thanks for your help.
import sys try: from PyQt5.QtCore import QEvent from PyQt5.QtWidgets import QApplication, QMainWindow, QDockWidget, QTabWidget, QLabel except: from PyQt4.QtCore import QEvent from PyQt4.QtGui import QApplication, QMainWindow, QDockWidget, QTabWidget, QLabel class DockToTabWidget(QDockWidget): def __init__(self, title, parent=0): QDockWidget.__init__(self, title, parent) self._title = title self.topLevelChanged.connect(self.dockToTab) def dockToTab(self): if not self.isFloating(): self.parent().addTab(self.widget(), self._title) self.close() del self class TabWidgetWithUndocking(QTabWidget): def __init__(self): super(TabWidgetWithUndocking, self).__init__() self.tabBar().installEventFilter(self) def eventFilter(self, object, event): if object == self.tabBar(): if event.type() == QEvent.MouseButtonDblClick: pos = event.pos() tabIndex = object.tabAt(pos) title = self.tabText(tabIndex) widget = self.widget(tabIndex) self.removeTab(tabIndex) dockWidget = DockToTabWidget(title, parent=self) dockWidget.setFeatures(QDockWidget.AllDockWidgetFeatures) dockWidget.setWidget(widget) dockWidget.setFloating(True) dockWidget.move(self.mapToGlobal(pos)) dockWidget.show() return True return False def tabClose(self, index): self.removeTab(index) qApp = QApplication([]) qApp.setQuitOnLastWindowClosed(True) sys.excepthook = sys.__excepthook__ main = QMainWindow() main.setWindowTitle('Main') twu = TabWidgetWithUndocking() for i in range(2): twu.addTab(QLabel('tab %i' % i), 'tab %i' % i) main.setCentralWidget(twu) main.show() qApp.exec_()
Advertisement
Answer
I don’t remember the implementation of Qt4, but in Qt5 the double click always check that the dock widget was actually set on a valid QMainWindow parent before trying to toggle its top level state.
You’re not adding the QDockWidget, nor you’re using a QMainWindow, so the signal is never emitted. This also makes it problematic in some cases as it prevent proper handling of mouse events to allow dragging of the dock widget.
The only solution is to properly check for double clicks, and that can only happen by overriding event()
, since it’s internally managed by the dock widget in case a “title bar widget” is set, and mouseDoubleClickEvent
will be never called.
The following edit of the original code should work fine also for PyQt4.
class DockToTabWidget(QDockWidget): attachRequested = pyqtSignal(QWidget, str) def __init__(self, title, widget): QDockWidget.__init__(self, title, widget.window()) self.setFeatures(QDockWidget.AllDockWidgetFeatures) self.setWidget(widget) self.setFloating(True) floatButton = self.findChild(QAbstractButton, 'qt_dockwidget_floatbutton') floatButton.clicked.connect(self.attach) def attach(self): self.attachRequested.emit(self.widget(), self.windowTitle()) self.deleteLater() def event(self, event): if ( event.type() == event.MouseButtonDblClick and event.button() == Qt.LeftButton ): opt = QStyleOptionDockWidget() self.initStyleOption(opt) if event.pos() in opt.rect: self.attach() return True return super().event(event) class TabWidgetWithUndocking(QTabWidget): def __init__(self): super(TabWidgetWithUndocking, self).__init__() self.tabBar().installEventFilter(self) def eventFilter(self, obj, event): if obj == self.tabBar(): if event.type() == QEvent.MouseButtonDblClick: pos = event.pos() tabIndex = obj.tabAt(pos) title = self.tabText(tabIndex) widget = self.widget(tabIndex) self.removeTab(tabIndex) dockWidget = DockToTabWidget(title, widget) dockWidget.attachRequested.connect(self.attachDock) dockWidget.move(self.mapToGlobal(pos)) dockWidget.show() return True return False def attachDock(self, widget, title): self.setCurrentIndex(self.addTab(widget, title))
Note: object
is a built-in type in Python: while not forbidden, you should not use it as a variable name.