Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Draggable PyQt5 tabs in custom TabWidget work in min example, but not in practice



  • Here's the min example code that I put together from other sources found on web.

    from PyQt5.QtWidgets import QTabWidget
    from PyQt5.QtCore import Qt, QPoint, QMimeData
    from PyQt5.QtGui import QPixmap, QRegion, QDrag, QCursor
    
    class TabWidget(QTabWidget):
       def __init__(self, parent=None, new=None):
          super().__init__(parent)
          self.setAcceptDrops(True)
          self.tabBar().setMouseTracking(True)
          self.setMovable(True)
          if new:
             TabWidget.setup(self)
    
       def __setstate__(self, data):
          self.__init__(new=False)
          self.setParent(data['parent'])
          for widget, tabname in data['tabs']:
             self.addTab(widget, tabname)
          TabWidget.setup(self)
          
       def __getstate__(self):
          data = {
             'parent' : self.parent(),
             'tabs' : [],
          }
          tab_list = data['tabs']
          for k in range(self.count()):
             tab_name = self.tabText(k)
             widget = self.widget(k)
             tab_list.append((widget, tab_name))
          return data
             
       def setup(self):
          pass
    
       def mouseMoveEvent(self, e):
          if e.buttons() != Qt.RightButton:
             return
    
          globalPos = self.mapToGlobal(e.pos())
          tabBar = self.tabBar()
          posInTab = tabBar.mapFromGlobal(globalPos)
          print(e.pos())
          index = tabBar.tabAt(e.pos())
          tabBar.dragged_content = self.widget(index)
          tabBar.dragged_tabname = self.tabText(index)
          tabRect = tabBar.tabRect(index)
    
          pixmap = QPixmap(tabRect.size())
          tabBar.render(pixmap,QPoint(),QRegion(tabRect))
          mimeData = QMimeData()
                
          drag = QDrag(tabBar)
          drag.setMimeData(mimeData)
          drag.setPixmap(pixmap)
          
          cursor = QCursor(Qt.OpenHandCursor)
          
          drag.setHotSpot(e.pos() - posInTab)
          drag.setDragCursor(cursor.pixmap(),Qt.MoveAction)
          drag.exec_(Qt.MoveAction)
    
       def dragEnterEvent(self, e):
          e.accept()
          #self.parent().dragged_index = self.indexOf(self.widget(self.dragged_index))
    
       def dragLeaveEvent(self,e):
          e.accept()
    
       def dropEvent(self, e):
          if e.source().parentWidget() == self:
             return
    
          e.setDropAction(Qt.MoveAction)
          e.accept()
          tabBar = e.source()
          self.addTab(tabBar.dragged_content, tabBar.dragged_tabname)
       
    
    if __name__ == '__main__':
       from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout
       import sys
       
       class Window(QWidget):
          def __init__(self):
       
             super().__init__()
       
             self.dragged_index = None
             tabWidgetOne = TabWidget(self)
             tabWidgetTwo = TabWidget(self)
             tabWidgetOne.addTab(QWidget(), "tab1")
             tabWidgetTwo.addTab(QWidget(), "tab2")
       
             layout = QHBoxLayout()
       
             self.moveWidget = None
       
             layout.addWidget(tabWidgetOne)
             layout.addWidget(tabWidgetTwo)
       
             self.setLayout(layout)
    
       app = QApplication(sys.argv)
       window = Window()
       window1 = Window()
       window.show()
       window1.show()
       sys.exit(app.exec_())
    

    It demonstrates that the concept works also between windows started from the same application instance.

    However, when I deploy this class to my real world scenario. Namely this GUI:

    01654731-44a9-4f05-8ff0-0f446641ba78-image.png

    It doesn't work. I've found that tabBar.tabAt(event.pos()) is returning -1 meaning it can't find the tab that I'm pressing on and attempting to drag.

    Any intuitive reason why this should be happening in my app and not in the minimal example? My app does nothing fancy, and in particular nothing that would cause this to fail, just a standard application of custom MainWindow and etc widgets.

    5463b733-f429-4319-8065-c53dbb0afe35-image.png



  • @enjoysmath said in Draggable PyQt5 tabs in custom TabWidget work in min example, but not in practice:
    This is purely a guess:

      globalPos = self.mapToGlobal(e.pos())
      tabBar = self.tabBar()
      posInTab = tabBar.mapFromGlobal(globalPos)
      print(e.pos())
      index = tabBar.tabAt(e.pos())
    

    How do you know that the globalPos lies within the tabBar? Look at the tab bar's rectangle and see if the mouse pos really does lie inside it? Similarly, are there any "gaps"/"areas" in the tab bar such that a mouse pos does not actually lie in a particular tab? I would debug out those position coordinates and compare to what's actually occupied by your tab bar/tabs, to understand what is going on.



  •    def mouseMoveEvent(self, e):
          if e.buttons() != Qt.RightButton:
             return
    
          globalPos = self.mapToGlobal(e.pos())
          tabBar = self.tabBar()
          posInTab = tabBar.mapFromGlobal(globalPos)
          index = tabBar.tabAt(posInTab)
          tabBar.dragged_content = self.widget(index)
          tabBar.dragged_tabname = self.tabText(index)
          tabRect = tabBar.tabRect(index)
    
          pixmap = QPixmap(tabRect.size())
          tabBar.render(pixmap,QPoint(),QRegion(tabRect))
          mimeData = QMimeData()
                
          drag = QDrag(tabBar)
          drag.setMimeData(mimeData)
          drag.setPixmap(pixmap)
          
          cursor = QCursor(Qt.OpenHandCursor)
          
          drag.setHotSpot(posInTab)
          drag.setDragCursor(cursor.pixmap(),Qt.MoveAction)
          drag.exec_(Qt.MoveAction)
    

    Replace code with this code. In particular we don't take e.pos() except once at the top, then we use globalPos for computing poosInTab, then drag.setHotSpot(posInTab). It works!


Log in to reply