
import sys

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *


class TabBar(QTabBar):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setMouseTracking(True)
        self.tabMoved.connect(self.tab_moved)
    
    def start_tab_drag(self):
        self.parent().start_tab_drag()
    
    def tab_moved(self, index_from, index_to):
        if index_from == self.parent().indexTab:
            self.parent().indexTab = index_to
            print(index_from, index_to)
            
    
    def mousePressEvent(self, e):
        super().mousePressEvent(e)
        self.parent().store_drag_data(e)
        print("store")
    
    def enterEvent(self, e):
        super().enterEvent(e)
        print("enter")
    
    def leaveEvent(self, e):
        super().leaveEvent(e)
        print("leave")

class TabWidget(QTabWidget):
    move_range = None
    drag_lock = None
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setTabBar(TabBar())
        self.tabBar().installEventFilter(self)
        self.drag_lock = False
    
    def store_drag_data(self, e):
        globalPos = self.mapToGlobal(e.pos())
        tabBar = self.tabBar()
        self.posInTab = tabBar.mapFromGlobal(globalPos)
        self.indexTab = tabBar.tabAt(e.pos())
        self.tabRect = tabBar.tabRect(self.indexTab)
        self.pixmap = QPixmap(self.tabRect.size())
        tabBar.render(self.pixmap,QPoint(),QRegion(self.tabRect))
        painter = QPainter(self.pixmap)
        painter.setCompositionMode(painter.CompositionMode_DestinationIn)
        painter.fillRect(self.pixmap.rect(), QColor(0, 0, 0, 64))
        painter.end()
    
    def start_tab_drag(self):
        tabBar = self.tabBar()
#        tabBar.render(self.pixmap,QPoint(),QRegion(self.tabRect))
        mimeData = QMimeData()
        drag = QDrag(tabBar)
        drag.setMimeData(mimeData)
        drag.setPixmap(self.pixmap)
        cursor = QCursor(Qt.OpenHandCursor)
#        drag.setHotSpot(self.e.pos() - self.posInTab)
        drag.setHotSpot(QPoint(self.tabRect.width()/2, self.tabRect.height()/2))
        drag.setDragCursor(cursor.pixmap(),Qt.MoveAction)
        dropAction = drag.exec_(Qt.MoveAction)

    def eventFilter(self, source, event):
        if source == self.tabBar():
            if event.type() == QEvent.MouseButtonPress and event.buttons() == Qt.LeftButton:
                QTimer.singleShot(0, self.setmove_range)
            elif event.type() == QEvent.MouseButtonRelease:
                self.move_range = None
            elif event.type() == QEvent.MouseMove and self.move_range is not None:
#                if event.x() < self.move_range[0] or event.x() > self.tabBar().width() - self.move_range[1]:
#                    return True
                if event.x() < self.move_range[0]:
                    new_event = QMouseEvent(
                        QEvent.MouseMove,
                        QPoint(self.move_range[0], event.y()),
                        event.button(),
                        event.buttons(),
                        Qt.NoModifier,
                    )
                    QCoreApplication.sendEvent(self.tabBar(), new_event)
                    return True
                elif event.x() > self.tabBar().width() - self.move_range[1]:
                    new_event = QMouseEvent(
                        QEvent.MouseMove,
                        QPoint(self.tabBar().width() - self.move_range[1], event.y()),
                        event.button(),
                        event.buttons(),
                        Qt.NoModifier,
                    )
                    QCoreApplication.sendEvent(self.tabBar(), new_event)
                    return True
            elif event.type() == QEvent.HoverMove:
                pos = event.pos()
                if self.tabBar().rect().contains(pos):
                    self.drag_lock = False
                else:
                    buttons = app.mouseButtons()
                    if buttons == Qt.LeftButton:
                        if self.drag_lock == False:
                            self.drag_lock = True
                            self.start_tab_drag()
                            new_event = QMouseEvent(
                                QEvent.PolishRequest,
                                QPoint(self.tabBar().width()/2, self.tabBar().height()/2),
                                Qt.NoButton,
                                Qt.NoButton,
                                Qt.NoModifier,
                            )
                            QCoreApplication.sendEvent(self.tabBar(), new_event)
                    else:
                        self.drag_lock = False
        return QTabWidget.eventFilter(self, source, event)

    def setmove_range(self):
        tabRect = self.tabBar().tabRect(self.currentIndex())
        pos = self.tabBar().mapFromGlobal(QCursor.pos())
        self.move_range = pos.x() - tabRect.left(), tabRect.right() - pos.x()

class Window(QWidget):
    def __init__(self):

        super().__init__()
        tabWidgetOne = TabWidget(self)
        tabWidgetOne.setMovable(True)
        tabWidgetOne.addTab(QWidget(tabWidgetOne), 'Tab One')
        tabWidgetOne.addTab(QWidget(tabWidgetOne), 'Tab Two')
        layout = QHBoxLayout()
        layout.addWidget(tabWidgetOne)
        self.setLayout(layout)

if __name__ == '__main__':

    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())