eric7/EricWidgets/EricSideBar.py

Thu, 30 Dec 2021 11:17:58 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 30 Dec 2021 11:17:58 +0100
branch
eric7
changeset 8881
54e42bc2437a
parent 8722
2f57e52a704b
permissions
-rw-r--r--

Updated copyright for 2022.

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

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

"""
Module implementing a sidebar class.
"""

import enum
import json

from PyQt6.QtCore import pyqtSlot, Qt, QSize
from PyQt6.QtWidgets import QWidget, QStackedWidget, QBoxLayout

from .EricIconBar import EricIconBar


class EricSideBarSide(enum.Enum):
    """
    Class defining the sidebar sides.
    """
    NORTH = 0
    EAST = 1
    SOUTH = 2
    WEST = 3


class EricSideBar(QWidget):
    """
    Class implementing a sidebar with a widget area, that is hidden or shown,
    if the current tab is clicked again.
    """
    Version = 4
    
    def __init__(self, orientation=None,
                 iconBarSize=EricIconBar.DefaultBarSize, parent=None):
        """
        Constructor
        
        @param orientation orientation of the sidebar widget
        @type EricSideBarSide
        @param iconBarSize size category for the bar (one of 'xs', 'sm', 'md',
            'lg', 'xl', 'xxl')
        @type str
        @param parent parent widget
        @type QWidget
        """
        super().__init__(parent)
        
        # initial layout is done for NORTH
        self.__iconBar = EricIconBar(orientation=Qt.Orientation.Horizontal,
                                     barSize=iconBarSize)
        
        self.__stackedWidget = QStackedWidget(self)
        self.__stackedWidget.setContentsMargins(0, 0, 0, 0)
        
        self.layout = QBoxLayout(QBoxLayout.Direction.TopToBottom)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(3)
        self.layout.addWidget(self.__iconBar)
        self.layout.addWidget(self.__stackedWidget)
        self.setLayout(self.layout)
        
        self.__minimized = False
        self.__minSize = 0
        self.__maxSize = 0
        self.__bigSize = QSize()
        
        self.__hasFocus = False
        # flag storing if this widget or any child has the focus
        self.__autoHide = False
        
        self.__orientation = EricSideBarSide.NORTH
        if orientation is None:
            orientation = EricSideBarSide.NORTH
        self.setOrientation(orientation)
        
        self.__iconBar.currentChanged.connect(
            self.__stackedWidget.setCurrentIndex)
        self.__iconBar.currentChanged.connect(
            self.__currentIconChanged)
        self.__iconBar.currentClicked.connect(
            self.__currentIconClicked)
    
    def __shrinkIt(self):
        """
        Private method to shrink the sidebar.
        """
        self.__minimized = True
        self.__bigSize = self.size()
        if self.__orientation in (
            EricSideBarSide.NORTH, EricSideBarSide.SOUTH
        ):
            self.__minSize = self.minimumSizeHint().height()
            self.__maxSize = self.maximumHeight()
        else:
            self.__minSize = self.minimumSizeHint().width()
            self.__maxSize = self.maximumWidth()
        
        self.__stackedWidget.hide()
        
        if self.__orientation in (
            EricSideBarSide.NORTH, EricSideBarSide.SOUTH
        ):
            self.setFixedHeight(self.__iconBar.minimumSizeHint().height())
        else:
            self.setFixedWidth(self.__iconBar.minimumSizeHint().width())
    
    def __expandIt(self):
        """
        Private method to expand the sidebar.
        """
        self.__minimized = False
        self.__stackedWidget.show()
        self.resize(self.__bigSize)
        if self.__orientation in (
            EricSideBarSide.NORTH, EricSideBarSide.SOUTH
        ):
            minSize = max(self.__minSize, self.minimumSizeHint().height())
            self.setMinimumHeight(minSize)
            self.setMaximumHeight(self.__maxSize)
        else:
            minSize = max(self.__minSize, self.minimumSizeHint().width())
            self.setMinimumWidth(minSize)
            self.setMaximumWidth(self.__maxSize)
    
    def isMinimized(self):
        """
        Public method to check the minimized state.
        
        @return flag indicating the minimized state (boolean)
        """
        return self.__minimized
    
    @pyqtSlot(int)
    def __currentIconChanged(self, index):
        """
        Private slot to handle a change of the current icon.
        
        @param index index of the current icon
        @type int
        """
        if self.isMinimized():
            self.__expandIt()
    
    @pyqtSlot(int)
    def __currentIconClicked(self, index):
        """
        Private slot to handle a click of the current icon.
        
        @param index index of the clicked icon
        @type int
        """
        if self.isMinimized():
            self.__expandIt()
        else:
            self.__shrinkIt()
    
    def addTab(self, widget, icon, label=None):
        """
        Public method to add a tab to the sidebar.
        
        @param widget reference to the widget to add
        @type QWidget
        @param icon reference to the icon of the widget
        @type QIcon
        @param label the label text of the widget
        @type str
        """
        self.__iconBar.addIcon(icon, label)
        self.__stackedWidget.addWidget(widget)
        if self.__orientation in (
            EricSideBarSide.NORTH, EricSideBarSide.SOUTH
        ):
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def insertTab(self, index, widget, icon, label=None):
        """
        Public method to insert a tab into the sidebar.
        
        @param index the index to insert the tab at
        @type int
        @param widget reference to the widget to insert
        @type QWidget
        @param icon reference to the icon of the widget
        @type QIcon
        @param label the label text of the widget
        @type str
        """
        self.__iconBar.insertIcon(index, icon, label)
        
        self.__stackedWidget.insertWidget(index, widget)
        if self.__orientation in (
            EricSideBarSide.NORTH, EricSideBarSide.SOUTH
        ):
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def removeTab(self, index):
        """
        Public method to remove a tab.
        
        @param index the index of the tab to remove
        @type int
        """
        self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index))
        self.__iconBar.removeIcon(index)
        if self.__orientation in (
            EricSideBarSide.NORTH, EricSideBarSide.SOUTH
        ):
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def clear(self):
        """
        Public method to remove all tabs.
        """
        while self.count() > 0:
            self.removeTab(0)
    
    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 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 count(self):
        """
        Public method to get the number of tabs.
        
        @return number of tabs in the sidebar (integer)
        """
        return self.__iconBar.count()
    
    def currentIndex(self):
        """
        Public method to get the index of the current tab.
        
        @return index of the current tab (integer)
        """
        return self.__stackedWidget.currentIndex()
    
    def setCurrentIndex(self, index):
        """
        Public slot to set the current index.
        
        @param index the index to set as the current index (integer)
        """
        self.__iconBar.setCurrentIndex(index)
        self.__stackedWidget.setCurrentIndex(index)
        if self.isMinimized():
            self.__expandIt()
    
    def currentWidget(self):
        """
        Public method to get a reference to the current widget.
        
        @return reference to the current widget (QWidget)
        """
        return self.__stackedWidget.currentWidget()
    
    def setCurrentWidget(self, widget):
        """
        Public slot to set the current widget.
        
        @param widget reference to the widget to become the current widget
            (QWidget)
        """
        self.__stackedWidget.setCurrentWidget(widget)
        self.__iconBar.setCurrentIndex(self.__stackedWidget.currentIndex())
        if self.isMinimized():
            self.__expandIt()
    
    def indexOf(self, widget):
        """
        Public method to get the index of the given widget.
        
        @param widget reference to the widget to get the index of (QWidget)
        @return index of the given widget (integer)
        """
        return self.__stackedWidget.indexOf(widget)
    
    def orientation(self):
        """
        Public method to get the orientation of the sidebar.
        
        @return orientation of the sidebar
        @rtype EricSideBarSide
        """
        return self.__orientation
    
    def setOrientation(self, orient):
        """
        Public method to set the orientation of the sidebar.

        @param orient orientation of the sidebar
        @type EricSideBarSide
        """
        if orient == EricSideBarSide.NORTH:
            self.__iconBar.setOrientation(Qt.Orientation.Horizontal)
            self.layout.setDirection(QBoxLayout.Direction.TopToBottom)
        elif orient == EricSideBarSide.EAST:
            self.__iconBar.setOrientation(Qt.Orientation.Vertical)
            self.layout.setDirection(QBoxLayout.Direction.RightToLeft)
        elif orient == EricSideBarSide.SOUTH:
            self.__iconBar.setOrientation(Qt.Orientation.Horizontal)
            self.layout.setDirection(QBoxLayout.Direction.BottomToTop)
        elif orient == EricSideBarSide.WEST:
            self.__iconBar.setOrientation(Qt.Orientation.Vertical)
            self.layout.setDirection(QBoxLayout.Direction.LeftToRight)
        self.__orientation = orient
    
    def widget(self, index):
        """
        Public method to get a reference to the widget associated with a tab.
        
        @param index index of the tab (integer)
        @return reference to the widget (QWidget)
        """
        return self.__stackedWidget.widget(index)
    
    def setIconBarColor(self, color):
        """
        Public method to set the icon bar color.
        
        @param color icon bar color
        @type QColor
        """
        self.__iconBar.setColor(color)
    
    def iconBarColor(self):
        """
        Public method to get the icon bar color.
        
        @return icon bar color
        @rtype QColor
        """
        return self.__iconBar.color()
    
    def setIconBarSize(self, barSize):
        """
        Public method to set the icon bar size.
        
        @param barSize size category for the bar (one of 'xs', 'sm', 'md',
            'lg', 'xl', 'xxl')
        @type str
        """
        self.__iconBar.setBarSize(barSize)
        if self.isMinimized():
            self.__shrinkIt()
        else:
            self.__expandIt()
    
    def barSize(self):
        """
        Public method to get the icon bar size.
        
        @return barSize size category for the bar (one of 'xs', 'sm', 'md',
            'lg', 'xl', 'xxl')
        @rtype str
        """
        return self.__iconBar.barSize()
    
    def saveState(self):
        """
        Public method to save the state of the sidebar.
        
        @return saved state as a byte array (QByteArray)
        """
        self.__bigSize = self.size()
        if self.__orientation in (
            EricSideBarSide.NORTH, EricSideBarSide.SOUTH
        ):
            self.__minSize = self.minimumSizeHint().height()
            self.__maxSize = self.maximumHeight()
        else:
            self.__minSize = self.minimumSizeHint().width()
            self.__maxSize = self.maximumWidth()
        
        dataDict = {
            "version": self.Version,
            "minimized": self.__minimized,
            "big_size": [self.__bigSize.width(), self.__bigSize.height()],
            "min_size": self.__minSize,
            "max_size": self.__maxSize,
        }
        data = json.dumps(dataDict)
        
        return data
    
    def restoreState(self, state):
        """
        Public method to restore the state of the sidebar.
        
        @param state byte array containing the saved state (QByteArray)
        @return flag indicating success (boolean)
        """
        if not isinstance(state, str) or state == "":
            return False
        
        try:
            stateDict = json.loads(state)
        except json.JSONDecodeError:
            return False
        
        if not stateDict:
            return False
        
        if self.__orientation in (
            EricSideBarSide.NORTH, EricSideBarSide.SOUTH
        ):
            minSize = self.layout.minimumSize().height()
            maxSize = self.maximumHeight()
        else:
            minSize = self.layout.minimumSize().width()
            maxSize = self.maximumWidth()
        
        if stateDict["version"] in (2, 3, 4):
            if stateDict["minimized"] and not self.__minimized:
                self.__shrinkIt()
            
            self.__bigSize = QSize(*stateDict["big_size"])
            self.__minSize = max(stateDict["min_size"], minSize)
            self.__maxSize = max(stateDict["max_size"], maxSize)
            
            if not stateDict["minimized"]:
                self.__expandIt()
            
            return True
        
        return False

eric ide

mercurial