--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/EricWidgets/EricSideBar.py Thu Jul 07 11:23:56 2022 +0200 @@ -0,0 +1,452 @@ +# -*- 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