eric7/EricWidgets/EricTabWidget.py

Sat, 22 May 2021 19:58:24 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 22 May 2021 19:58:24 +0200
branch
eric7
changeset 8358
144a6b854f70
parent 8356
eric7/E5Gui/EricTabWidget.py@68ec9c3d4de5
child 8881
54e42bc2437a
permissions
-rw-r--r--

Sorted the eric specific extensions into packages named like the corresponding PyQt packages (i.e. EricCore,EricGui and EricWidgets).

# -*- coding: utf-8 -*-

# Copyright (c) 2005 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a TabWidget class substituting QTabWidget.
"""

import contextlib

from PyQt6.QtCore import pyqtSignal, Qt, QPoint, QMimeData
from PyQt6.QtGui import QDrag
from PyQt6.QtWidgets import QTabWidget, QTabBar, QApplication, QStyle

from EricWidgets.EricAnimatedLabel import EricAnimatedLabel


class EricWheelTabBar(QTabBar):
    """
    Class implementing a tab bar class substituting QTabBar to support wheel
    events.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super().__init__(parent)
        self._tabWidget = parent
    
    def wheelEvent(self, event):
        """
        Protected slot to support wheel events.
        
        @param event reference to the wheel event (QWheelEvent)
        """
        with contextlib.suppress(AttributeError):
            delta = event.angleDelta().y()
            if delta > 0:
                self._tabWidget.prevTab()
            elif delta < 0:
                self._tabWidget.nextTab()
            
            event.accept()


class EricDnDTabBar(EricWheelTabBar):
    """
    Class implementing a tab bar class substituting QTabBar.
    
    @signal tabMoveRequested(int, int) emitted to signal a tab move request
        giving the old and new index position
    """
    tabMoveRequested = pyqtSignal(int, int)
    
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        EricWheelTabBar.__init__(self, parent)
        self.setAcceptDrops(True)
        
        self.__dragStartPos = QPoint()
    
    def mousePressEvent(self, event):
        """
        Protected method to handle mouse press events.
        
        @param event reference to the mouse press event (QMouseEvent)
        """
        if event.button() == Qt.MouseButton.LeftButton:
            self.__dragStartPos = QPoint(event.position().toPoint())
        EricWheelTabBar.mousePressEvent(self, event)
    
    def mouseMoveEvent(self, event):
        """
        Protected method to handle mouse move events.
        
        @param event reference to the mouse move event (QMouseEvent)
        """
        if (
            event.buttons() == Qt.MouseButton.LeftButton and
            (event.position().toPoint() - self.__dragStartPos)
            .manhattanLength() > QApplication.startDragDistance()
        ):
            drag = QDrag(self)
            mimeData = QMimeData()
            index = self.tabAt(event.position().toPoint())
            mimeData.setText(self.tabText(index))
            mimeData.setData("action", b"tab-reordering")
            mimeData.setData("tabbar-id", str(id(self)).encode("utf-8"))
            drag.setMimeData(mimeData)
            drag.exec()
        EricWheelTabBar.mouseMoveEvent(self, event)
    
    def dragEnterEvent(self, event):
        """
        Protected method to handle drag enter events.
        
        @param event reference to the drag enter event (QDragEnterEvent)
        """
        mimeData = event.mimeData()
        formats = mimeData.formats()
        if (
            "action" in formats and
            mimeData.data("action") == b"tab-reordering" and
            "tabbar-id" in formats and
            int(mimeData.data("tabbar-id")) == id(self)
        ):
            event.acceptProposedAction()
        EricWheelTabBar.dragEnterEvent(self, event)
    
    def dropEvent(self, event):
        """
        Protected method to handle drop events.
        
        @param event reference to the drop event (QDropEvent)
        """
        fromIndex = self.tabAt(self.__dragStartPos)
        toIndex = self.tabAt(event.position().toPoint())
        if fromIndex != toIndex:
            self.tabMoveRequested.emit(fromIndex, toIndex)
            event.acceptProposedAction()
        EricWheelTabBar.dropEvent(self, event)


class EricTabWidget(QTabWidget):
    """
    Class implementing a tab widget class substituting QTabWidget.
    
    It provides slots to show the previous and next tab and give
    them the input focus and it allows to have a context menu for the tabs.
    
    @signal customTabContextMenuRequested(const QPoint & point, int index)
        emitted when a context menu for a tab is requested
    """
    customTabContextMenuRequested = pyqtSignal(QPoint, int)
    
    def __init__(self, parent=None, dnd=False):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        @param dnd flag indicating the support for Drag & Drop (boolean)
        """
        super().__init__(parent)
        
        if dnd:
            if not hasattr(self, 'setMovable'):
                self.__tabBar = EricDnDTabBar(self)
                self.__tabBar.tabMoveRequested.connect(self.moveTab)
                self.setTabBar(self.__tabBar)
            else:
                self.__tabBar = EricWheelTabBar(self)
                self.setTabBar(self.__tabBar)
                self.setMovable(True)
        else:
            self.__tabBar = EricWheelTabBar(self)
            self.setTabBar(self.__tabBar)
        
        self.__lastCurrentIndex = -1
        self.__currentIndex = -1
        self.currentChanged.connect(self.__currentChanged)
    
    def setCustomTabBar(self, dnd, tabBar):
        """
        Public method to set a custom tab bar.
        
        @param dnd flag indicating the support for Drag & Drop (boolean)
        @param tabBar reference to the tab bar to set (QTabBar)
        """
        self.__tabBar = tabBar
        self.setTabBar(self.__tabBar)
        if dnd:
            if isinstance(tabBar, EricDnDTabBar):
                self.__tabBar.tabMoveRequested.connect(self.moveTab)
            else:
                self.setMovable(True)
    
    def __currentChanged(self, index):
        """
        Private slot to handle the currentChanged signal.
        
        @param index index of the current tab
        """
        if index == -1:
            self.__lastCurrentIndex = -1
        else:
            self.__lastCurrentIndex = self.__currentIndex
        self.__currentIndex = index
        
    def switchTab(self):
        """
        Public slot used to switch between the current and the previous
        current tab.
        """
        if self.__lastCurrentIndex == -1 or self.__currentIndex == -1:
            return
        
        self.setCurrentIndex(self.__lastCurrentIndex)
        self.currentWidget().setFocus()
        
    def nextTab(self):
        """
        Public slot used to show the next tab.
        """
        ind = self.currentIndex() + 1
        if ind == self.count():
            ind = 0
            
        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()

    def prevTab(self):
        """
        Public slot used to show the previous tab.
        """
        ind = self.currentIndex() - 1
        if ind == -1:
            ind = self.count() - 1
            
        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()

    def setTabContextMenuPolicy(self, policy):
        """
        Public method to set the context menu policy of the tab.
        
        @param policy context menu policy to set (Qt.ContextMenuPolicy)
        """
        self.tabBar().setContextMenuPolicy(policy)
        if policy == Qt.ContextMenuPolicy.CustomContextMenu:
            self.tabBar().customContextMenuRequested.connect(
                self.__handleTabCustomContextMenuRequested)
        else:
            self.tabBar().customContextMenuRequested.disconnect(
                self.__handleTabCustomContextMenuRequested)

    def __handleTabCustomContextMenuRequested(self, point):
        """
        Private slot to handle the context menu request for the tabbar.
        
        @param point point the context menu was requested (QPoint)
        """
        _tabbar = self.tabBar()
        for index in range(_tabbar.count()):
            rect = _tabbar.tabRect(index)
            if rect.contains(point):
                self.customTabContextMenuRequested.emit(
                    _tabbar.mapToParent(point), index)
                return
        
        self.customTabContextMenuRequested.emit(_tabbar.mapToParent(point), -1)
    
    def selectTab(self, pos):
        """
        Public method to get the index of a tab given a position.
        
        @param pos position determining the tab index (QPoint)
        @return index of the tab (integer)
        """
        _tabbar = self.tabBar()
        for index in range(_tabbar.count()):
            rect = _tabbar.tabRect(index)
            if rect.contains(pos):
                return index
        
        return -1

    def moveTab(self, curIndex, newIndex):
        """
        Public method to move a tab to a new index.
        
        @param curIndex index of tab to be moved (integer)
        @param newIndex index the tab should be moved to (integer)
        """
        # step 1: save the tab data of tab to be moved
        toolTip = self.tabToolTip(curIndex)
        text = self.tabText(curIndex)
        icon = self.tabIcon(curIndex)
        whatsThis = self.tabWhatsThis(curIndex)
        widget = self.widget(curIndex)
        curWidget = self.currentWidget()
        
        # step 2: move the tab
        self.removeTab(curIndex)
        self.insertTab(newIndex, widget, icon, text)
        
        # step 3: set the tab data again
        self.setTabToolTip(newIndex, toolTip)
        self.setTabWhatsThis(newIndex, whatsThis)
        
        # step 4: set current widget
        self.setCurrentWidget(curWidget)
    
    def __freeSide(self):
        """
        Private method to determine the free side of a tab.
        
        @return free side (QTabBar.ButtonPosition)
        """
        side = self.__tabBar.style().styleHint(
            QStyle.StyleHint.SH_TabBar_CloseButtonPosition,
            None, None, None)
        side = (
            QTabBar.ButtonPosition.RightSide
            if side == QTabBar.ButtonPosition.LeftSide else
            QTabBar.ButtonPosition.LeftSide
        )
        return side
    
    def animationLabel(self, index, animationFile, interval=100):
        """
        Public slot to set an animated icon.
        
        @param index tab index
        @type int
        @param animationFile name of the file containing the animation
        @type str
        @param interval interval in milliseconds between animation frames
        @type int
        @return reference to the created label
        @rtype EricAnimatedLabel
        """
        if index == -1:
            return None
        
        if hasattr(self.__tabBar, 'setTabButton'):
            side = self.__freeSide()
            animation = EricAnimatedLabel(
                self, animationFile=animationFile, interval=interval)
            self.__tabBar.setTabButton(index, side, None)
            self.__tabBar.setTabButton(index, side, animation)
            animation.start()
            return animation
        else:
            return None
    
    def resetAnimation(self, index):
        """
        Public slot to reset an animated icon.
        
        @param index tab index (integer)
        """
        if index == -1:
            return
        
        if hasattr(self.__tabBar, 'tabButton'):
            side = self.__freeSide()
            animation = self.__tabBar.tabButton(index, side)
            if animation is not None:
                animation.stop()
                self.__tabBar.setTabButton(index, side, None)
                del animation

eric ide

mercurial