eric7/EricWidgets/EricSideBar.py

Thu, 09 Sep 2021 19:22:12 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 09 Sep 2021 19:22:12 +0200
branch
eric7
changeset 8587
78971b458d25
parent 8584
90391fda03d5
child 8588
84dfb28716e2
permissions
-rw-r--r--

Made the height/width of the icons bar configurable.

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

# Copyright (c) 2008 - 2021 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 = 3
    
    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(0)
        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.splitter = None
        self.splitterSizes = []
        
        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 setSplitter(self, splitter):
        """
        Public method to set the splitter managing the sidebar.
        
        @param splitter reference to the splitter (QSplitter)
        """
        self.splitter = splitter
        self.splitter.splitterMoved.connect(self.__splitterMoved)
        self.splitter.setChildrenCollapsible(False)
        index = self.splitter.indexOf(self)
        self.splitter.setCollapsible(index, False)
    
    def __splitterMoved(self, pos, index):
        """
        Private slot to react on splitter moves.
        
        @param pos new position of the splitter handle (integer)
        @param index index of the splitter handle (integer)
        """
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()
    
    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()
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()
        
        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)
        if self.splitter:
            self.splitter.setSizes(self.splitterSizes)
    
    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)
    
    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)
        """
        if len(self.splitterSizes) == 0:
            if self.splitter:
                self.splitterSizes = self.splitter.sizes()
            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,
            "splitter_sizes": self.splitterSizes,
        }
        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):
            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)
            self.splitterSizes = stateDict["splitter_sizes"]
            
            if not stateDict["minimized"]:
                self.__expandIt()
            
            return True
        
        return False

eric ide

mercurial