RickardSjogren
RickardSjogren

Reputation: 4238

Inserted tabs not showing in QTabWidget despite show() called

I have a QTabWidget in PyQt which provides the possibility to tear off tabs and the re-attach them when they are closed. It works well except that newly attached tabs aren't immediately showing. A blank widget is showing and the widget is shown only after the current tab has been changed and then changed back.

I've searched StackOverflow and the answers to similar problems have pointed out that the added widget's show()-method needs to be called after the new tab is added. I tried that but the newly added tab is still not showing.

Slimmed down example code:

from PyQt4 import QtGui, QtCore


class DetachableTabWidget(QtGui.QTabWidget):

    """ Subclass of QTabWidget which provides the ability to detach
    tabs and making floating windows from them which can be reattached.
    """

    def __init__(self, *args, **kwargs):
        super(DetachableTabWidget, self).__init__(*args, **kwargs)
        self.setTabBar(_DetachableTabBar())

    def detach_tab(self, i):
        """ Make floating window of tab.

        :param i: index of tab to detach
        """
        teared_widget = self.widget(i)
        widget_name = self.tabText(i)

        # Shift index to the left and remove tab.
        self.setCurrentIndex(self.currentIndex() - 1 if self.currentIndex() > 0 else 0)
        self.removeTab(i)

        # Store widgets window-flags and close event.
        teared_widget._flags = teared_widget.windowFlags()
        teared_widget._close = teared_widget.closeEvent

        # Make stand-alone window.
        teared_widget.setWindowFlags(QtCore.Qt.Window)
        teared_widget.show()

        # Redirect windows close-event into reattachment.
        teared_widget.closeEvent = lambda event: self.attach_tab(teared_widget, widget_name)

    def attach_tab(self, widget, name):
        """ Attach widget when receiving signal from child-window.

        :param widget: :class:`QtGui.QWidget`
        :param name: name of attached widget
        """
        widget.setWindowFlags(widget._flags)
        widget.closeEvent = widget._close
        self.addTab(widget, name)
        self.setCurrentWidget(widget)
        self.currentWidget().show()


class _DetachableTabBar(QtGui.QTabBar):

    def __init__(self, *args, **kwargs):
        super(_DetachableTabBar, self).__init__(*args, **kwargs)
        self._start_drag_pos = None
        self._has_dragged = False
        self.setMovable(True)

    def mousePressEvent(self, event):
        # Keep track of where drag-starts.
        self._start_drag_pos = event.globalPos()
        super(_DetachableTabBar, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        # If tab is already detached, do nothing.
        if self._has_dragged:
            return

        # Detach-tab if drag in y-direction is large enough.
        if abs((self._start_drag_pos - event.globalPos()).y()) >= QtGui.QApplication.startDragDistance()*8:
            self._has_dragged = True
            self.parent().detach_tab(self.currentIndex())

    def mouseReleaseEvent(self, event):
        self._has_dragged = False


if __name__ == '__main__':
    import sys
    app = QtGui.QApplication(sys.argv)
    window = QtGui.QMainWindow()
    widget = DetachableTabWidget()

    widget.addTab(QtGui.QLabel('Tab 1'), 'tab 1')
    widget.addTab(QtGui.QLabel('Tab 2'), 'tab 2')

    window.setCentralWidget(widget)
    window.show()
    sys.exit(app.exec_())

Upvotes: 1

Views: 967

Answers (1)

It seems than when you accept the QCloseEvent (default behavior) the widget is hidden at the end of your closeEvent handler. But your call to show() occurs before the end of the handler. The solution is to ignore the QCloseEvent.

    ...
    teared_widget.closeEvent = lambda event: self.attach_tab(teared_widget, widget_name, event)

def attach_tab(self, widget, name, event):
    """ Attach widget when receiving signal from child-window.

    :param widget: :class:`QtGui.QWidget`
    :param name: name of attached widget
    :param event: close Event
    """
    widget.setWindowFlags(widget._flags)
    widget.closeEvent = widget._close
    self.addTab(widget, name)
    self.setCurrentWidget(widget)
    event.ignore()

Upvotes: 3

Related Questions