From 003615c3bede4c399fb5554d07303246b3db6c35 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 8 Nov 2023 21:27:14 +0100 Subject: [PATCH] Make some minor modifications to the doc toolbar, and add typings in themes --- novelwriter/gui/doceditor.py | 14 +--- novelwriter/gui/noveltree.py | 2 +- novelwriter/gui/theme.py | 133 +++++++++++++++---------------- tests/test_gui/test_gui_theme.py | 6 +- 4 files changed, 69 insertions(+), 86 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index f0aa7041..a3bc291c 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -2188,18 +2188,6 @@ class GuiDocToolBar(QWidget): palette.setColor(QPalette.Text, QColor(*SHARED.theme.colText)) self.setPalette(palette) - # qPalette = self.palette() - # qPalette.setBrush(QPalette.Window, qPalette.base()) - # self.setPalette(qPalette) - - # fadeCol = qPalette.text().color() - # buttonStyle = ( - # "QToolButton {{padding: {0}px; border: none; background: transparent;}} " - # "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}" - # ).format(CONFIG.pxInt(4), fadeCol.red(), fadeCol.green(), fadeCol.blue()) - # buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}" - # self.tbEdit.setStyleSheet(buttonStyle) - tPx = int(0.8*SHARED.theme.fontPixelSize) self.tbMode.setIcon(SHARED.theme.getToggleIcon("fmt_mode", (tPx, tPx))) self.tbBold.setIcon(SHARED.theme.getIcon("fmt_bold")) @@ -2212,7 +2200,7 @@ class GuiDocToolBar(QWidget): return ## - # Internal Slots + # Private Slots ## @pyqtSlot(bool) diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 24683733..40aae9ef 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -468,7 +468,7 @@ class GuiNovelTree(QTreeWidget): def updateTheme(self) -> None: """Update theme elements.""" iPx = SHARED.theme.baseIconSize - self._pMore = SHARED.theme.loadDecoration("deco_doc_more", pxH=iPx) + self._pMore = SHARED.theme.loadDecoration("deco_doc_more", h=iPx) return ## diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 22d8cf95..6c4d7e76 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -36,7 +36,7 @@ from PyQt5.QtGui import ( ) from novelwriter import CONFIG -from novelwriter.enum import nwItemLayout, nwItemType +from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.error import logException from novelwriter.common import NWConfigParser, minmax from novelwriter.constants import nwLabels @@ -51,7 +51,7 @@ logger = logging.getLogger(__name__) class GuiTheme: - def __init__(self): + def __init__(self) -> None: self.iconCache = GuiIcons(self) @@ -183,7 +183,7 @@ class GuiTheme: # Theme Methods ## - def loadTheme(self): + def loadTheme(self) -> bool: """Load the currently specified GUI theme.""" guiTheme = CONFIG.guiTheme if guiTheme not in self._availThemes: @@ -270,7 +270,7 @@ class GuiTheme: return True - def loadSyntax(self): + def loadSyntax(self) -> bool: """Load the currently specified syntax highlighter theme.""" guiSyntax = CONFIG.guiSyntax if guiSyntax not in self._availSyntax: @@ -364,7 +364,7 @@ class GuiTheme: # Internal Functions ## - def _setGuiFont(self): + def _setGuiFont(self) -> None: """Update the GUI's font style from settings.""" theFont = QFont() fontDB = QFontDatabase() @@ -396,9 +396,7 @@ class GuiTheme: return True - def _parseColour( - self, parser: NWConfigParser, section: str, name: str - ) -> list[int]: + def _parseColour(self, parser: NWConfigParser, section: str, name: str) -> list[int]: """Parse a colour value from a config string.""" if parser.has_option(section, name): values = parser.get(section, name).split(",") @@ -415,9 +413,8 @@ class GuiTheme: result = [0, 0, 0] return result - def _setPalette( - self, parser: NWConfigParser, section: str, name: str, value: QPalette.ColorRole - ): + def _setPalette(self, parser: NWConfigParser, section: str, + name: str, value: QPalette.ColorRole) -> None: """Set a palette colour value from a config string.""" self._guiPalette.setColor( value, QColor(*self._parseColour(parser, section, name)) @@ -488,17 +485,17 @@ class GuiIcons: "wiz-back": "wizard-back.jpg", } - def __init__(self, mainTheme): + def __init__(self, mainTheme: GuiTheme) -> None: self.mainTheme = mainTheme # Storage - self._qIcons = {} - self._themeMap = {} - self._headerDec = [] - self._confName = "icons.conf" + self._qIcons: dict[str, QIcon] = {} + self._themeMap: dict[str, Path] = {} + self._headerDec: list[QPixmap] = [] # Icon Theme Path + self._confName = "icons.conf" self._iconPath = CONFIG.assetPath("icons") # Icon Theme Meta @@ -516,7 +513,7 @@ class GuiIcons: # Actions ## - def loadTheme(self, iconTheme): + def loadTheme(self, iconTheme: str) -> bool: """Update the theme map. This is more of an init, since many of the GUI icons cannot really be replaced without writing specific update functions for the classes where they're used. @@ -590,60 +587,60 @@ class GuiIcons: # Access Functions ## - def loadDecoration(self, decoKey, pxW=None, pxH=None): + def loadDecoration(self, name: str, w: int | None = None, h: int | None = None) -> QPixmap: """Load graphical decoration element based on the decoration map or the icon map. This function always returns a QPixmap. """ - if decoKey in self._themeMap: - imgPath = self._themeMap[decoKey] - elif decoKey in self.IMAGE_MAP: - imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[decoKey] + if name in self._themeMap: + imgPath = self._themeMap[name] + elif name in self.IMAGE_MAP: + imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name] else: - logger.error("Decoration with name '%s' does not exist", decoKey) + logger.error("Decoration with name '%s' does not exist", name) return QPixmap() if not imgPath.is_file(): logger.error("Asset not found: %s", imgPath) return QPixmap() - theDeco = QPixmap(str(imgPath)) - if pxW is not None and pxH is not None: - return theDeco.scaled(pxW, pxH, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) - elif pxW is None and pxH is not None: - return theDeco.scaledToHeight(pxH, Qt.SmoothTransformation) - elif pxW is not None and pxH is None: - return theDeco.scaledToWidth(pxW, Qt.SmoothTransformation) + pixmap = QPixmap(str(imgPath)) + if w is not None and h is not None: + return pixmap.scaled(w, h, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) + elif w is None and h is not None: + return pixmap.scaledToHeight(h, Qt.SmoothTransformation) + elif w is not None and h is None: + return pixmap.scaledToWidth(w, Qt.SmoothTransformation) - return theDeco + return pixmap - def getIcon(self, iconKey): + def getIcon(self, name: str) -> QIcon: """Return an icon from the icon buffer, or load it.""" - if iconKey in self._qIcons: - return self._qIcons[iconKey] + if name in self._qIcons: + return self._qIcons[name] else: - qIcon = self._loadIcon(iconKey) - self._qIcons[iconKey] = qIcon - return qIcon + icon = self._loadIcon(name) + self._qIcons[name] = icon + return icon - def getToggleIcon(self, iconKey: str, iconSize: tuple[int, int]) -> QIcon: + def getToggleIcon(self, name: str, size: tuple[int, int]) -> QIcon: """Return a toggle icon from the icon buffer. or load it.""" - if iconKey in self.TOGGLE_ICON_KEYS: - pixOne = self.getPixmap(self.TOGGLE_ICON_KEYS[iconKey][0], iconSize) - pixTwo = self.getPixmap(self.TOGGLE_ICON_KEYS[iconKey][1], iconSize) - qIcon = QIcon() - qIcon.addPixmap(pixOne, QIcon.Normal, QIcon.On) - qIcon.addPixmap(pixTwo, QIcon.Normal, QIcon.Off) - return qIcon + if name in self.TOGGLE_ICON_KEYS: + pOne = self.getPixmap(self.TOGGLE_ICON_KEYS[name][0], size) + pTwo = self.getPixmap(self.TOGGLE_ICON_KEYS[name][1], size) + icon = QIcon() + icon.addPixmap(pOne, QIcon.Normal, QIcon.On) + icon.addPixmap(pTwo, QIcon.Normal, QIcon.Off) + return icon return QIcon() - def getPixmap(self, iconKey, iconSize): + def getPixmap(self, name: str, size: tuple[int, int]) -> QPixmap: """Return an icon from the icon buffer as a QPixmap. If it doesn't exist, return an empty QPixmap. """ - qIcon = self.getIcon(iconKey) - return qIcon.pixmap(iconSize[0], iconSize[1], QIcon.Normal) + return self.getIcon(name).pixmap(size[0], size[1], QIcon.Normal) - def getItemIcon(self, tType, tClass, tLayout, hLevel="H0"): + def getItemIcon(self, tType: nwItemType, tClass: nwItemClass, + tLayout: nwItemLayout, hLevel: str = "H0") -> QIcon: """Get the correct icon for a project item based on type, class and header level """ @@ -670,17 +667,16 @@ class GuiIcons: return self.getIcon(iconName) - def getHeaderDecoration(self, hLevel): - """Get the decoration for a specific header level. - """ + def getHeaderDecoration(self, hLevel: int) -> QPixmap: + """Get the decoration for a specific header level.""" if not self._headerDec: iPx = self.mainTheme.baseIconSize self._headerDec = [ - self.loadDecoration("deco_doc_h0", pxH=iPx), - self.loadDecoration("deco_doc_h1", pxH=iPx), - self.loadDecoration("deco_doc_h2", pxH=iPx), - self.loadDecoration("deco_doc_h3", pxH=iPx), - self.loadDecoration("deco_doc_h4", pxH=iPx), + self.loadDecoration("deco_doc_h0", h=iPx), + self.loadDecoration("deco_doc_h1", h=iPx), + self.loadDecoration("deco_doc_h2", h=iPx), + self.loadDecoration("deco_doc_h3", h=iPx), + self.loadDecoration("deco_doc_h4", h=iPx), ] return self._headerDec[minmax(hLevel, 0, 4)] @@ -688,27 +684,27 @@ class GuiIcons: # Internal Functions ## - def _loadIcon(self, iconKey): + def _loadIcon(self, name: str) -> QIcon: """Load an icon from the assets themes folder. Is guaranteed to return a QIcon. """ - if iconKey not in self.ICON_KEYS: - logger.error("Requested unknown icon name '%s'", iconKey) + if name not in self.ICON_KEYS: + logger.error("Requested unknown icon name '%s'", name) return QIcon() # If we just want the app icons, return right away - if iconKey == "novelwriter": + if name == "novelwriter": return QIcon(str(self._iconPath / "novelwriter.svg")) - elif iconKey == "proj_nwx": + elif name == "proj_nwx": return QIcon(str(self._iconPath / "x-novelwriter-project.svg")) # Otherwise, we load from the theme folder - if iconKey in self._themeMap: - logger.debug("Loading: %s", self._themeMap[iconKey].name) - return QIcon(str(self._themeMap[iconKey])) + if name in self._themeMap: + logger.debug("Loading: %s", self._themeMap[name].name) + return QIcon(str(self._themeMap[name])) # If we didn't find one, give up and return an empty icon - logger.warning("Did not load an icon for '%s'", iconKey) + logger.warning("Did not load an icon for '%s'", name) return QIcon() @@ -719,9 +715,8 @@ class GuiIcons: # Module Functions # =============================================================================================== # -def _loadInternalName(confParser, confFile): - """Open a conf file and read the 'name' setting. - """ +def _loadInternalName(confParser: NWConfigParser, confFile: str | Path) -> str: + """Open a conf file and read the 'name' setting.""" try: with open(confFile, mode="r", encoding="utf-8") as inFile: confParser.read_file(inFile) diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index f476ca94..cfb16ab1 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -322,17 +322,17 @@ def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, tstPaths): assert qPix.isNull() is True # Test image sizes - qPix = iconCache.loadDecoration("wiz-back", pxW=100, pxH=None) + qPix = iconCache.loadDecoration("wiz-back", w=100, h=None) assert qPix.isNull() is False assert qPix.width() == 100 assert qPix.height() > 100 - qPix = iconCache.loadDecoration("wiz-back", pxW=None, pxH=100) + qPix = iconCache.loadDecoration("wiz-back", w=None, h=100) assert qPix.isNull() is False assert qPix.width() < 100 assert qPix.height() == 100 - qPix = iconCache.loadDecoration("wiz-back", pxW=100, pxH=100) + qPix = iconCache.loadDecoration("wiz-back", w=100, h=100) assert qPix.isNull() is False assert qPix.width() == 100 assert qPix.height() == 100