From 0eb4319fc110263b9537aa625c59d372a189deff Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 14 May 2020 18:38:41 +0200
Subject: [PATCH] Finished the code for icon themes
---
nw/assets/icons/fallback/status_lang-dark.svg | 57 +++++++++++++++
nw/assets/icons/fallback/status_lang.svg | 56 +++++++++++++++
nw/assets/icons/fallback/status_time-dark.svg | 62 ++++++++++++++++
nw/assets/icons/fallback/status_time.svg | 61 ++++++++++++++++
.../typicons_colour_dark/input-checked.svg | 70 +++++++++----------
nw/config.py | 6 +-
nw/gui/dialogs/configeditor.py | 32 +++++++--
nw/gui/dialogs/itemeditor.py | 3 +-
nw/gui/dialogs/projecteditor.py | 22 +++---
nw/gui/icons.py | 52 ++++++++++++--
nw/gui/theme.py | 18 +++--
tests/reference/novelwriter.conf | 1 +
12 files changed, 376 insertions(+), 64 deletions(-)
create mode 100644 nw/assets/icons/fallback/status_lang-dark.svg
create mode 100644 nw/assets/icons/fallback/status_lang.svg
create mode 100644 nw/assets/icons/fallback/status_time-dark.svg
create mode 100644 nw/assets/icons/fallback/status_time.svg
diff --git a/nw/assets/icons/fallback/status_lang-dark.svg b/nw/assets/icons/fallback/status_lang-dark.svg
new file mode 100644
index 00000000..8966c4c9
--- /dev/null
+++ b/nw/assets/icons/fallback/status_lang-dark.svg
@@ -0,0 +1,57 @@
+
+
diff --git a/nw/assets/icons/fallback/status_lang.svg b/nw/assets/icons/fallback/status_lang.svg
new file mode 100644
index 00000000..255ac776
--- /dev/null
+++ b/nw/assets/icons/fallback/status_lang.svg
@@ -0,0 +1,56 @@
+
+
diff --git a/nw/assets/icons/fallback/status_time-dark.svg b/nw/assets/icons/fallback/status_time-dark.svg
new file mode 100644
index 00000000..7d489bae
--- /dev/null
+++ b/nw/assets/icons/fallback/status_time-dark.svg
@@ -0,0 +1,62 @@
+
+
diff --git a/nw/assets/icons/fallback/status_time.svg b/nw/assets/icons/fallback/status_time.svg
new file mode 100644
index 00000000..129d58cf
--- /dev/null
+++ b/nw/assets/icons/fallback/status_time.svg
@@ -0,0 +1,61 @@
+
+
diff --git a/nw/assets/icons/typicons_colour_dark/input-checked.svg b/nw/assets/icons/typicons_colour_dark/input-checked.svg
index e4b22265..a4b66a4d 100644
--- a/nw/assets/icons/typicons_colour_dark/input-checked.svg
+++ b/nw/assets/icons/typicons_colour_dark/input-checked.svg
@@ -7,13 +7,13 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
- sodipodi:docname="input-checked.svg"
- id="svg11367"
- viewBox="0 0 24 24"
- height="24"
+ version="1.2"
width="24"
- version="1.2">
+ height="24"
+ viewBox="0 0 24 24"
+ id="svg11367"
+ sodipodi:docname="input-checked.svg"
+ inkscape:version="1.0rc1 (09960d6f05, 2020-04-09)">
@@ -28,38 +28,38 @@
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="2560"
+ inkscape:window-height="1344"
+ id="namedview11369"
+ showgrid="false"
+ inkscape:zoom="38.125"
+ inkscape:cx="7.2131148"
+ inkscape:cy="12"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg11367" />
-
+
+ style="display:inline;fill:#aeaeae;fill-opacity:1;stroke-width:1.28570998"
+ inkscape:connector-curvature="0"
+ id="path16196"
+ d="M 17.142857,21 H 6.8571429 C 4.7305714,21 3,19.269429 3,17.142857 V 6.8571429 C 3,4.7305714 4.7305714,3 6.8571429,3 h 6.4285711 c 0.711,0 1.285715,0.576 1.285715,1.2857143 0,0.7097143 -0.574715,1.2857143 -1.285715,1.2857143 H 6.8571429 c -0.7097143,0 -1.2857143,0.5772857 -1.2857143,1.2857143 V 17.142857 c 0,0.708429 0.576,1.285714 1.2857143,1.285714 H 17.142857 c 0.709714,0 1.285714,-0.577285 1.285714,-1.285714 V 13.285714 C 18.428571,12.576 19.003286,12 19.714286,12 20.425286,12 21,12.576 21,13.285714 v 3.857143 C 21,19.269429 19.269429,21 17.142857,21 Z" />
diff --git a/nw/config.py b/nw/config.py
index 04685fb6..985f4b8d 100644
--- a/nw/config.py
+++ b/nw/config.py
@@ -83,7 +83,7 @@ class Config:
## General
self.guiTheme = "default"
self.guiSyntax = "default_light"
- self.guiIcons = "typicons_colour_dark"
+ self.guiIcons = "default"
self.guiDark = False
## Sizes
@@ -304,6 +304,9 @@ class Config:
self.guiSyntax = self._parseLine(
cnfParse, cnfSec, "syntax", self.CNF_STR, self.guiSyntax
)
+ self.guiIcons = self._parseLine(
+ cnfParse, cnfSec, "icons", self.CNF_STR, self.guiIcons
+ )
self.guiDark = self._parseLine(
cnfParse, cnfSec, "guidark", self.CNF_BOOL, self.guiDark
)
@@ -452,6 +455,7 @@ class Config:
cnfParse.set(cnfSec,"timestamp", formatTimeStamp(time()))
cnfParse.set(cnfSec,"theme", str(self.guiTheme))
cnfParse.set(cnfSec,"syntax", str(self.guiSyntax))
+ cnfParse.set(cnfSec,"icons", str(self.guiIcons))
cnfParse.set(cnfSec,"guidark", str(self.guiDark))
## Sizes
diff --git a/nw/gui/dialogs/configeditor.py b/nw/gui/dialogs/configeditor.py
index cb60a40e..e03d7bad 100644
--- a/nw/gui/dialogs/configeditor.py
+++ b/nw/gui/dialogs/configeditor.py
@@ -58,7 +58,11 @@ class GuiConfigEditor(QDialog):
self.innerBox = QVBoxLayout()
self.setWindowTitle("Preferences")
- self.guiDeco = self.theParent.theTheme.loadDecoration("settings",(64,64))
+ self.guiDeco = self.theParent.theTheme.loadDecoration("settings", (64,64))
+ self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop)
+ self.outerBox.addLayout(self.innerBox)
+ self.outerBox.setSpacing(16)
+ self.setLayout(self.outerBox)
self.tabGeneral = GuiConfigEditGeneralTab(self.theParent)
self.tabLayout = GuiConfigEditLayoutTab(self.theParent)
@@ -72,10 +76,6 @@ class GuiConfigEditor(QDialog):
self.tabWidget.addTab(self.tabEditing, "Editing")
self.tabWidget.addTab(self.tabAutoRep, "Auto-Replace")
- self.setLayout(self.outerBox)
- self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop)
- self.outerBox.addLayout(self.innerBox)
-
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose)
@@ -169,6 +169,22 @@ class GuiConfigEditGeneralTab(QWidget):
"Changing this requires restarting %s." % nw.__package__
)
+ ## Select Icon Theme
+ self.selectIcons = QComboBox()
+ self.selectIcons.setMinimumWidth(200)
+ self.theIcons = self.theTheme.theIcons.listThemes()
+ for iconDir, iconName in self.theIcons:
+ self.selectIcons.addItem(iconName, iconDir)
+ iconIdx = self.selectIcons.findData(self.mainConf.guiIcons)
+ if iconIdx != -1:
+ self.selectIcons.setCurrentIndex(iconIdx)
+
+ self.mainForm.addRow(
+ "Main icon theme",
+ self.selectIcons,
+ "Changing this requires restarting %s." % nw.__package__
+ )
+
## Syntax Highlighting
self.selectSyntax = QComboBox()
self.selectSyntax.setMinimumWidth(200)
@@ -181,7 +197,8 @@ class GuiConfigEditGeneralTab(QWidget):
self.mainForm.addRow(
"Syntax highlight theme",
- self.selectSyntax
+ self.selectSyntax,
+ ""
)
## Dark Icons
@@ -275,6 +292,7 @@ class GuiConfigEditGeneralTab(QWidget):
guiTheme = self.selectTheme.currentData()
guiSyntax = self.selectSyntax.currentData()
+ guiIcons = self.selectIcons.currentData()
guiDark = self.preferDarkIcons.isChecked()
showFullPath = self.showFullPath.isChecked()
autoSaveDoc = self.autoSaveDoc.value()
@@ -285,9 +303,11 @@ class GuiConfigEditGeneralTab(QWidget):
# Check if restart is needed
needsRestart |= self.mainConf.guiTheme != guiTheme
+ needsRestart |= self.mainConf.guiIcons != guiIcons
self.mainConf.guiTheme = guiTheme
self.mainConf.guiSyntax = guiSyntax
+ self.mainConf.guiIcons = guiIcons
self.mainConf.guiDark = guiDark
self.mainConf.showFullPath = showFullPath
self.mainConf.autoSaveDoc = autoSaveDoc
diff --git a/nw/gui/dialogs/itemeditor.py b/nw/gui/dialogs/itemeditor.py
index 0b24e9d6..38af48d6 100644
--- a/nw/gui/dialogs/itemeditor.py
+++ b/nw/gui/dialogs/itemeditor.py
@@ -60,11 +60,10 @@ class GuiItemEditor(QDialog):
nwLabels.CLASS_ICON[self.theItem.itemClass], (64,64)
)
)
-
- self.setLayout(self.outerBox)
self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop)
self.outerBox.addLayout(self.innerBox)
self.outerBox.setSpacing(16)
+ self.setLayout(self.outerBox)
self.mainGroup = QGroupBox("Item Settings")
self.mainForm = QFormLayout()
diff --git a/nw/gui/dialogs/projecteditor.py b/nw/gui/dialogs/projecteditor.py
index afa5dbe5..fd256c3a 100644
--- a/nw/gui/dialogs/projecteditor.py
+++ b/nw/gui/dialogs/projecteditor.py
@@ -52,12 +52,18 @@ class GuiProjectEditor(QDialog):
self.theParent = theParent
self.theProject = theProject
- self.outerBox = QHBoxLayout()
- self.innerBox = QVBoxLayout()
- self.setWindowTitle("Project Settings")
- self.setLayout(self.outerBox)
+ self.outerBox = QHBoxLayout()
+ self.innerBox = QVBoxLayout()
- self.guiDeco = self.theParent.theTheme.loadDecoration("settings",(64,64))
+ self.setWindowTitle("Project Settings")
+ self.guiDeco = QLabel()
+ self.guiDeco.setPixmap(
+ self.theParent.theTheme.getPixmap("cls_novel", (64,64))
+ )
+ self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop)
+ self.outerBox.addLayout(self.innerBox)
+ self.outerBox.setSpacing(16)
+ self.setLayout(self.outerBox)
self.theProject.countStatus()
self.tabMain = GuiProjectEditMain(self.theParent, self.theProject)
@@ -71,9 +77,6 @@ class GuiProjectEditor(QDialog):
self.tabWidget.addTab(self.tabImport, "Importance")
self.tabWidget.addTab(self.tabReplace,"Auto-Replace")
- self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop)
- self.outerBox.addLayout(self.innerBox)
-
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doSave)
self.buttonBox.rejected.connect(self._doClose)
@@ -363,6 +366,9 @@ class GuiProjectEditReplace(QWidget):
self.saveButton = QPushButton(self.theTheme.getIcon("done"),"")
self.addButton = QPushButton(self.theTheme.getIcon("add"),"")
self.delButton = QPushButton(self.theTheme.getIcon("remove"),"")
+ self.saveButton.setToolTip("Save entry")
+ self.addButton.setToolTip("Add new entry")
+ self.delButton.setToolTip("Delete selected entry")
self.editKey.setEnabled(False)
self.editKey.setMaxLength(40)
diff --git a/nw/gui/icons.py b/nw/gui/icons.py
index c45a9aaf..35bc8122 100644
--- a/nw/gui/icons.py
+++ b/nw/gui/icons.py
@@ -29,7 +29,7 @@ import logging
import configparser
import nw
-from os import path
+from os import path, listdir
from PyQt5.QtCore import QSize
from PyQt5.QtSvg import QSvgWidget
@@ -90,8 +90,11 @@ class GuiIcons:
self.theParent = theParent
# Storage
- self.qIcons = {}
- self.themeMap = {}
+ self.qIcons = {}
+ self.themeMap = {}
+ self.themeList = []
+ self.fbackName = "fallback"
+ self.confName = "icons.conf"
# Icon Theme Path
self.iconPath = None
@@ -121,7 +124,7 @@ class GuiIcons:
if path.isdir(checkPath):
logger.debug("Loading icon theme '%s'" % self.mainConf.guiIcons)
self.iconPath = checkPath
- self.confFile = path.join(checkPath, "icons.conf")
+ self.confFile = path.join(checkPath, self.confName)
else:
return False
@@ -170,7 +173,6 @@ class GuiIcons:
"""Load graphical decoration element based on the decoration
map. This function always returns a QSwgWidget.
"""
-
if decoKey not in self.DECO_MAP:
logger.error("Decoration with name '%s' does not exist" % decoKey)
return QSvgWidget()
@@ -209,6 +211,37 @@ class GuiIcons:
qIcon = self.getIcon(iconKey)
return qIcon.pixmap(iconSize[0], iconSize[1], QIcon.Normal)
+ def listThemes(self):
+ """Scan the icons themes folder and list all themes.
+ """
+ if self.themeList:
+ return self.themeList
+
+ confParser = configparser.ConfigParser()
+ for themeDir in listdir(self.mainConf.iconPath):
+ themePath = path.join(self.mainConf.iconPath, themeDir)
+ if not path.isdir(themePath) or themeDir == self.fbackName:
+ continue
+ themeConf = path.join(themePath, self.confName)
+ logger.verbose("Checking icon theme config for '%s'" % themeDir)
+ try:
+ confParser.read_file(open(themeConf, mode="r", encoding="utf8"))
+ except Exception as e:
+ self.theParent.makeAlert(["Could not load theme config file.",str(e)],nwAlert.ERROR)
+ continue
+ themeName = ""
+ if confParser.has_section("Main"):
+ if confParser.has_option("Main", "name"):
+ themeName = confParser.get("Main", "name")
+ logger.verbose("Theme name is '%s'" % themeName)
+ if themeName != "":
+ self.themeList.append((themeDir, themeName))
+
+ self.themeList = sorted(self.themeList, key=lambda x: x[1])
+ self.themeList.insert(0, ("default", "System Icons"))
+
+ return self.themeList
+
##
# Internal Functions
##
@@ -238,6 +271,15 @@ class GuiIcons:
logger.verbose("Loading icon '%s' from system theme" % iconKey)
return QIcon().fromTheme(self.ICON_MAP[iconKey][1])
+ # Finally. we check if we have a fallback icon
+ if self.mainConf.guiDark:
+ fbackIcon = path.join(self.mainConf.iconPath, self.fbackName, "%s-dark.svg" % iconKey)
+ else:
+ fbackIcon = path.join(self.mainConf.iconPath, self.fbackName, "%s.svg" % iconKey)
+ if path.isfile(fbackIcon):
+ logger.verbose("Loading icon '%s' from fallback theme" % iconKey)
+ return QIcon(fbackIcon)
+
# Give up and return an empty icon
logger.warning("Did not load an icon for '%s'" % iconKey)
diff --git a/nw/gui/theme.py b/nw/gui/theme.py
index b2f11357..31e862e0 100644
--- a/nw/gui/theme.py
+++ b/nw/gui/theme.py
@@ -260,8 +260,9 @@ class GuiTheme:
return True
def listThemes(self):
-
- if len(self.themeList) > 0:
+ """Scan the gui themes folder and list all themes.
+ """
+ if self.themeList:
return self.themeList
confParser = configparser.ConfigParser()
@@ -275,8 +276,9 @@ class GuiTheme:
continue
themeName = ""
if confParser.has_section("Main"):
- themeName = confParser.get("Main","name")
- logger.verbose("Theme name is '%s'" % themeName)
+ if confParser.has_option("Main", "name"):
+ themeName = confParser.get("Main", "name")
+ logger.verbose("Theme name is '%s'" % themeName)
if themeName != "":
self.themeList.append((themeDir, themeName))
@@ -285,8 +287,9 @@ class GuiTheme:
return self.themeList
def listSyntax(self):
-
- if len(self.syntaxList) > 0:
+ """Scan the syntax themes folder and list all themes.
+ """
+ if self.syntaxList:
return self.syntaxList
confParser = configparser.ConfigParser()
@@ -303,7 +306,8 @@ class GuiTheme:
return []
syntaxName = ""
if confParser.has_section("Main"):
- syntaxName = confParser.get("Main","name")
+ if confParser.has_option("Main", "name"):
+ syntaxName = confParser.get("Main", "name")
if len(syntaxFile) > 5 and syntaxName != "":
self.syntaxList.append((syntaxFile[:-5], syntaxName))
logger.verbose("Syntax name is '%s'" % syntaxName)
diff --git a/tests/reference/novelwriter.conf b/tests/reference/novelwriter.conf
index 7102783d..13c3637a 100644
--- a/tests/reference/novelwriter.conf
+++ b/tests/reference/novelwriter.conf
@@ -2,6 +2,7 @@
timestamp = 2020-05-01 21:28:25
theme = default
syntax = default_light
+icons = default
guidark = False
[Sizes]