Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Draggable PyQt5 tabs in custom TabWidget work in min example, but not in practice
Forum Updated to NodeBB v4.3 + New Features

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

Scheduled Pinned Locked Moved Solved General and Desktop
3 Posts 2 Posters 534 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • enjoysmathE Offline
    enjoysmathE Offline
    enjoysmath
    wrote on last edited by
    #1

    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

    https://github.com/enjoysmath
    https://math.stackexchange.com/users/26327/exercisingmathematician

    JonBJ 1 Reply Last reply
    0
    • enjoysmathE enjoysmath

      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

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by JonB
      #2

      @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.

      1 Reply Last reply
      2
      • enjoysmathE Offline
        enjoysmathE Offline
        enjoysmath
        wrote on last edited by
        #3
           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!

        https://github.com/enjoysmath
        https://math.stackexchange.com/users/26327/exercisingmathematician

        1 Reply Last reply
        1

        • Login

        • Login or register to search.
        • First post
          Last post
        0
        • Categories
        • Recent
        • Tags
        • Popular
        • Users
        • Groups
        • Search
        • Get Qt Extensions
        • Unsolved