Search code examples
pythonpyqt4pysidemoveqtabwidget

PySide Qt4 - Qtabwidget - Disable drag and drop of a single q tab widget


I have a qtabwidget with one or more tabs in it at any given time. I would like the user to be able to rearrange the second through last tab in any order, but the first tab to remain at index 0.

From everything I've found, there is no way to enable setMovable independently for each tab.

The best workaround I've come up with thus far is to simply move the first tab back to position 0 if the user moves it (or another tab in front of it). This obviously isn't ideal, but it would be acceptable if it worked correctly... It works for a short while, however it occasionally crashed the application (It appears to happen when user drags a tab before it and holds the mouse there, so its in a constant loop of trying to rearrange and something low level crashes)

Any other suggestions on a feasible workaround (either to this method, or a similar widget where this capability would be easier to implement)? I figure its probably possible to re-class the QTabWidget in a way that it would ignore mouse drags on the first tab, but I'm not sure how I could go about preventing another tab from being moved before it...


Solution

  • The only way I've found, yet, to "pin" the first tab of a QTabWidget is by using a subclass of QTabBar. The global strategy consists to install an eventFilter on the subclass ofQTabBar and to conditionally block the MouseMove events in order to:

    1. fix the position of the first tab at index 0;
    2. prevent the left edge of the other tabs to be moved farther to the left of the right edge of the first tab. This prevent the other tabs to be moved in front of the first tab.

    The code below present a simple application to show how this can be done.

    import sys
    from PySide import QtGui, QtCore
    
    class myQTabBar(QtGui.QTabBar):
        def __init__(self, *args, **kargs):
            super(myQTabBar, self).__init__(*args, **kargs)
    
            self.setMovable(True)
            self.installEventFilter(self)
    
        def eventFilter(self, source, event):
    
            if event.type() == QtCore.QEvent.Type.MouseMove:
    
                if source.currentIndex() == 0: # Block MouseMove for first tab.             
                    return True 
    
                else: # For remaining tabs:
    
                      # block MouseMove if the left edge of the moving tab goes
                      # farther to the left than the right edge of first tab.
    
                    moving_leftEdge = event.pos().x() - self.edge_offset
                    fixed_rightEdge = self.tabRect(0).width()
    
                    if moving_leftEdge < fixed_rightEdge:                    
                        return True  
    
            elif event.type() == QtCore.QEvent.Type.MouseButtonPress:
    
                # Get mouse click horizontal position.
                xclick = event.pos().x()
    
                # Get the left edge horizontal position of the targeted tab.
                xleft = self.tabRect(self.tabAt(event.pos())).x()
    
                # Compute and store offset between mouse click horizontal 
                # position and the left edge of the targeted tab            
                self.edge_offset = xclick - xleft
    
            return QtGui.QWidget.eventFilter(self, source, event)
    
    class myQTabWidget(QtGui.QTabWidget):
        def __init__(self, *args, **kargs):
            super(myQTabWidget, self).__init__(*args, **kargs)
    
            tab_bar = myQTabBar()       
            self.setTabBar(tab_bar)
    
            self.addTab(QtGui.QWidget(), 'Tab1')
            self.addTab(QtGui.QWidget(), 'Tab2')
            self.addTab(QtGui.QWidget(), 'Tab3')
    
    
    if __name__ == '__main__':
    
        app = QtGui.QApplication(sys.argv)
        instance1 = myQTabWidget()
        instance1.show()
        sys.exit(app.exec_())
    

    Which results in:

    enter image description here