diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index 61f1fbc9..b8196cc2 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Main Window Elements +from nw.gui.icons import GuiIcons from nw.gui.mainmenu import GuiMainMenu from nw.gui.statusbar import GuiMainStatus from nw.gui.theme import GuiTheme @@ -27,6 +28,7 @@ from nw.gui.tools.dochighlight import GuiDocHighlighter from nw.gui.tools.wordcounter import WordCounter __all__ = [ + "GuiIcons", "GuiMainMenu", "GuiMainStatus", "GuiTheme", diff --git a/nw/gui/dialogs/configeditor.py b/nw/gui/dialogs/configeditor.py index bc809282..685e7e94 100644 --- a/nw/gui/dialogs/configeditor.py +++ b/nw/gui/dialogs/configeditor.py @@ -15,8 +15,7 @@ import nw from os import path -from PyQt5.QtCore import Qt, QSize -from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtCore import Qt from PyQt5.QtGui import ( QIcon, QPixmap, QColor, QBrush, QStandardItemModel, QFont ) @@ -46,10 +45,7 @@ class GuiConfigEditor(QDialog): self.innerBox = QVBoxLayout() self.setWindowTitle("Preferences") - - self.gradPath = path.abspath(path.join(self.mainConf.graphPath,"gear.svg")) - self.svgGradient = QSvgWidget(path.join(self.gradPath)) - self.svgGradient.setFixedSize(QSize(64,64)) + self.guiDeco = self.theParent.theTheme.loadDecoration("settings",(64,64)) self.theProject.countStatus() self.tabMain = GuiConfigEditGeneral(self.theParent) @@ -60,7 +56,7 @@ class GuiConfigEditor(QDialog): self.tabWidget.addTab(self.tabEditor, "Editor") self.setLayout(self.outerBox) - self.outerBox.addWidget(self.svgGradient, 0, Qt.AlignTop) + self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop) self.outerBox.addLayout(self.innerBox) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) diff --git a/nw/gui/dialogs/export.py b/nw/gui/dialogs/export.py index c3053ff0..b3944f74 100644 --- a/nw/gui/dialogs/export.py +++ b/nw/gui/dialogs/export.py @@ -16,8 +16,7 @@ import nw from os import path -from PyQt5.QtCore import Qt, QSize -from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QGridLayout, QGroupBox, QCheckBox, QLabel, QComboBox, QLineEdit, QPushButton, @@ -50,9 +49,7 @@ class GuiExport(QDialog): self.setWindowTitle("Export Project") self.setLayout(self.outerBox) - self.gradPath = path.abspath(path.join(self.mainConf.graphPath,"export.svg")) - self.svgGradient = QSvgWidget(self.gradPath) - self.svgGradient.setFixedSize(QSize(64,64)) + self.guiDeco = self.theParent.theTheme.loadDecoration("export",(64,64)) self.theProject.countStatus() self.tabMain = GuiExportMain(self.theParent, self.theProject, self.optState) @@ -62,7 +59,7 @@ class GuiExport(QDialog): self.tabWidget.addTab(self.tabMain, "Settings") self.tabWidget.addTab(self.tabPandoc, "Pandoc") - self.outerBox.addWidget(self.svgGradient, 0, Qt.AlignTop) + self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop) self.outerBox.addLayout(self.innerBox) self.doExportForm = QGridLayout() diff --git a/nw/gui/dialogs/itemeditor.py b/nw/gui/dialogs/itemeditor.py index f099a32d..af6fa609 100644 --- a/nw/gui/dialogs/itemeditor.py +++ b/nw/gui/dialogs/itemeditor.py @@ -15,8 +15,7 @@ import nw from os import path -from PyQt5.QtCore import Qt, QSize -from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( QDialog, QHBoxLayout, QVBoxLayout, QGroupBox, QFormLayout, QLineEdit, QPushButton, QComboBox @@ -42,13 +41,10 @@ class GuiItemEditor(QDialog): self.innerBox = QVBoxLayout() self.setWindowTitle("Item Settings") - - self.gradPath = path.abspath(path.join(self.mainConf.graphPath,"gear.svg")) - self.svgGradient = QSvgWidget(self.gradPath) - self.svgGradient.setFixedSize(QSize(64,64)) + self.guiDeco = self.theParent.theTheme.loadDecoration("settings",(64,64)) self.setLayout(self.outerBox) - self.outerBox.addWidget(self.svgGradient, 0, Qt.AlignTop) + self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop) self.outerBox.addLayout(self.innerBox) self.mainGroup = QGroupBox("Item Settings") diff --git a/nw/gui/dialogs/projecteditor.py b/nw/gui/dialogs/projecteditor.py index 1db212f6..0c6d8fae 100644 --- a/nw/gui/dialogs/projecteditor.py +++ b/nw/gui/dialogs/projecteditor.py @@ -15,9 +15,8 @@ import nw from os import path -from PyQt5.QtCore import Qt, QSize -from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush, QStandardItemModel -from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush from PyQt5.QtWidgets import ( QDialog, QHBoxLayout, QVBoxLayout, QFormLayout, QLineEdit, QPlainTextEdit, QLabel, QWidget, QTabWidget, QDialogButtonBox, QListWidget, @@ -45,9 +44,7 @@ class GuiProjectEditor(QDialog): self.setWindowTitle("Project Settings") self.setLayout(self.outerBox) - self.gradPath = path.abspath(path.join(self.mainConf.graphPath,"gear.svg")) - self.svgGradient = QSvgWidget(self.gradPath) - self.svgGradient.setFixedSize(QSize(64,64)) + self.guiDeco = self.theParent.theTheme.loadDecoration("settings",(64,64)) self.theProject.countStatus() self.tabMain = GuiProjectEditMain(self.theParent, self.theProject) @@ -61,7 +58,7 @@ class GuiProjectEditor(QDialog): self.tabWidget.addTab(self.tabImport, "Importance") self.tabWidget.addTab(self.tabReplace,"Auto-Replace") - self.outerBox.addWidget(self.svgGradient, 0, Qt.AlignTop) + self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop) self.outerBox.addLayout(self.innerBox) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) diff --git a/nw/gui/icons.py b/nw/gui/icons.py new file mode 100644 index 00000000..c44f84e5 --- /dev/null +++ b/nw/gui/icons.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- +"""novelWriter Icons Class + + novelWriter – Icons Class +=========================== + This class manages the GUI icons + + File History: + Created: 2019-11-08 [0.4.0] + +""" + +import logging +import nw + +from os import path + +from PyQt5.QtCore import QSize +from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtGui import QIcon + +logger = logging.getLogger(__name__) + +class GuiIcons: + + # Icon keys should either be a .svg or .png file under the gui + # theme folder, or have a fallback that is either compatible with + # QIcon.fromTheme, as specified here: + # https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html + # or, if there is no fallback, the variable should be None. + ICON_MAP = { + "root" : "drive-harddisk", + "folder" : "folder", + "document" : "x-office-document", + "trash" : "user-trash", + "orphan" : "dialog-warning", + "save" : "document-save", + "add" : "list-add", + "remove" : "list-remove", + "close" : "edit-delete", + "search" : "edit-find", + "replace" : "edit-find-replace", + "time" : None, + } + + DECO_MAP = { + "export" : "export.svg", + "settings" : "settings.svg", + } + + def __init__(self, theParent): + + self.mainConf = nw.CONFIG + self.theParent = theParent + + # Storage + self.qIcons = {} + + # Look for files in + self.priPath = "" # The gui theme's icon folder + self.secPath = "" # The main assets icon folder + self.prefDark = False # Load dark icons, if available + + return + + def initIcons(self, priPath): + + self.priPath = priPath + self.secPath = self.mainConf.iconPath + self.prefDark = self.mainConf.guiDark + + for iconKey in self.ICON_MAP.keys(): + self.qIcons[iconKey] = self._loadIcon(iconKey) + + return + + def loadDecoration(self, decoKey, decoSize=None): + + if decoKey not in self.DECO_MAP: + logger.error("Decoration with name '%s' does not exist" % decoKey) + return QSvgWidget() + + svgPath = path.join(self.mainConf.graphPath, self.DECO_MAP[decoKey]) + if not path.isfile(svgPath): + logger.error("Decoration file '%s' not in assets folder" % self.DECO_MAP[decoKey]) + return QSvgWidget() + + svgDeco = QSvgWidget(svgPath) + if decoSize is not None: + svgDeco.setFixedSize(QSize(decoSize[0],decoSize[1])) + + return svgDeco + + def getIcon(self, iconKey, iconSize=None): + if iconKey in self.qIcons: + return self.qIcons[iconKey] + return QIcon() + + ## + # Internal Functions + ## + + def _loadIcon(self, iconKey): + + if iconKey not in self.ICON_MAP: + logger.error("Icon with name '%s' does not exist" % iconKey) + return QIcon() + + if self.prefDark: + iconSuffix = "dark" + else: + iconSuffix = "light" + + iconNames = [] + iconNames.append("%s-%s.svg" % (iconKey, iconSuffix)) + iconNames.append("%s-%s.png" % (iconKey, iconSuffix)) + iconNames.append("%s.svg" % iconKey) + iconNames.append("%s.png" % iconKey) + + # Check theme folder + loadFrom = None + for iconName in iconNames: + checkPath = path.join(self.priPath, iconName) + if path.isfile(checkPath): + loadFrom = checkPath + logger.verbose("Loading icon '%s' from theme" % iconName) + break + if loadFrom is not None: + return QIcon(loadFrom) + + # Check assets folder + for iconName in iconNames: + checkPath = path.join(self.secPath, iconName) + if path.isfile(checkPath): + loadFrom = checkPath + logger.verbose("Loading icon '%s' from assets" % iconName) + break + if loadFrom is not None: + return QIcon(loadFrom) + + # If we're still here, try to set from system theme + if self.ICON_MAP[iconKey] is not None: + logger.verbose("Loading icon '%s' from system theme" % iconKey) + return QIcon().fromTheme(self.ICON_MAP[iconKey]) + + # Give up and return an empty icomn + logger.warning("Did not load an icon for '%s'" % iconKey) + + return QIcon() + +# END Class GuiIcons diff --git a/nw/gui/theme.py b/nw/gui/theme.py index 907a4f1d..681fc6ff 100644 --- a/nw/gui/theme.py +++ b/nw/gui/theme.py @@ -20,34 +20,17 @@ from PyQt5.QtWidgets import qApp from PyQt5.QtGui import QPalette, QColor, QIcon from nw.constants import nwAlert +from nw.gui.icons import GuiIcons logger = logging.getLogger(__name__) class GuiTheme: - # Icons should either have a valid icon name by using Qt internal - # QIcon.fromTheme, as specified here: - # https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html - # If there is no fallback, the variable should be None. - ICON_MAP = { - "root" : "drive-harddisk", - "folder" : "folder", - "document" : "x-office-document", - "trash" : "user-trash", - "orphan" : "dialog-warning", - "save" : "document-save", - "add" : "list-add", - "remove" : "list-remove", - "close" : "edit-delete", - "search" : "edit-find", - "replace" : "edit-find-replace", - "time" : None, - } - def __init__(self, theParent): self.mainConf = nw.CONFIG self.theParent = theParent + self.theIcons = GuiIcons(self.theParent) self.guiPalette = QPalette() self.guiPath = "gui" self.iconPath = "icons" @@ -96,9 +79,6 @@ class GuiTheme: self.colTagErr = [ 0, 0, 0] self.colRepTag = [ 0, 0, 0] - ## Icons - self.themeIcons = {} - # Changeable Settings self.guiTheme = None self.guiSyntax = None @@ -110,6 +90,9 @@ class GuiTheme: self.updateTheme() + self.getIcon = self.theIcons.getIcon + self.loadDecoration = self.theIcons.loadDecoration + return ## @@ -126,8 +109,7 @@ class GuiTheme: self.confFile = path.join(self.themePath,self.confName) self.cssFile = path.join(self.themePath,self.cssName) - for iconName in self.ICON_MAP: - self.themeIcons[iconName] = QIcon.fromTheme(self.ICON_MAP[iconName]) + self.theIcons.initIcons(path.join(self.themePath,self.iconPath)) self.loadTheme() self.loadSyntax() @@ -149,21 +131,6 @@ class GuiTheme: logger.error("Could not load theme css file") return False - # Icon Files - iconsDir = path.join(self.themePath,self.iconPath) - if path.isdir(iconsDir): - for iconName in self.ICON_MAP: - iconFile = path.join(iconsDir,iconName+".svg") - if path.isfile(iconFile): - self.themeIcons[iconName] = QIcon(iconFile) - logger.verbose("Loaded theme icon '%s'" % (iconName+".svg")) - continue - iconFile = path.join(iconsDir,iconName+".png") - if path.isfile(iconFile): - self.themeIcons[iconName] = QIcon(iconFile) - logger.verbose("Loaded theme icon '%s'" % (iconName+".png")) - continue - # Config File confParser = configparser.ConfigParser() try: @@ -307,11 +274,6 @@ class GuiTheme: return self.syntaxList - def getIcon(self, iconName, iconSize=None): - if iconName in self.themeIcons: - return self.themeIcons[iconName] - return QIcon() - ## # Internal Functions ##