Skip to content
Advertisement

QDockWidget does not dock any longer on doubleclick in pyQt5

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.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement