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:
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.
-
@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 thetabBar
? 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!