From 682804f7035efb05cef14cdd1240183c5dec0f48 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sun, 1 Jun 2025 15:00:44 +0200
Subject: [PATCH 01/96] Bump main branch to 2.8 Alpha 0
---
novelwriter/__init__.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py
index 7e526b72..4905f4b3 100644
--- a/novelwriter/__init__.py
+++ b/novelwriter/__init__.py
@@ -49,8 +49,8 @@ __license__ = "GPLv3"
__author__ = "Veronica Berglyd Olsen"
__maintainer__ = "Veronica Berglyd Olsen"
__email__ = "code@vkbo.net"
-__version__ = "2.7"
-__hexversion__ = "0x020700f0"
+__version__ = "2.8 Alpha 0"
+__hexversion__ = "0x020800a0"
__date__ = "2025-06-01"
__status__ = "Stable"
__domain__ = "novelwriter.io"
From d12113b94c2248d5c83baea780d21996a5ed65e6 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sun, 1 Jun 2025 18:41:13 +0200
Subject: [PATCH 02/96] Limit main GUI size to available screen size of the
current screen (#2354)
---
novelwriter/guimain.py | 13 +++++++++++--
novelwriter/shared.py | 9 +++++++--
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index a12383fe..8a895bfe 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -38,7 +38,7 @@ from PyQt6.QtWidgets import (
)
from novelwriter import CONFIG, SHARED, __hexversion__, __version__
-from novelwriter.common import formatFileFilter, formatVersion, hexToInt
+from novelwriter.common import formatFileFilter, formatVersion, hexToInt, minmax
from novelwriter.constants import nwConst
from novelwriter.dialogs.about import GuiAbout
from novelwriter.dialogs.preferences import GuiPreferences
@@ -103,7 +103,7 @@ class GuiMain(QMainWindow):
SHARED.initSharedData(self)
# Prepare Main Window
- self.resize(*CONFIG.mainWinSize)
+ self._setWindowSize(CONFIG.mainWinSize)
self._updateWindowTitle()
nwIcon = CONFIG.assetPath("icons") / "novelwriter.svg"
@@ -1326,6 +1326,15 @@ class GuiMain(QMainWindow):
# Internal Functions
##
+ def _setWindowSize(self, size: list[int]) -> None:
+ """Set the main window size."""
+ if len(size) == 2 and (screen := SHARED.mainScreen):
+ availSize = screen.availableSize()
+ width = minmax(size[0], 900, availSize.width())
+ height = minmax(size[1], 500, availSize.height())
+ self.resize(width, height)
+ return
+
def _updateWindowTitle(self, projName: str | None = None) -> None:
"""Set the window title and add the project's name."""
self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName])))
diff --git a/novelwriter/shared.py b/novelwriter/shared.py
index 04add94c..228b9678 100644
--- a/novelwriter/shared.py
+++ b/novelwriter/shared.py
@@ -32,8 +32,8 @@ from time import time
from typing import TYPE_CHECKING, TypeVar
from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
-from PyQt6.QtGui import QDesktopServices, QFont
-from PyQt6.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
+from PyQt6.QtGui import QDesktopServices, QFont, QScreen
+from PyQt6.QtWidgets import QApplication, QFileDialog, QFontDialog, QMessageBox, QWidget
from novelwriter.common import formatFileFilter
from novelwriter.constants import nwFiles
@@ -150,6 +150,11 @@ class SharedData(QObject):
"""Return the last alert message."""
return self._lastAlert
+ @property
+ def mainScreen(self) -> QScreen | None:
+ """Return the screen of the main window."""
+ return QApplication.screenAt(self.mainGui.rect().center())
+
##
# Setters
##
From 1fafa0dae7c8154efcb41154bfe0772f81913799 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 16:28:06 +0200
Subject: [PATCH 03/96] Remove syntax theme parsing and add light and dark
theme user settings
---
novelwriter/common.py | 11 ++
novelwriter/config.py | 52 ++++----
novelwriter/dialogs/preferences.py | 52 ++++----
novelwriter/enum.py | 7 ++
novelwriter/gui/theme.py | 188 ++++++++++++-----------------
novelwriter/guimain.py | 1 -
6 files changed, 148 insertions(+), 163 deletions(-)
diff --git a/novelwriter/common.py b/novelwriter/common.py
index a4e37f62..6d1e8519 100644
--- a/novelwriter/common.py
+++ b/novelwriter/common.py
@@ -31,6 +31,7 @@ import xml.etree.ElementTree as ET
from configparser import ConfigParser
from datetime import datetime
+from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal, TypeGuard, TypeVar
from urllib.parse import urljoin
@@ -676,6 +677,9 @@ def openExternalPath(path: Path) -> bool:
# Classes
##
+_T_Enum = TypeVar("_T_Enum", bound=Enum)
+
+
class NWConfigParser(ConfigParser):
"""Common: Adapted Config Parser
@@ -736,3 +740,10 @@ class NWConfigParser(ConfigParser):
for i in range(min(len(data), len(result))):
result[i] = checkInt(data[i].strip(), result[i])
return result
+
+ def rdEnum(self, section: str, option: str, default: _T_Enum) -> _T_Enum:
+ """Read enum value."""
+ if self.has_option(section, option):
+ data = self.get(section, option, fallback="")
+ return type(default).__members__.get(data.upper(), default)
+ return default
diff --git a/novelwriter/config.py b/novelwriter/config.py
index ac403fdb..9b8752fc 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -45,6 +45,7 @@ from novelwriter.common import (
formatTimeStamp, processDialogSymbols, simplified
)
from novelwriter.constants import nwFiles, nwHtmlUnicode, nwQuotes, nwUnicode
+from novelwriter.enum import nwTheme
from novelwriter.error import formatException, logException
if TYPE_CHECKING:
@@ -55,8 +56,8 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
-DEF_GUI = "default"
-DEF_SYNTAX = "default_light"
+DEF_GUI_DARK = "default_dark"
+DEF_GUI_LIGHT = "default_light"
DEF_ICONS = "material_rounded_normal"
DEF_TREECOL = "theme"
@@ -69,22 +70,22 @@ class Config:
"_manuals", "_nwLangPath", "_qLocale", "_qtLangPath", "_qtTrans", "_recentPaths",
"_recentProjects", "_splash", "allowOpenDial", "altDialogClose", "altDialogOpen",
"appHandle", "appName", "askBeforeBackup", "askBeforeExit", "autoSaveDoc", "autoSaveProj",
- "autoScroll", "autoScrollPos", "autoSelect", "backupOnClose", "cursorWidth", "dialogLine",
- "dialogStyle", "doJustify", "doReplace", "doReplaceDQuote", "doReplaceDash",
+ "autoScroll", "autoScrollPos", "autoSelect", "backupOnClose", "cursorWidth", "darkTheme",
+ "dialogLine", "dialogStyle", "doJustify", "doReplace", "doReplaceDQuote", "doReplaceDash",
"doReplaceDots", "doReplaceSQuote", "emphLabels", "fmtApostrophe", "fmtDQuoteClose",
"fmtDQuoteOpen", "fmtPadAfter", "fmtPadBefore", "fmtPadThin", "fmtSQuoteClose",
- "fmtSQuoteOpen", "focusWidth", "guiFont", "guiLocale", "guiSyntax", "guiTheme",
- "hasEnchant", "hideFocusFooter", "hideHScroll", "hideVScroll", "highlightEmph", "hostName",
- "iconColDocs", "iconColTree", "iconTheme", "incNotesWCount", "isDebug", "kernelVer",
- "lastNotes", "mainPanePos", "mainWinSize", "memInfo", "narratorBreak", "narratorDialog",
- "nativeFont", "osDarwin", "osLinux", "osType", "osUnknown", "osWindows", "outlinePanePos",
+ "fmtSQuoteOpen", "focusWidth", "guiFont", "guiLocale", "hasEnchant", "hideFocusFooter",
+ "hideHScroll", "hideVScroll", "highlightEmph", "hostName", "iconColDocs", "iconColTree",
+ "iconTheme", "incNotesWCount", "isDebug", "kernelVer", "lastNotes", "lightTheme",
+ "mainPanePos", "mainWinSize", "memInfo", "narratorBreak", "narratorDialog", "nativeFont",
+ "osDarwin", "osLinux", "osType", "osUnknown", "osWindows", "outlinePanePos",
"prefsWinSize", "scrollPastEnd", "searchCase", "searchLoop", "searchMatchCap",
"searchNextFile", "searchProjCase", "searchProjRegEx", "searchProjWord", "searchRegEx",
"searchWord", "showEditToolBar", "showFullPath", "showLineEndings", "showMultiSpaces",
"showSessionTime", "showTabsNSpaces", "showViewerPanel", "spellLanguage", "stopWhenIdle",
- "tabWidth", "textFont", "textMargin", "textWidth", "useCharCount", "userIdleTime",
- "verPyQtString", "verPyQtValue", "verPyString", "verQtString", "verQtValue",
- "viewComments", "viewPanePos", "viewSynopsis", "welcomeWinSize",
+ "tabWidth", "textFont", "textMargin", "textWidth", "themeMode", "useCharCount",
+ "userIdleTime", "verPyQtString", "verPyQtValue", "verPyString", "verQtString",
+ "verQtValue", "viewComments", "viewPanePos", "viewSynopsis", "welcomeWinSize",
)
LANG_NW = 1
@@ -153,14 +154,15 @@ class Config:
# General GUI Settings
self.guiLocale = self._qLocale.name()
- self.guiTheme = DEF_GUI # GUI theme
- self.guiSyntax = DEF_SYNTAX # Syntax theme
- self.guiFont = QFont() # Main GUI font
- self.hideVScroll = False # Hide vertical scroll bars on main widgets
- self.hideHScroll = False # Hide horizontal scroll bars on main widgets
- self.lastNotes = "0x0" # The latest release notes that have been shown
- self.nativeFont = True # Use native font dialog
- self.useCharCount = False # Use character count as primary count
+ self.lightTheme = DEF_GUI_LIGHT # Light GUI theme
+ self.darkTheme = DEF_GUI_DARK # Dark GUI theme
+ self.themeMode = nwTheme.AUTO # Colour theme mode
+ self.guiFont = QFont() # Main GUI font
+ self.hideVScroll = False # Hide vertical scroll bars on main widgets
+ self.hideHScroll = False # Hide horizontal scroll bars on main widgets
+ self.lastNotes = "0x0" # The latest release notes that have been shown
+ self.nativeFont = True # Use native font dialog
+ self.useCharCount = False # Use character count as primary count
# Icons
self.iconTheme = DEF_ICONS # Icons theme
@@ -626,8 +628,9 @@ class Config:
# Main
sec = "Main"
self.setGuiFont(conf.rdStr(sec, "font", ""))
- self.guiTheme = conf.rdStr(sec, "theme", self.guiTheme)
- self.guiSyntax = conf.rdStr(sec, "syntax", self.guiSyntax)
+ self.lightTheme = conf.rdStr(sec, "lighttheme", self.lightTheme)
+ self.darkTheme = conf.rdStr(sec, "darktheme", self.darkTheme)
+ self.themeMode = conf.rdEnum(sec, "thememode", self.themeMode)
self.iconTheme = conf.rdStr(sec, "icons", self.iconTheme)
self.iconColTree = conf.rdStr(sec, "iconcoltree", self.iconColTree)
self.iconColDocs = conf.rdBool(sec, "iconcoldocs", self.iconColDocs)
@@ -751,8 +754,9 @@ class Config:
conf["Main"] = {
"font": self.guiFont.toString(),
- "theme": str(self.guiTheme),
- "syntax": str(self.guiSyntax),
+ "lighttheme": str(self.lightTheme),
+ "darktheme": str(self.darkTheme),
+ "thememode": self.themeMode.name,
"icons": str(self.iconTheme),
"iconcoltree": str(self.iconColTree),
"iconcoldocs": str(self.iconColDocs),
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index 281487b3..9587db6a 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -35,7 +35,7 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import compact, describeFont, processDialogSymbols, uniqueCompact
-from novelwriter.config import DEF_GUI, DEF_ICONS, DEF_SYNTAX, DEF_TREECOL
+from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS, DEF_TREECOL
from novelwriter.constants import nwLabels, nwQuotes, nwUnicode, trConst
from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.extensions.configlayout import NColorLabel, NScrollableForm
@@ -165,14 +165,24 @@ class GuiPreferences(NDialog):
)
# Colour Theme
- self.guiTheme = NComboBox(self)
- self.guiTheme.setMinimumWidth(200)
- for theme, name in SHARED.theme.listThemes():
- self.guiTheme.addItem(name, theme)
- self.guiTheme.setCurrentData(CONFIG.guiTheme, DEF_GUI)
+ self.lightTheme = NComboBox(self)
+ self.lightTheme.setMinimumWidth(200)
+ self.darkTheme = NComboBox(self)
+ self.darkTheme.setMinimumWidth(200)
+ for theme, name, dark in SHARED.theme.listThemes():
+ if dark:
+ self.darkTheme.addItem(name, theme)
+ else:
+ self.lightTheme.addItem(name, theme)
+ self.lightTheme.setCurrentData(CONFIG.lightTheme, DEF_GUI_LIGHT)
+ self.darkTheme.setCurrentData(CONFIG.darkTheme, DEF_GUI_DARK)
self.mainForm.addRow(
- self.tr("Colour theme"), self.guiTheme,
+ self.tr("Light colour theme"), self.lightTheme,
+ self.tr("User interface colour theme."), stretch=(3, 2)
+ )
+ self.mainForm.addRow(
+ self.tr("Dark colour theme"), self.darkTheme,
self.tr("User interface colour theme."), stretch=(3, 2)
)
@@ -242,18 +252,6 @@ class GuiPreferences(NDialog):
self.sidebar.addButton(title, section)
self.mainForm.addGroupLabel(title, section)
- # Document Colour Theme
- self.guiSyntax = NComboBox(self)
- self.guiSyntax.setMinimumWidth(200)
- for syntax, name in SHARED.theme.listSyntax():
- self.guiSyntax.addItem(name, syntax)
- self.guiSyntax.setCurrentData(CONFIG.guiSyntax, DEF_SYNTAX)
-
- self.mainForm.addRow(
- self.tr("Document colour theme"), self.guiSyntax,
- self.tr("Colour theme for the editor and viewer."), stretch=(3, 2)
- )
-
# Document Font Family
self.textFont = QLineEdit(self)
self.textFont.setReadOnly(True)
@@ -966,18 +964,23 @@ class GuiPreferences(NDialog):
# Appearance
guiLocale = self.guiLocale.currentData()
- guiTheme = self.guiTheme.currentData()
+ lightTheme = self.lightTheme.currentData()
+ darkTheme = self.darkTheme.currentData()
iconTheme = self.iconTheme.currentData()
useCharCount = self.useCharCount.isChecked()
- updateTheme |= CONFIG.guiTheme != guiTheme
+ updateTheme |= CONFIG.lightTheme != lightTheme
+ updateTheme |= CONFIG.darkTheme != darkTheme
updateTheme |= CONFIG.iconTheme != iconTheme
needsRestart |= CONFIG.guiLocale != guiLocale
needsRestart |= CONFIG.guiFont != self._guiFont
refreshTree |= CONFIG.useCharCount != useCharCount
+ updateSyntax |= CONFIG.lightTheme != lightTheme
+ updateSyntax |= CONFIG.darkTheme != darkTheme
CONFIG.guiLocale = guiLocale
- CONFIG.guiTheme = guiTheme
+ CONFIG.lightTheme = lightTheme
+ CONFIG.darkTheme = darkTheme
CONFIG.iconTheme = iconTheme
CONFIG.hideVScroll = self.hideVScroll.isChecked()
CONFIG.hideHScroll = self.hideHScroll.isChecked()
@@ -986,11 +989,6 @@ class GuiPreferences(NDialog):
CONFIG.setGuiFont(self._guiFont)
# Document Style
- guiSyntax = self.guiSyntax.currentData()
-
- updateSyntax |= CONFIG.guiSyntax != guiSyntax
-
- CONFIG.guiSyntax = guiSyntax
CONFIG.showFullPath = self.showFullPath.isChecked()
CONFIG.incNotesWCount = self.incNotesWCount.isChecked()
CONFIG.setTextFont(self._textFont)
diff --git a/novelwriter/enum.py b/novelwriter/enum.py
index a837d824..18899295 100644
--- a/novelwriter/enum.py
+++ b/novelwriter/enum.py
@@ -156,6 +156,13 @@ class nwFocus(Enum):
OUTLINE = 3
+class nwTheme(Enum):
+
+ AUTO = 0
+ LIGHT = 1
+ DARK = 2
+
+
class nwOutline(Enum):
TITLE = 0
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 1eb2e487..1118f7d5 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -31,16 +31,16 @@ from typing import TYPE_CHECKING, Final
from PyQt6.QtCore import QSize, Qt
from PyQt6.QtGui import (
- QColor, QFont, QFontDatabase, QFontMetrics, QIcon, QPainter, QPainterPath,
- QPalette, QPixmap
+ QColor, QFont, QFontDatabase, QFontMetrics, QGuiApplication, QIcon,
+ QPainter, QPainterPath, QPalette, QPixmap
)
from PyQt6.QtWidgets import QApplication
from novelwriter import CONFIG
from novelwriter.common import NWConfigParser, minmax
-from novelwriter.config import DEF_GUI, DEF_ICONS, DEF_SYNTAX
+from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS
from novelwriter.constants import nwLabels
-from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
+from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwTheme
from novelwriter.error import logException
from novelwriter.types import QtBlack, QtHexArgb, QtPaintAntiAlias, QtTransparent
@@ -49,6 +49,8 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
+T_ThemeEntry = tuple[str, str, bool]
+
STYLES_FLAT_TABS = "flatTabWidget"
STYLES_MIN_TOOLBUTTON = "minimalToolButton"
STYLES_BIG_TOOLBUTTON = "bigToolButton"
@@ -57,6 +59,7 @@ STYLES_BIG_TOOLBUTTON = "bigToolButton"
class ThemeMeta:
name: str = ""
+ mode: str = ""
description: str = ""
author: str = ""
credit: str = ""
@@ -96,13 +99,13 @@ class GuiTheme:
"""
__slots__ = (
- "_availSyntax", "_availThemes", "_guiPalette", "_styleSheets", "_syntaxList", "_themeList",
- "baseButtonHeight", "baseIconHeight", "baseIconSize", "buttonIconSize", "errorText",
- "fadedText", "fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration",
- "getHeaderDecorationNarrow", "getIcon", "getIconColor", "getItemIcon", "getPixmap",
- "getToggleIcon", "guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall",
- "helpText", "iconCache", "isDarkTheme", "syntaxMeta", "syntaxTheme", "textNHeight",
- "textNWidth", "themeMeta",
+ "_availSyntax", "_availThemes", "_darkThemes", "_guiPalette", "_lightThemes",
+ "_styleSheets", "_syntaxList", "_themeList", "baseButtonHeight", "baseIconHeight",
+ "baseIconSize", "buttonIconSize", "errorText", "fadedText", "fontPixelSize",
+ "fontPointSize", "getDecoration", "getHeaderDecoration", "getHeaderDecorationNarrow",
+ "getIcon", "getIconColor", "getItemIcon", "getPixmap", "getToggleIcon", "guiFont",
+ "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", "helpText", "iconCache",
+ "isDarkTheme", "syntaxMeta", "syntaxTheme", "textNHeight", "textNWidth", "themeMeta",
)
def __init__(self) -> None:
@@ -124,8 +127,7 @@ class GuiTheme:
# Load Themes
self._guiPalette = QPalette()
- self._themeList: list[tuple[str, str]] = []
- self._syntaxList: list[tuple[str, str]] = []
+ self._themeList: list[T_ThemeEntry] = []
self._availThemes: dict[str, Path] = {}
self._availSyntax: dict[str, Path] = {}
self._styleSheets: dict[str, str] = {}
@@ -179,13 +181,10 @@ class GuiTheme:
logger.debug("Text 'N' Width: %d", self.textNWidth)
# Process Themes
- _listConf(self._availSyntax, CONFIG.assetPath("syntax"), ".conf")
_listConf(self._availThemes, CONFIG.assetPath("themes"), ".conf")
- _listConf(self._availSyntax, CONFIG.dataPath("syntax"), ".conf")
_listConf(self._availThemes, CONFIG.dataPath("themes"), ".conf")
self.loadTheme()
- self.loadSyntax()
return
@@ -207,13 +206,35 @@ class GuiTheme:
# Theme Methods
##
+ def isDesktopDarkMode(self) -> bool:
+ """Check if the desktop is in dark mode."""
+ if CONFIG.verQtValue >= 0x060500 and (hint := QGuiApplication.styleHints()):
+ return hint.colorScheme() == Qt.ColorScheme.Dark
+
+ palette = QPalette()
+ text = palette.color(QPalette.ColorRole.WindowText)
+ window = palette.color(QPalette.ColorRole.Window)
+ return text.lightnessF() > window.lightnessF()
+
def loadTheme(self) -> bool:
"""Load the currently specified GUI theme."""
- theme = CONFIG.guiTheme
+ match CONFIG.themeMode:
+ case nwTheme.LIGHT:
+ darkMode = False
+ case nwTheme.DARK:
+ darkMode = True
+ case _:
+ darkMode = self.isDesktopDarkMode()
+
+ theme = CONFIG.darkTheme if darkMode else CONFIG.lightTheme
if theme not in self._availThemes:
logger.error("Could not find GUI theme '%s'", theme)
- theme = DEF_GUI
- CONFIG.guiTheme = theme
+ if darkMode:
+ theme = DEF_GUI_DARK
+ CONFIG.darkTheme = DEF_GUI_DARK
+ else:
+ theme = DEF_GUI_LIGHT
+ CONFIG.lightTheme = DEF_GUI_LIGHT
if not (file := self._availThemes.get(theme)):
logger.error("Could not load GUI theme")
@@ -238,6 +259,7 @@ class GuiTheme:
meta = ThemeMeta()
if parser.has_section(sec):
meta.name = parser.rdStr(sec, "name", "")
+ meta.mode = parser.rdStr(sec, "mode", "light")
meta.description = parser.rdStr(sec, "description", "N/A")
meta.author = parser.rdStr(sec, "author", "N/A")
meta.credit = parser.rdStr(sec, "credit", "N/A")
@@ -296,6 +318,31 @@ class GuiTheme:
self.fadedText = self._parseColor(parser, sec, "fadedtext")
self.errorText = self._parseColor(parser, sec, "errortext")
+ # Syntax
+ sec = "Syntax"
+ self.syntaxTheme = SyntaxColors()
+ if parser.has_section(sec):
+ self.syntaxTheme.back = self._parseColor(parser, sec, "background")
+ self.syntaxTheme.text = self._parseColor(parser, sec, "text")
+ self.syntaxTheme.link = self._parseColor(parser, sec, "link")
+ self.syntaxTheme.head = self._parseColor(parser, sec, "headertext")
+ self.syntaxTheme.headH = self._parseColor(parser, sec, "headertag")
+ self.syntaxTheme.emph = self._parseColor(parser, sec, "emphasis")
+ self.syntaxTheme.dialN = self._parseColor(parser, sec, "dialog")
+ self.syntaxTheme.dialA = self._parseColor(parser, sec, "altdialog")
+ self.syntaxTheme.hidden = self._parseColor(parser, sec, "hidden")
+ self.syntaxTheme.note = self._parseColor(parser, sec, "note")
+ self.syntaxTheme.code = self._parseColor(parser, sec, "shortcode")
+ self.syntaxTheme.key = self._parseColor(parser, sec, "keyword")
+ self.syntaxTheme.tag = self._parseColor(parser, sec, "tag")
+ self.syntaxTheme.val = self._parseColor(parser, sec, "value")
+ self.syntaxTheme.opt = self._parseColor(parser, sec, "optional")
+ self.syntaxTheme.spell = self._parseColor(parser, sec, "spellcheckline")
+ self.syntaxTheme.error = self._parseColor(parser, sec, "errorline")
+ self.syntaxTheme.repTag = self._parseColor(parser, sec, "replacetag")
+ self.syntaxTheme.mod = self._parseColor(parser, sec, "modifier")
+ self.syntaxTheme.mark = self._parseColor(parser, sec, "texthighlight")
+
# Update Dependant Colours
# Based on: https://github.com/qt/qtbase/blob/dev/src/gui/kernel/qplatformtheme.cpp
text = self._guiPalette.text().color()
@@ -363,105 +410,22 @@ class GuiTheme:
return True
- def loadSyntax(self) -> bool:
- """Load the currently specified syntax highlighter theme."""
- theme = CONFIG.guiSyntax
- if theme not in self._availSyntax:
- logger.error("Could not find syntax theme '%s'", theme)
- theme = DEF_SYNTAX
- CONFIG.guiSyntax = theme
-
- if not (file := self._availSyntax.get(theme)):
- logger.error("Could not load syntax theme")
- return False
-
- CONFIG.splashMessage("Loading syntax theme ...")
- logger.info("Loading syntax theme '%s'", theme)
- parser = NWConfigParser()
- try:
- with open(file, mode="r", encoding="utf-8") as fo:
- parser.read_file(fo)
- except Exception:
- logger.error("Could not read file: %s", file)
- logException()
- return False
-
- # Main
- sec = "Main"
- meta = ThemeMeta()
- if parser.has_section(sec):
- meta.name = parser.rdStr(sec, "name", "")
- meta.description = parser.rdStr(sec, "description", "N/A")
- meta.author = parser.rdStr(sec, "author", "N/A")
- meta.credit = parser.rdStr(sec, "credit", "N/A")
- meta.url = parser.rdStr(sec, "url", "")
- meta.license = parser.rdStr(sec, "license", "N/A")
- meta.licenseUrl = parser.rdStr(sec, "licenseurl", "")
-
- # Syntax
- sec = "Syntax"
- syntax = SyntaxColors()
- if parser.has_section(sec):
- syntax.back = self._parseColor(parser, sec, "background")
- syntax.text = self._parseColor(parser, sec, "text")
- syntax.link = self._parseColor(parser, sec, "link")
- syntax.head = self._parseColor(parser, sec, "headertext")
- syntax.headH = self._parseColor(parser, sec, "headertag")
- syntax.emph = self._parseColor(parser, sec, "emphasis")
- syntax.dialN = self._parseColor(parser, sec, "dialog")
- syntax.dialA = self._parseColor(parser, sec, "altdialog")
- syntax.hidden = self._parseColor(parser, sec, "hidden")
- syntax.note = self._parseColor(parser, sec, "note")
- syntax.code = self._parseColor(parser, sec, "shortcode")
- syntax.key = self._parseColor(parser, sec, "keyword")
- syntax.tag = self._parseColor(parser, sec, "tag")
- syntax.val = self._parseColor(parser, sec, "value")
- syntax.opt = self._parseColor(parser, sec, "optional")
- syntax.spell = self._parseColor(parser, sec, "spellcheckline")
- syntax.error = self._parseColor(parser, sec, "errorline")
- syntax.repTag = self._parseColor(parser, sec, "replacetag")
- syntax.mod = self._parseColor(parser, sec, "modifier")
- syntax.mark = self._parseColor(parser, sec, "texthighlight")
-
- CONFIG.splashMessage(f"Loaded syntax theme: {meta.name}")
-
- self.syntaxMeta = meta
- self.syntaxTheme = syntax
-
- return True
-
- def listThemes(self) -> list[tuple[str, str]]:
+ def listThemes(self) -> list[T_ThemeEntry]:
"""Scan the GUI themes folder and list all themes."""
if self._themeList:
return self._themeList
- themes = []
+ themes: list[T_ThemeEntry] = []
parser = NWConfigParser()
for key, path in self._availThemes.items():
logger.debug("Checking theme config '%s'", key)
- if name := _loadInternalName(parser, path):
- themes.append((key, name))
+ if meta := _loadInternalName(parser, path):
+ themes.append((key, meta[0], meta[1]))
self._themeList = sorted(themes, key=_sortTheme)
return self._themeList
- def listSyntax(self) -> list[tuple[str, str]]:
- """Scan the syntax themes folder and list all themes."""
- if self._syntaxList:
- return self._syntaxList
-
- themes = []
- parser = NWConfigParser()
- for key, path in self._availSyntax.items():
- logger.debug("Checking theme syntax '%s'", key)
- if name := _loadInternalName(parser, path):
- themes.append((key, name))
-
- self._syntaxList = sorted(themes, key=_sortTheme)
-
- return self._syntaxList
-
def getStyleSheet(self, name: str) -> str:
"""Load a standard style sheet."""
return self._styleSheets.get(name, "")
@@ -889,22 +853,24 @@ def _listConf(target: dict, path: Path, extension: str) -> None:
return
-def _sortTheme(data: tuple[str, str]) -> str:
+def _sortTheme(data: tuple) -> str:
"""Key function for theme sorting."""
- key, name = data
+ key, name = data[:2]
return f"*{name}" if key.startswith("default_") else name
-def _loadInternalName(parser: NWConfigParser, path: str | Path) -> str:
+def _loadInternalName(parser: NWConfigParser, path: str | Path) -> tuple[str, bool]:
"""Open a conf file and read the 'name' setting."""
try:
with open(path, mode="r", encoding="utf-8") as inFile:
parser.read_file(inFile)
- return parser.rdStr("Main", "name", "")
+ name = parser.rdStr("Main", "name", "")
+ dark = parser.rdStr("Main", "mode", "light").lower() == "dark"
+ return name, dark
except Exception:
logger.error("Could not read file: %s", path)
logException()
- return ""
+ return "", False
def _loadIconName(path: Path) -> str:
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 8a895bfe..601540f0 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -1069,7 +1069,6 @@ class GuiMain(QMainWindow):
SHARED.project.tree.refreshAllItems()
if syntax:
- SHARED.theme.loadSyntax()
self.docEditor.updateSyntaxColors()
self.docEditor.initEditor()
From 857327e9ef7a2c168075d7395ef21d31f56f47d4 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 17:06:28 +0200
Subject: [PATCH 04/96] Update theme colour processing and fix current tests
---
novelwriter/assets/themes/default_dark.conf | 1 +
novelwriter/assets/themes/default_light.conf | 1 +
novelwriter/gui/statusbar.py | 6 +-
novelwriter/gui/theme.py | 278 +++++++++++--------
tests/reference/baseConfig_novelwriter.conf | 7 +-
tests/test_base/test_base_config.py | 8 +-
tests/test_dialogs/test_dlg_preferences.py | 34 +--
tests/test_gui/test_gui_guimain.py | 9 +-
tests/test_gui/test_gui_theme.py | 150 ++++++----
9 files changed, 285 insertions(+), 209 deletions(-)
diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf
index 76916604..6ccf6ede 100644
--- a/novelwriter/assets/themes/default_dark.conf
+++ b/novelwriter/assets/themes/default_dark.conf
@@ -1,5 +1,6 @@
[Main]
name = Default Dark Theme
+mode = dark
description = The novelWriter standard dark theme
author = Veronica Berglyd Olsen
credit = Veronica Berglyd Olsen
diff --git a/novelwriter/assets/themes/default_light.conf b/novelwriter/assets/themes/default_light.conf
index 29b4b98a..f236f404 100644
--- a/novelwriter/assets/themes/default_light.conf
+++ b/novelwriter/assets/themes/default_light.conf
@@ -1,5 +1,6 @@
[Main]
name = Default Light Theme
+mode = light
description = The novelWriter standard light theme
author = Veronica Berglyd Olsen
credit = Veronica Berglyd Olsen
diff --git a/novelwriter/gui/statusbar.py b/novelwriter/gui/statusbar.py
index 3239fd4a..315633da 100644
--- a/novelwriter/gui/statusbar.py
+++ b/novelwriter/gui/statusbar.py
@@ -143,9 +143,9 @@ class GuiMainStatus(QStatusBar):
self.idlePixmap = SHARED.theme.getPixmap("timer_off", (iPx, iPx))
self.timeIcon.setPixmap(self.timePixmap)
- colNone = SHARED.theme.getIconColor("default").darker(150)
- colSaved = SHARED.theme.getIconColor("green").darker(150)
- colUnsaved = SHARED.theme.getIconColor("red").darker(150)
+ colNone = SHARED.theme.getBaseColor("default").darker(150)
+ colSaved = SHARED.theme.getBaseColor("green").darker(150)
+ colUnsaved = SHARED.theme.getBaseColor("red").darker(150)
self.docIcon.setColors(colNone, colSaved, colUnsaved)
self.projIcon.setColors(colNone, colSaved, colUnsaved)
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 1118f7d5..437531a3 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -26,6 +26,7 @@ from __future__ import annotations
import logging
+from configparser import ConfigParser
from math import ceil
from typing import TYPE_CHECKING, Final
@@ -37,7 +38,7 @@ from PyQt6.QtGui import (
from PyQt6.QtWidgets import QApplication
from novelwriter import CONFIG
-from novelwriter.common import NWConfigParser, minmax
+from novelwriter.common import checkInt, minmax
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS
from novelwriter.constants import nwLabels
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwTheme
@@ -99,13 +100,14 @@ class GuiTheme:
"""
__slots__ = (
- "_availSyntax", "_availThemes", "_darkThemes", "_guiPalette", "_lightThemes",
- "_styleSheets", "_syntaxList", "_themeList", "baseButtonHeight", "baseIconHeight",
- "baseIconSize", "buttonIconSize", "errorText", "fadedText", "fontPixelSize",
- "fontPointSize", "getDecoration", "getHeaderDecoration", "getHeaderDecorationNarrow",
- "getIcon", "getIconColor", "getItemIcon", "getPixmap", "getToggleIcon", "guiFont",
- "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", "helpText", "iconCache",
- "isDarkTheme", "syntaxMeta", "syntaxTheme", "textNHeight", "textNWidth", "themeMeta",
+ "_availSyntax", "_availThemes", "_darkThemes", "_guiPalette", "_lightThemes", "_qColors",
+ "_styleSheets", "_svgColors", "_syntaxList", "_themeList", "baseButtonHeight",
+ "baseIconHeight", "baseIconSize", "buttonIconSize", "errorText", "fadedText",
+ "fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration",
+ "getHeaderDecorationNarrow", "getIcon", "getItemIcon", "getPixmap", "getToggleIcon",
+ "guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", "helpText",
+ "iconCache", "isDarkTheme", "syntaxMeta", "syntaxTheme", "textNHeight", "textNWidth",
+ "themeMeta",
)
def __init__(self) -> None:
@@ -131,12 +133,13 @@ class GuiTheme:
self._availThemes: dict[str, Path] = {}
self._availSyntax: dict[str, Path] = {}
self._styleSheets: dict[str, str] = {}
+ self._svgColors: dict[str, bytes] = {}
+ self._qColors: dict[str, QColor] = {}
# Icon Functions
self.getIcon = self.iconCache.getIcon
self.getPixmap = self.iconCache.getPixmap
self.getItemIcon = self.iconCache.getItemIcon
- self.getIconColor = self.iconCache.getIconColor
self.getToggleIcon = self.iconCache.getToggleIcon
self.getDecoration = self.iconCache.getDecoration
self.getHeaderDecoration = self.iconCache.getHeaderDecoration
@@ -202,6 +205,14 @@ class GuiTheme:
qMetrics = QFontMetrics(self.guiFont)
return ceil(qMetrics.boundingRect(text).width())
+ def getBaseColor(self, name: str) -> QColor:
+ """Return a base color."""
+ return QColor(self._qColors.get(name) or QtBlack)
+
+ def getRawBaseColor(self, name: str) -> bytes:
+ """Return a base color."""
+ return self._svgColors.get(name, self._svgColors.get("default", b"#000000"))
+
##
# Theme Methods
##
@@ -216,6 +227,34 @@ class GuiTheme:
window = palette.color(QPalette.ColorRole.Window)
return text.lightnessF() > window.lightnessF()
+ def parseColor(self, value: str, default: QColor = QtBlack) -> QColor:
+ """Parse a string as a colour value."""
+ if value in self._qColors:
+ # Named colour
+ return self._qColors[value]
+ elif value.startswith("#"):
+ if len(value) >= 9:
+ # Convert from #RRGGBBAA to #AARRGGBB
+ return QColor.fromString(f"#{value[7:9]}{value[1:7]}")
+ else:
+ # Assume #RRGGBB
+ return QColor.fromString(value[:7])
+ elif "," in value:
+ data = value.split(",")
+ entries = len(data)
+ if entries == 2:
+ # Assume name, alpha
+ color = self._qColors.get(data[0].strip(), default)
+ color.setAlpha(checkInt(data[1], 255))
+ return color
+ else:
+ # Assume red, green, blue, alpha
+ result = [0, 0, 0, 255]
+ for i in range(min(entries, 4)):
+ result[i] = checkInt(data[i].strip(), result[i])
+ return QColor(*result)
+ return default
+
def loadTheme(self) -> bool:
"""Load the currently specified GUI theme."""
match CONFIG.themeMode:
@@ -242,7 +281,7 @@ class GuiTheme:
CONFIG.splashMessage("Loading GUI theme ...")
logger.info("Loading GUI theme '%s'", theme)
- parser = NWConfigParser()
+ parser = ConfigParser()
try:
with open(file, mode="r", encoding="utf-8") as fo:
parser.read_file(fo)
@@ -258,40 +297,40 @@ class GuiTheme:
sec = "Main"
meta = ThemeMeta()
if parser.has_section(sec):
- meta.name = parser.rdStr(sec, "name", "")
- meta.mode = parser.rdStr(sec, "mode", "light")
- meta.description = parser.rdStr(sec, "description", "N/A")
- meta.author = parser.rdStr(sec, "author", "N/A")
- meta.credit = parser.rdStr(sec, "credit", "N/A")
- meta.url = parser.rdStr(sec, "url", "")
- meta.license = parser.rdStr(sec, "license", "N/A")
- meta.licenseUrl = parser.rdStr(sec, "licenseurl", "")
+ meta.name = parser.get(sec, "name", fallback="")
+ meta.mode = parser.get(sec, "mode", fallback="light")
+ meta.description = parser.get(sec, "description", fallback="N/A")
+ meta.author = parser.get(sec, "author", fallback="N/A")
+ meta.credit = parser.get(sec, "credit", fallback="N/A")
+ meta.url = parser.get(sec, "url", fallback="")
+ meta.license = parser.get(sec, "license", fallback="N/A")
+ meta.licenseUrl = parser.get(sec, "licenseurl", fallback="")
self.themeMeta = meta
# Icons
- sec = "Icons"
+ sec = "Base"
if parser.has_section(sec):
- self.iconCache.setIconColor("default", self._parseColor(parser, sec, "default"))
- self.iconCache.setIconColor("faded", self._parseColor(parser, sec, "faded"))
- self.iconCache.setIconColor("red", self._parseColor(parser, sec, "red"))
- self.iconCache.setIconColor("orange", self._parseColor(parser, sec, "orange"))
- self.iconCache.setIconColor("yellow", self._parseColor(parser, sec, "yellow"))
- self.iconCache.setIconColor("green", self._parseColor(parser, sec, "green"))
- self.iconCache.setIconColor("aqua", self._parseColor(parser, sec, "aqua"))
- self.iconCache.setIconColor("blue", self._parseColor(parser, sec, "blue"))
- self.iconCache.setIconColor("purple", self._parseColor(parser, sec, "purple"))
+ self._setBaseColor("default", self._readColor(parser, sec, "default"))
+ self._setBaseColor("faded", self._readColor(parser, sec, "faded"))
+ self._setBaseColor("red", self._readColor(parser, sec, "red"))
+ self._setBaseColor("orange", self._readColor(parser, sec, "orange"))
+ self._setBaseColor("yellow", self._readColor(parser, sec, "yellow"))
+ self._setBaseColor("green", self._readColor(parser, sec, "green"))
+ self._setBaseColor("aqua", self._readColor(parser, sec, "aqua"))
+ self._setBaseColor("blue", self._readColor(parser, sec, "blue"))
+ self._setBaseColor("purple", self._readColor(parser, sec, "purple"))
# Project
sec = "Project"
if parser.has_section(sec):
- self.iconCache.setIconColor("root", self._parseColor(parser, sec, "root"))
- self.iconCache.setIconColor("folder", self._parseColor(parser, sec, "folder"))
- self.iconCache.setIconColor("file", self._parseColor(parser, sec, "file"))
- self.iconCache.setIconColor("title", self._parseColor(parser, sec, "title"))
- self.iconCache.setIconColor("chapter", self._parseColor(parser, sec, "chapter"))
- self.iconCache.setIconColor("scene", self._parseColor(parser, sec, "scene"))
- self.iconCache.setIconColor("note", self._parseColor(parser, sec, "note"))
+ self._setBaseColor("root", self._readColor(parser, sec, "root"))
+ self._setBaseColor("folder", self._readColor(parser, sec, "folder"))
+ self._setBaseColor("file", self._readColor(parser, sec, "file"))
+ self._setBaseColor("title", self._readColor(parser, sec, "title"))
+ self._setBaseColor("chapter", self._readColor(parser, sec, "chapter"))
+ self._setBaseColor("scene", self._readColor(parser, sec, "scene"))
+ self._setBaseColor("note", self._readColor(parser, sec, "note"))
# Palette
sec = "Palette"
@@ -314,34 +353,34 @@ class GuiTheme:
# GUI
sec = "GUI"
if parser.has_section(sec):
- self.helpText = self._parseColor(parser, sec, "helptext")
- self.fadedText = self._parseColor(parser, sec, "fadedtext")
- self.errorText = self._parseColor(parser, sec, "errortext")
+ self.helpText = self._readColor(parser, sec, "helptext")
+ self.fadedText = self._readColor(parser, sec, "fadedtext")
+ self.errorText = self._readColor(parser, sec, "errortext")
# Syntax
sec = "Syntax"
self.syntaxTheme = SyntaxColors()
if parser.has_section(sec):
- self.syntaxTheme.back = self._parseColor(parser, sec, "background")
- self.syntaxTheme.text = self._parseColor(parser, sec, "text")
- self.syntaxTheme.link = self._parseColor(parser, sec, "link")
- self.syntaxTheme.head = self._parseColor(parser, sec, "headertext")
- self.syntaxTheme.headH = self._parseColor(parser, sec, "headertag")
- self.syntaxTheme.emph = self._parseColor(parser, sec, "emphasis")
- self.syntaxTheme.dialN = self._parseColor(parser, sec, "dialog")
- self.syntaxTheme.dialA = self._parseColor(parser, sec, "altdialog")
- self.syntaxTheme.hidden = self._parseColor(parser, sec, "hidden")
- self.syntaxTheme.note = self._parseColor(parser, sec, "note")
- self.syntaxTheme.code = self._parseColor(parser, sec, "shortcode")
- self.syntaxTheme.key = self._parseColor(parser, sec, "keyword")
- self.syntaxTheme.tag = self._parseColor(parser, sec, "tag")
- self.syntaxTheme.val = self._parseColor(parser, sec, "value")
- self.syntaxTheme.opt = self._parseColor(parser, sec, "optional")
- self.syntaxTheme.spell = self._parseColor(parser, sec, "spellcheckline")
- self.syntaxTheme.error = self._parseColor(parser, sec, "errorline")
- self.syntaxTheme.repTag = self._parseColor(parser, sec, "replacetag")
- self.syntaxTheme.mod = self._parseColor(parser, sec, "modifier")
- self.syntaxTheme.mark = self._parseColor(parser, sec, "texthighlight")
+ self.syntaxTheme.back = self._readColor(parser, sec, "background")
+ self.syntaxTheme.text = self._readColor(parser, sec, "text")
+ self.syntaxTheme.link = self._readColor(parser, sec, "link")
+ self.syntaxTheme.head = self._readColor(parser, sec, "headertext")
+ self.syntaxTheme.headH = self._readColor(parser, sec, "headertag")
+ self.syntaxTheme.emph = self._readColor(parser, sec, "emphasis")
+ self.syntaxTheme.dialN = self._readColor(parser, sec, "dialog")
+ self.syntaxTheme.dialA = self._readColor(parser, sec, "altdialog")
+ self.syntaxTheme.hidden = self._readColor(parser, sec, "hidden")
+ self.syntaxTheme.note = self._readColor(parser, sec, "note")
+ self.syntaxTheme.code = self._readColor(parser, sec, "shortcode")
+ self.syntaxTheme.key = self._readColor(parser, sec, "keyword")
+ self.syntaxTheme.tag = self._readColor(parser, sec, "tag")
+ self.syntaxTheme.val = self._readColor(parser, sec, "value")
+ self.syntaxTheme.opt = self._readColor(parser, sec, "optional")
+ self.syntaxTheme.spell = self._readColor(parser, sec, "spellcheckline")
+ self.syntaxTheme.error = self._readColor(parser, sec, "errorline")
+ self.syntaxTheme.repTag = self._readColor(parser, sec, "replacetag")
+ self.syntaxTheme.mod = self._readColor(parser, sec, "modifier")
+ self.syntaxTheme.mark = self._readColor(parser, sec, "texthighlight")
# Update Dependant Colours
# Based on: https://github.com/qt/qtbase/blob/dev/src/gui/kernel/qplatformtheme.cpp
@@ -398,6 +437,18 @@ class GuiTheme:
self._guiPalette.setBrush(QtColInactive, QPalette.ColorRole.Accent, highlight)
self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.Accent, grey)
+ # Set project override colours
+ if (override := CONFIG.iconColTree) != "theme":
+ color = self._svgColors.get(override, b"#000000")
+ self._svgColors["root"] = color
+ self._svgColors["folder"] = color
+ if not CONFIG.iconColDocs:
+ self._svgColors["file"] = color
+ self._svgColors["title"] = color
+ self._svgColors["chapter"] = color
+ self._svgColors["scene"] = color
+ self._svgColors["note"] = color
+
# Load icons after the theme is parsed
self.iconCache.loadTheme(CONFIG.iconTheme)
@@ -416,7 +467,7 @@ class GuiTheme:
return self._themeList
themes: list[T_ThemeEntry] = []
- parser = NWConfigParser()
+ parser = ConfigParser()
for key, path in self._availThemes.items():
logger.debug("Checking theme config '%s'", key)
if meta := _loadInternalName(parser, path):
@@ -434,6 +485,12 @@ class GuiTheme:
# Internal Functions
##
+ def _setBaseColor(self, key: str, color: QColor) -> None:
+ """Set the colour for a named colour."""
+ self._qColors[key] = QColor(color)
+ self._svgColors[key] = color.name(QColor.NameFormat.HexRgb).encode("utf-8")
+ return
+
def _resetTheme(self) -> None:
"""Reset GUI colours to default values."""
palette = QPalette()
@@ -460,37 +517,38 @@ class GuiTheme:
self._guiPalette = palette
- # Reset Icons
- icons = self.iconCache
- icons.clear()
- icons.setIconColor("default", text)
- icons.setIconColor("faded", faded)
- icons.setIconColor("red", red)
- icons.setIconColor("orange", orange)
- icons.setIconColor("yellow", yellow)
- icons.setIconColor("green", green)
- icons.setIconColor("aqua", aqua)
- icons.setIconColor("blue", blue)
- icons.setIconColor("purple", purple)
- icons.setIconColor("root", blue)
- icons.setIconColor("folder", yellow)
- icons.setIconColor("file", text)
- icons.setIconColor("title", green)
- icons.setIconColor("chapter", red)
- icons.setIconColor("scene", blue)
- icons.setIconColor("note", yellow)
+ # Reset Base Colours and Icons
+ self.iconCache.clear()
+ self._svgColors = {}
+ self._qColors = {}
+ self._setBaseColor("default", text)
+ self._setBaseColor("faded", faded)
+ self._setBaseColor("red", red)
+ self._setBaseColor("orange", orange)
+ self._setBaseColor("yellow", yellow)
+ self._setBaseColor("green", green)
+ self._setBaseColor("aqua", aqua)
+ self._setBaseColor("blue", blue)
+ self._setBaseColor("purple", purple)
+ self._setBaseColor("root", blue)
+ self._setBaseColor("folder", yellow)
+ self._setBaseColor("file", text)
+ self._setBaseColor("title", green)
+ self._setBaseColor("chapter", red)
+ self._setBaseColor("scene", blue)
+ self._setBaseColor("note", yellow)
return
- def _parseColor(self, parser: NWConfigParser, section: str, name: str) -> QColor:
+ def _readColor(self, parser: ConfigParser, section: str, name: str) -> QColor:
"""Parse a colour value from a config string."""
- return QColor(*parser.rdIntList(section, name, [0, 0, 0, 255]))
+ return self.parseColor(parser.get(section, name, fallback="default"))
def _setPalette(
- self, parser: NWConfigParser, section: str, name: str, value: QPalette.ColorRole
+ self, parser: ConfigParser, section: str, name: str, value: QPalette.ColorRole
) -> None:
"""Set a palette colour value from a config string."""
- self._guiPalette.setBrush(value, self._parseColor(parser, section, name))
+ self._guiPalette.setBrush(value, self._readColor(parser, section, name))
return
def _buildStyleSheets(self, palette: QPalette) -> None:
@@ -536,9 +594,8 @@ class GuiIcons:
"""
__slots__ = (
- "_availThemes", "_headerDec", "_headerDecNarrow", "_noIcon",
- "_qColors", "_qIcons", "_svgColors", "_svgData", "_themeList",
- "mainTheme", "themeMeta",
+ "_availThemes", "_headerDec", "_headerDecNarrow", "_meta", "_noIcon",
+ "_qIcons", "_svgData", "_theme", "_themeList",
)
TOGGLE_ICON_KEYS: Final[dict[str, tuple[str, str]]] = {
@@ -552,13 +609,11 @@ class GuiIcons:
def __init__(self, mainTheme: GuiTheme) -> None:
- self.mainTheme = mainTheme
- self.themeMeta = ThemeMeta()
+ self._theme = mainTheme
+ self._meta = ThemeMeta()
# Storage
self._svgData: dict[str, bytes] = {}
- self._svgColors: dict[str, bytes] = {}
- self._qColors: dict[str, QColor] = {}
self._qIcons: dict[str, QIcon] = {}
self._headerDec: list[QPixmap] = []
self._headerDecNarrow: list[QPixmap] = []
@@ -578,12 +633,10 @@ class GuiIcons:
def clear(self) -> None:
"""Clear the icon cache."""
self._svgData = {}
- self._svgColors = {}
- self._qColors = {}
self._qIcons = {}
self._headerDec = []
self._headerDecNarrow = []
- self.themeMeta = ThemeMeta()
+ self._meta = ThemeMeta()
return
##
@@ -622,7 +675,7 @@ class GuiIcons:
meta.author = value
elif key == "meta:license":
meta.license = value
- self.themeMeta = meta
+ self._meta = meta
except Exception:
logger.error("Could not read file: %s", file)
logException()
@@ -631,38 +684,16 @@ class GuiIcons:
CONFIG.splashMessage(f"Loaded icon theme: {meta.name}")
CONFIG.splashMessage("Generating additional icons ...")
- # Set colour overrides for project item icons
- if (override := CONFIG.iconColTree) != "theme":
- color = self._svgColors.get(override, b"#000000")
- self._svgColors["root"] = color
- self._svgColors["folder"] = color
- if not CONFIG.iconColDocs:
- self._svgColors["file"] = color
- self._svgColors["title"] = color
- self._svgColors["chapter"] = color
- self._svgColors["scene"] = color
- self._svgColors["note"] = color
-
# Populate generated icons cache
self.getHeaderDecoration(0)
self.getHeaderDecorationNarrow(0)
return True
- def setIconColor(self, key: str, color: QColor) -> None:
- """Set an icon colour for a named colour."""
- self._qColors[key] = QColor(color)
- self._svgColors[key] = color.name(QColor.NameFormat.HexRgb).encode("utf-8")
- return
-
##
# Access Functions
##
- def getIconColor(self, name: str) -> QColor:
- """Return an icon color."""
- return QColor(self._qColors.get(name) or QtBlack)
-
def getIcon(self, name: str, color: str | None = None, w: int = 24, h: int = 24) -> QIcon:
"""Return an icon from the icon buffer, or load it."""
variant = f"{name}-{color}" if color else name
@@ -733,7 +764,7 @@ class GuiIcons:
map or the icon map. This function always returns a QPixmap.
"""
if name in self.IMAGE_MAP:
- idx = int(self.mainTheme.isDarkTheme)
+ idx = int(self._theme.isDarkTheme)
imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name][idx]
else:
logger.error("Decoration with name '%s' does not exist", name)
@@ -757,7 +788,7 @@ class GuiIcons:
def getHeaderDecoration(self, hLevel: int) -> QPixmap:
"""Get the decoration for a specific heading level."""
if not self._headerDec:
- iPx = self.mainTheme.baseIconHeight
+ iPx = self._theme.baseIconHeight
self._headerDec = [
self._generateDecoration("file", iPx, 0),
self._generateDecoration("title", iPx, 0),
@@ -770,7 +801,7 @@ class GuiIcons:
def getHeaderDecorationNarrow(self, hLevel: int) -> QPixmap:
"""Get the narrow decoration for a specific heading level."""
if not self._headerDecNarrow:
- iPx = self.mainTheme.baseIconHeight
+ iPx = self._theme.baseIconHeight
self._headerDecNarrow = [
self._generateDecoration("file", iPx, 0),
self._generateDecoration("title", iPx, 0),
@@ -811,7 +842,7 @@ class GuiIcons:
return QIcon(str(CONFIG.assetPath("icons") / "x-novelwriter-project.svg"))
if svg := self._svgData.get(name, b""):
- if fill := self._svgColors.get(color or "default"):
+ if fill := self._theme.getRawBaseColor(color or "default"):
svg = svg.replace(b"#000000", fill)
pixmap = QPixmap(w, h)
pixmap.fill(QtTransparent)
@@ -833,7 +864,7 @@ class GuiIcons:
painter = QPainter(pixmap)
painter.setRenderHint(QtPaintAntiAlias)
- if fill := self._svgColors.get(color or "default"):
+ if fill := self._theme.getRawBaseColor(color or "default"):
painter.fillPath(path, QColor(fill.decode(encoding="utf-8")))
painter.end()
@@ -859,13 +890,14 @@ def _sortTheme(data: tuple) -> str:
return f"*{name}" if key.startswith("default_") else name
-def _loadInternalName(parser: NWConfigParser, path: str | Path) -> tuple[str, bool]:
+def _loadInternalName(parser: ConfigParser, path: str | Path) -> tuple[str, bool]:
"""Open a conf file and read the 'name' setting."""
try:
+ parser.clear()
with open(path, mode="r", encoding="utf-8") as inFile:
parser.read_file(inFile)
- name = parser.rdStr("Main", "name", "")
- dark = parser.rdStr("Main", "mode", "light").lower() == "dark"
+ name = parser.get("Main", "name", fallback="")
+ dark = parser.get("Main", "mode", fallback="light").lower() == "dark"
return name, dark
except Exception:
logger.error("Could not read file: %s", path)
diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf
index 78441636..8943a9b2 100644
--- a/tests/reference/baseConfig_novelwriter.conf
+++ b/tests/reference/baseConfig_novelwriter.conf
@@ -1,10 +1,11 @@
[Meta]
-timestamp = 2025-05-11 13:28:11
+timestamp = 2025-06-02 16:21:38
[Main]
font =
-theme = default
-syntax = default_light
+lighttheme = default_light
+darktheme = default_dark
+thememode = AUTO
icons = material_rounded_normal
iconcoltree = theme
iconcoldocs = False
diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py
index 81b03831..98a1063d 100644
--- a/tests/test_base/test_base_config.py
+++ b/tests/test_base/test_base_config.py
@@ -131,15 +131,15 @@ def testBaseConfig_InitLoadSave(monkeypatch, fncPath, tstPaths):
assert tstConf.errorText().startswith("Could not load config file")
# Change a few settings, save, reset, and reload
- tstConf.guiTheme = "foo"
- tstConf.guiSyntax = "bar"
+ tstConf.lightTheme = "foo"
+ tstConf.darkTheme = "bar"
assert tstConf.saveConfig() is True
newConf = Config()
newConf.initConfig(confPath=fncPath, dataPath=fncPath)
newConf.loadConfig()
- assert newConf.guiTheme == "foo"
- assert newConf.guiSyntax == "bar"
+ assert newConf.lightTheme == "foo"
+ assert newConf.darkTheme == "bar"
# Test Correcting Quote Settings
tstConf.fmtDQuoteOpen = '"'
diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py
index 5572f430..ccb77133 100644
--- a/tests/test_dialogs/test_dlg_preferences.py
+++ b/tests/test_dialogs/test_dlg_preferences.py
@@ -27,7 +27,7 @@ from PyQt6.QtGui import QAction, QFont, QFontDatabase, QKeyEvent
from PyQt6.QtWidgets import QFileDialog, QFontDialog
from novelwriter import CONFIG, SHARED
-from novelwriter.config import DEF_GUI
+from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT
from novelwriter.constants import nwUnicode
from novelwriter.dialogs.preferences import GuiPreferences
from novelwriter.dialogs.quotes import GuiQuoteSelect
@@ -55,15 +55,11 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths):
assert "en_GB" in languages
# Check GUI Themes
- themes = [prefs.guiTheme.itemData(i) for i in range(prefs.guiTheme.count())]
- assert len(themes) >= 5
- assert DEF_GUI in themes
+ themes = [prefs.lightTheme.itemData(i) for i in range(prefs.lightTheme.count())]
+ assert DEF_GUI_LIGHT in themes
- # Check GUI Syntax
- syntax = [prefs.guiSyntax.itemData(i) for i in range(prefs.guiSyntax.count())]
- assert len(syntax) >= 10
- assert "default_dark" in syntax
- assert "default_light" in syntax
+ themes = [prefs.darkTheme.itemData(i) for i in range(prefs.darkTheme.count())]
+ assert DEF_GUI_DARK in themes
# Check Spell Checking
spelling = [prefs.spellLanguage.itemData(i) for i in range(prefs.spellLanguage.count())]
@@ -124,7 +120,7 @@ def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI):
button = prefs.buttonBox.button(QtDialogSave)
assert button is not None
button.click()
- assert signal.args == [False, False, False, False]
+ assert len(signal.args) == 4
# Check Close Button
prefs.show()
@@ -151,6 +147,12 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
(fncPath / "nw_en_US.qm").touch()
(fncPath / "project_en_US.json").touch()
CONFIG._nwLangPath = fncPath
+ SHARED.theme._themeList = [
+ ("theme1", "Theme 1", False),
+ ("theme2", "Theme 2", False),
+ ("theme3", "Theme 3", True),
+ ("theme4", "Theme 4", True),
+ ]
prefs = GuiPreferences(nwGUI)
with qtbot.waitExposed(prefs):
@@ -158,7 +160,8 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
# Appearance
prefs.guiLocale.setCurrentIndex(prefs.guiLocale.findData("en_US"))
- prefs.guiTheme.setCurrentIndex(prefs.guiTheme.findData("default_dark"))
+ prefs.lightTheme.setCurrentIndex(prefs.lightTheme.findData("theme1"))
+ prefs.darkTheme.setCurrentIndex(prefs.darkTheme.findData("theme3"))
with monkeypatch.context() as mp:
mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
prefs.nativeFont.setChecked(True) # Use OS font dialog
@@ -169,14 +172,14 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
prefs.useCharCount.setChecked(True)
assert CONFIG.guiLocale != "en_US"
- assert CONFIG.guiTheme != "default_dark"
+ assert CONFIG.lightTheme == "default_light"
+ assert CONFIG.darkTheme == "default_dark"
assert CONFIG.guiFont.family() != ""
assert CONFIG.hideVScroll is False
assert CONFIG.hideHScroll is False
assert CONFIG.useCharCount is False
# Document Style
- prefs.guiSyntax.setCurrentIndex(prefs.guiSyntax.findData("default_dark"))
with monkeypatch.context() as mp:
mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
prefs.nativeFont.setChecked(False) # Use Qt font dialog
@@ -185,7 +188,6 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
prefs.showFullPath.setChecked(False)
prefs.incNotesWCount.setChecked(False)
- assert CONFIG.guiSyntax != "default_dark"
assert CONFIG.textFont.family() != ""
assert CONFIG.showFullPath is True
assert CONFIG.incNotesWCount is True
@@ -344,14 +346,14 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
# Appearance
assert CONFIG.guiLocale == "en_US"
- assert CONFIG.guiTheme == "default_dark"
+ assert CONFIG.lightTheme == "theme1"
+ assert CONFIG.darkTheme == "theme3"
assert CONFIG.guiFont == QFont()
assert CONFIG.hideVScroll is True
assert CONFIG.hideHScroll is True
assert CONFIG.useCharCount is True
# Document Style
- assert CONFIG.guiSyntax == "default_dark"
assert CONFIG.textFont == QFont()
assert CONFIG.showFullPath is False
assert CONFIG.incNotesWCount is False
diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py
index 6fda9b6a..91207d09 100644
--- a/tests/test_gui/test_gui_guimain.py
+++ b/tests/test_gui/test_gui_guimain.py
@@ -34,7 +34,7 @@ from PyQt6.QtWidgets import QInputDialog, QMessageBox
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwFiles
from novelwriter.dialogs.editlabel import GuiEditLabel
-from novelwriter.enum import nwDocAction, nwDocMode, nwFocus, nwItemType, nwView
+from novelwriter.enum import nwDocAction, nwDocMode, nwFocus, nwItemType, nwTheme, nwView
from novelwriter.gui.doceditor import GuiDocEditor
from novelwriter.gui.noveltree import GuiNovelView
from novelwriter.gui.outline import GuiOutlineView
@@ -180,10 +180,11 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
def testGuiMain_UpdateTheme(qtbot, nwGUI):
"""Test updating the theme in the GUI."""
mainTheme = SHARED.theme
- CONFIG.guiTheme = "default_dark"
- CONFIG.guiSyntax = "default_dark"
+ CONFIG.themeMode = nwTheme.DARK
+ CONFIG.darkTheme = "default_dark"
+ CONFIG.lightTheme = "default_light"
mainTheme.loadTheme()
- mainTheme.loadSyntax()
+
nwGUI._processConfigChanges(False, True, False, False)
nwGUI._processConfigChanges(True, True, True, True)
diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py
index c64f1172..ff4308f2 100644
--- a/tests/test_gui/test_gui_theme.py
+++ b/tests/test_gui/test_gui_theme.py
@@ -22,6 +22,7 @@ from __future__ import annotations
import sys
+from configparser import ConfigParser
from pathlib import Path
import pytest
@@ -29,8 +30,7 @@ import pytest
from PyQt6.QtGui import QColor, QIcon, QPalette, QPixmap
from novelwriter import CONFIG, SHARED
-from novelwriter.common import NWConfigParser
-from novelwriter.config import DEF_GUI
+from novelwriter.config import DEF_GUI_LIGHT
from novelwriter.constants import nwLabels
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.gui.theme import _listConf
@@ -39,17 +39,51 @@ from tests.mocked import causeOSError
from tests.tools import writeFile
+@pytest.mark.gui
+def testGuiTheme_ParseColor(qtbot, nwGUI):
+ """Test the colour parsing."""
+ theme = SHARED.theme
+
+ # Pre-Populate
+ theme._qColors["red"] = QColor(255, 0, 0)
+ theme._qColors["green"] = QColor(0, 255, 0)
+ theme._qColors["blue"] = QColor(0, 0, 255)
+
+ # By Name
+ assert theme.parseColor("red").getRgb() == (255, 0, 0, 255)
+ assert theme.parseColor("green").getRgb() == (0, 255, 0, 255)
+ assert theme.parseColor("blue").getRgb() == (0, 0, 255, 255)
+ assert theme.parseColor("bob").getRgb() == (0, 0, 0, 255)
+
+ # CSS Format
+ assert theme.parseColor("#ff0000").getRgb() == (255, 0, 0, 255)
+ assert theme.parseColor("#ff00007f").getRgb() == (255, 0, 0, 127)
+ assert theme.parseColor("#ff00").getRgb() == (0, 0, 0, 255) # Too short -> ignored
+ assert theme.parseColor("#ff00007f15").getRgb() == (255, 0, 0, 127) # Too long -> truncated
+
+ # Name + Alpha
+ assert theme.parseColor("red, 255").getRgb() == (255, 0, 0, 255)
+ assert theme.parseColor("red, 127").getRgb() == (255, 0, 0, 127)
+ assert theme.parseColor("red, 512").getRgb() == (255, 0, 0, 255) # Value truncated
+
+ # Values
+ assert theme.parseColor("255, 0, 0").getRgb() == (255, 0, 0, 255)
+ assert theme.parseColor("255, 0, 0, 255").getRgb() == (255, 0, 0, 255)
+ assert theme.parseColor("255, 0, 0, 127").getRgb() == (255, 0, 0, 127)
+ assert theme.parseColor("255, 0, 0, 127, 42").getRgb() == (255, 0, 0, 127) # Truncated
+
+
@pytest.mark.gui
def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
"""Test the theme class init."""
- mainTheme = SHARED.theme
+ theme = SHARED.theme
# Methods
# =======
- mSize = mainTheme.getTextWidth("m")
+ mSize = theme.getTextWidth("m")
assert mSize > 0
- assert mainTheme.getTextWidth("m", mainTheme.guiFont) == mSize
+ assert theme.getTextWidth("m", theme.guiFont) == mSize
# Scan for Themes
# ===============
@@ -70,7 +104,7 @@ def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
# Parse Colours
# =============
- parser = NWConfigParser()
+ parser = ConfigParser()
parser["Palette"] = {
"colour1": "100, 150, 200", # Valid
"colour2": "100, 150, 200, 250", # With alpha
@@ -81,38 +115,39 @@ def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
}
# Test the parser for several valid and invalid values
- assert mainTheme._parseColor(parser, "Palette", "colour1").getRgb() == (100, 150, 200, 255)
- assert mainTheme._parseColor(parser, "Palette", "colour2").getRgb() == (100, 150, 200, 250)
- assert mainTheme._parseColor(parser, "Palette", "colour3").getRgb() == (100, 150, 200, 250)
- assert mainTheme._parseColor(parser, "Palette", "colour4").getRgb() == (250, 250, 0, 255)
- assert mainTheme._parseColor(parser, "Palette", "colour5").getRgb() == (0, 0, 0, 0)
- assert mainTheme._parseColor(parser, "Palette", "colour6").getRgb() == (0, 127, 255, 255)
+ assert theme._readColor(parser, "Palette", "colour1").getRgb() == (100, 150, 200, 255)
+ assert theme._readColor(parser, "Palette", "colour2").getRgb() == (100, 150, 200, 250)
+ assert theme._readColor(parser, "Palette", "colour3").getRgb() == (100, 150, 200, 250)
+ assert theme._readColor(parser, "Palette", "colour4").getRgb() == (0, 0, 0, 250)
+ assert theme._readColor(parser, "Palette", "colour5").getRgb() == (0, 0, 0, 0)
+ assert theme._readColor(parser, "Palette", "colour6").getRgb() == (0, 127, 255, 255)
# The palette should load with the parsed values
- mainTheme._setPalette(parser, "Palette", "colour1", QPalette.ColorRole.Window)
- assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 255)
- mainTheme._setPalette(parser, "Palette", "colour2", QPalette.ColorRole.Window)
- assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 250)
- mainTheme._setPalette(parser, "Palette", "colour3", QPalette.ColorRole.Window)
- assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 250)
- mainTheme._setPalette(parser, "Palette", "colour4", QPalette.ColorRole.Window)
- assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (250, 250, 0, 255)
- mainTheme._setPalette(parser, "Palette", "colour5", QPalette.ColorRole.Window)
- assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 0)
- mainTheme._setPalette(parser, "Palette", "colour6", QPalette.ColorRole.Window)
- assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 127, 255, 255)
+ theme._setPalette(parser, "Palette", "colour1", QPalette.ColorRole.Window)
+ assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 255)
+ theme._setPalette(parser, "Palette", "colour2", QPalette.ColorRole.Window)
+ assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 250)
+ theme._setPalette(parser, "Palette", "colour3", QPalette.ColorRole.Window)
+ assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 250)
+ theme._setPalette(parser, "Palette", "colour4", QPalette.ColorRole.Window)
+ assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 250)
+ theme._setPalette(parser, "Palette", "colour5", QPalette.ColorRole.Window)
+ assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 0)
+ theme._setPalette(parser, "Palette", "colour6", QPalette.ColorRole.Window)
+ assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 127, 255, 255)
# Non-existing value should return default colour
- mainTheme._setPalette(parser, "Palette", "stuff", QPalette.ColorRole.Window)
- assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 255)
+ theme._setPalette(parser, "Palette", "stuff", QPalette.ColorRole.Window)
+ assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 255)
# qtbot.stop()
@pytest.mark.gui
+@pytest.mark.skip
def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths):
"""Test the theme part of the class."""
- mainTheme = SHARED.theme
+ theme = SHARED.theme
# List Themes
# ===========
@@ -120,45 +155,46 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths):
# Block the reading of the files
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
- assert mainTheme.listThemes() == []
+ theme._themeList = []
+ assert theme.listThemes() == []
# Load the theme info, default themes first
- themesList = mainTheme.listThemes()
+ themesList = theme.listThemes()
assert themesList[0] == ("default_dark", "Default Dark Theme")
assert themesList[1] == ("default_light", "Default Light Theme")
assert themesList[2] == ("cyberpunk_night", "Cyberpunk Night")
assert themesList[3] == ("dracula", "Dracula")
# A second call should returned the cached list
- assert mainTheme.listThemes() == mainTheme._themeList
+ assert theme.listThemes() == theme._themeList
# Check handling of broken theme settings
CONFIG.guiTheme = "not_a_theme"
- availThemes = mainTheme._availThemes
- mainTheme._availThemes = {}
- assert mainTheme.loadTheme() is False
- mainTheme._availThemes = availThemes
+ availThemes = theme._availThemes
+ theme._availThemes = {}
+ assert theme.loadTheme() is False
+ theme._availThemes = availThemes
# Check handling of unreadable file
- CONFIG.guiTheme = DEF_GUI
+ CONFIG.guiTheme = DEF_GUI_LIGHT
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
- assert mainTheme.loadTheme() is False
+ assert theme.loadTheme() is False
# Load Default Theme
# ==================
if sys.platform != "win32":
# Set a mock colour for the window background
- mainTheme._guiPalette.color(QPalette.ColorRole.Window).setRgb(0, 0, 0, 0)
+ theme._guiPalette.color(QPalette.ColorRole.Window).setRgb(0, 0, 0, 0)
# Load the default theme
- CONFIG.guiTheme = DEF_GUI
- assert mainTheme.loadTheme() is True
+ CONFIG.guiTheme = DEF_GUI_LIGHT
+ assert theme.loadTheme() is True
# This should load a standard palette
wCol = QPalette().color(QPalette.ColorRole.Window).getRgb()
- assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == wCol
+ assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == wCol
# Mock Dark Theme
# ===============
@@ -172,32 +208,32 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths):
"window = 0, 0, 0\n"
"text = 255, 255, 255\n"
)
- mainTheme._availThemes["test"] = mockTheme
+ theme._availThemes["test"] = mockTheme
CONFIG.guiTheme = "test"
- assert mainTheme.loadTheme() is True
- assert mainTheme._guiPalette.window().color().getRgb() == (0, 0, 0, 255)
- assert mainTheme._guiPalette.text().color().getRgb() == (255, 255, 255, 255)
- assert mainTheme._guiPalette.light().color().getRgb() == (57, 57, 57, 255)
- assert mainTheme.isDarkTheme is True
+ assert theme.loadTheme() is True
+ assert theme._guiPalette.window().color().getRgb() == (0, 0, 0, 255)
+ assert theme._guiPalette.text().color().getRgb() == (255, 255, 255, 255)
+ assert theme._guiPalette.light().color().getRgb() == (57, 57, 57, 255)
+ assert theme.isDarkTheme is True
# Load Default Light Theme
# ========================
CONFIG.guiTheme = "default_light"
- assert mainTheme.loadTheme() is True
+ assert theme.loadTheme() is True
# Check a few values
- assert mainTheme._guiPalette.color(
+ assert theme._guiPalette.color(
QPalette.ColorRole.Window
).getRgb() == (239, 239, 239, 255)
- assert mainTheme._guiPalette.color(
+ assert theme._guiPalette.color(
QPalette.ColorRole.WindowText
).getRgb() == (0, 0, 0, 255)
- assert mainTheme._guiPalette.color(
+ assert theme._guiPalette.color(
QPalette.ColorRole.Base
).getRgb() == (255, 255, 255, 255)
- assert mainTheme._guiPalette.color(
+ assert theme._guiPalette.color(
QPalette.ColorRole.AlternateBase
).getRgb() == (224, 224, 224, 255)
@@ -205,21 +241,22 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths):
# =======================
CONFIG.guiTheme = "default_dark"
- assert mainTheme.loadTheme() is True
+ assert theme.loadTheme() is True
# Check a few values
- assert mainTheme._guiPalette.color(
+ assert theme._guiPalette.color(
QPalette.ColorRole.Window).getRgb() == (54, 54, 54, 255)
- assert mainTheme._guiPalette.color(
+ assert theme._guiPalette.color(
QPalette.ColorRole.WindowText).getRgb() == (204, 204, 204, 255)
- assert mainTheme._guiPalette.color(
+ assert theme._guiPalette.color(
QPalette.ColorRole.Base).getRgb() == (62, 62, 62, 255)
- assert mainTheme._guiPalette.color(
+ assert theme._guiPalette.color(
QPalette.ColorRole.AlternateBase).getRgb() == (78, 78, 78, 255)
# qtbot.stop()
+@pytest.mark.skip
@pytest.mark.gui
def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI):
"""Test the syntax part of the class."""
@@ -283,6 +320,7 @@ def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI):
# qtbot.stop()
+@pytest.mark.skip
@pytest.mark.gui
def testGuiTheme_IconThemes(qtbot, caplog, monkeypatch, nwGUI, tstPaths):
"""Test the icon cache class."""
From 04526385d1ae7757aeeec52c3ca5cc059995c571 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 17:43:27 +0200
Subject: [PATCH 05/96] Update existing matched GUI and syntax themes
---
.../assets/syntax/cyberpunk_night.conf | 28 -----
novelwriter/assets/syntax/dracula.conf | 44 -------
novelwriter/assets/syntax/snazzy.conf | 42 -------
novelwriter/assets/syntax/solarized_dark.conf | 29 -----
.../assets/syntax/solarized_light.conf | 29 -----
.../assets/themes/cyberpunk_night.conf | 91 +++++++++------
novelwriter/assets/themes/dracula.conf | 94 +++++++++------
novelwriter/assets/themes/snazzy.conf | 104 +++++++++++------
novelwriter/assets/themes/solarized_dark.conf | 110 ++++++++++++------
.../assets/themes/solarized_light.conf | 110 ++++++++++++------
10 files changed, 338 insertions(+), 343 deletions(-)
delete mode 100644 novelwriter/assets/syntax/cyberpunk_night.conf
delete mode 100644 novelwriter/assets/syntax/dracula.conf
delete mode 100644 novelwriter/assets/syntax/snazzy.conf
delete mode 100644 novelwriter/assets/syntax/solarized_dark.conf
delete mode 100644 novelwriter/assets/syntax/solarized_light.conf
diff --git a/novelwriter/assets/syntax/cyberpunk_night.conf b/novelwriter/assets/syntax/cyberpunk_night.conf
deleted file mode 100644
index 57a14536..00000000
--- a/novelwriter/assets/syntax/cyberpunk_night.conf
+++ /dev/null
@@ -1,28 +0,0 @@
-[Main]
-name = Cyberpunk Night
-author = Anders Lemvigh
-url = https://github.com/alemvigh
-license = CC BY-SA 4.0
-licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
-
-[Syntax]
-background = 0, 0, 0
-text = 150, 150, 150
-link = 77, 77, 255
-headertext = 255, 255, 255
-headertag = 50, 0, 180
-emphasis = 0, 255, 255
-dialog = 0, 255, 0
-altdialog = 0, 140, 255
-note = 150, 150, 150
-hidden = 77, 77, 100
-shortcode = 255, 255, 0
-keyword = 255, 100, 255
-tag = 255, 150, 10
-value = 255, 150, 10
-spellcheckline = 242, 72, 23
-errorline = 186, 218, 4
-replacetag = 0, 0, 180
-modifier = 144, 142, 176
-optional = 180, 180, 180
-texthighlight = 255, 255, 132, 96
diff --git a/novelwriter/assets/syntax/dracula.conf b/novelwriter/assets/syntax/dracula.conf
deleted file mode 100644
index fad01ea8..00000000
--- a/novelwriter/assets/syntax/dracula.conf
+++ /dev/null
@@ -1,44 +0,0 @@
-[Main]
-name = Dracula
-author = Veronica Berglyd Olsen (adaptation)
-credit = Zeno Rocha
-url = https://draculatheme.com
-license = MIT
-licenseurl = https://github.com/dracula/dracula-theme/blob/main/LICENSE
-
-##
-# Colours:
-# Background = 282a36 : 40, 42, 54
-# Foreground = f8f8f2 : 248, 248, 242
-# Comment = 6272a4 : 98, 114, 164
-# Cyan = 8be9fd : 139, 233, 253
-# Green = 50fa7b : 80, 250, 123
-# Orange = ffb86c : 255, 184, 108
-# Pink = ff79c6 : 255, 121, 198
-# Purple = bd93f9 : 189, 147, 249
-# Red = ff5555 : 255, 85, 85
-# Yellow = f1fa8c : 241, 250, 140
-##
-
-[Syntax]
-# See: https://spec.draculatheme.com/
-background = 40, 42, 54
-text = 248, 248, 242
-link = 255, 121, 198
-headertext = 189, 147, 249
-headertag = 189, 147, 249, 160
-emphasis = 255, 184, 108
-dialog = 80, 250, 123
-altdialog = 241, 250, 140
-note = 255, 204, 233
-hidden = 98, 114, 164
-shortcode = 139, 233, 253
-keyword = 255, 121, 198
-tag = 255, 184, 108
-value = 255, 184, 108
-optional = 80, 250, 123
-spellcheckline = 255, 85, 85
-errorline = 80, 250, 123
-replacetag = 241, 250, 140
-modifier = 139, 233, 253
-texthighlight = 241, 250, 140, 96
diff --git a/novelwriter/assets/syntax/snazzy.conf b/novelwriter/assets/syntax/snazzy.conf
deleted file mode 100644
index d948b770..00000000
--- a/novelwriter/assets/syntax/snazzy.conf
+++ /dev/null
@@ -1,42 +0,0 @@
-[Main]
-name = Snazzy Light
-author = Veronica Berglyd Olsen (adaptation)
-credit = Florian Reuschel (color theme)
-url = https://github.com/loilo/vscode-snazzy-light
-license = MIT License
-licenseurl = https://github.com/loilo/vscode-snazzy-light/blob/master/LICENSE
-
-##
-# Colours:
-# Background = fafbfc : 250, 251, 252
-# Foreground = 565869 : 86, 88, 105
-# Comment = 9194a2 : 145, 148, 162
-# Yellow = cf9c00 : 207, 156, 0
-# Red = ff5c57 : 255, 92, 87
-# Pink = f767bb : 247, 103, 187
-# Blue = 09a1ed : 9, 161, 237
-# Cyan = 13bbb7 : 19, 187, 183
-# Green = 2dae58 : 45, 174, 88
-##
-
-[Syntax]
-background = 250, 251, 252
-text = 86, 88, 105
-link = 9, 161, 237
-headertext = 45, 174, 88
-headertag = 45, 174, 88, 160
-emphasis = 247, 103, 187
-dialog = 9, 161, 237
-altdialog = 207, 156, 0
-note = 120, 187, 185
-hidden = 145, 148, 162
-shortcode = 247, 103, 187
-keyword = 9, 161, 237
-tag = 45, 174, 88
-value = 207, 156, 0
-optional = 207, 156, 0
-spellcheckline = 255, 92, 87
-errorline = 45, 174, 88
-replacetag = 19, 187, 183
-modifier = 247, 103, 187
-texthighlight = 207, 156, 0, 96
diff --git a/novelwriter/assets/syntax/solarized_dark.conf b/novelwriter/assets/syntax/solarized_dark.conf
deleted file mode 100644
index 0fbacdb7..00000000
--- a/novelwriter/assets/syntax/solarized_dark.conf
+++ /dev/null
@@ -1,29 +0,0 @@
-[Main]
-name = Solarized Dark
-author = nullbasis
-credit = Ethan Schoonover
-url = https://ethanschoonover.com/solarized/
-license = MIT
-licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
-
-[Syntax]
-background = 7, 54, 66
-text = 253, 246, 227
-link = 38, 139, 210
-headertext = 147, 161, 161
-headertag = 42, 161, 152
-emphasis = 38, 139, 210
-dialog = 42, 161, 152
-altdialog = 42, 161, 152
-note = 101, 161, 156
-hidden = 147, 161, 161
-shortcode = 147, 161, 161
-keyword = 133, 153, 0
-tag = 203, 75, 22
-value = 203, 75, 22
-optional = 147, 161, 161
-spellcheckline = 203, 75, 22
-errorline = 220, 50, 47
-replacetag = 133, 153, 0
-modifier = 181, 137, 0
-texthighlight = 181, 137, 0, 96
diff --git a/novelwriter/assets/syntax/solarized_light.conf b/novelwriter/assets/syntax/solarized_light.conf
deleted file mode 100644
index 525a59e5..00000000
--- a/novelwriter/assets/syntax/solarized_light.conf
+++ /dev/null
@@ -1,29 +0,0 @@
-[Main]
-name = Solarized Light
-author = nullbasis
-credit = Ethan Schoonover
-url = https://ethanschoonover.com/solarized/
-license = MIT
-licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
-
-[Syntax]
-background = 253, 246, 227
-text = 0, 43, 54
-link = 38, 139, 210
-headertext = 88, 110, 117
-headertag = 42, 161, 152
-emphasis = 38, 139, 210
-dialog = 42, 161, 152
-altdialog = 42, 161, 152
-note = 27, 102, 96
-hidden = 88, 110, 117
-shortcode = 88, 110, 117
-keyword = 133, 153, 0
-tag = 203, 75, 22
-value = 203, 75, 22
-optional = 88, 110, 117
-spellcheckline = 203, 75, 22
-errorline = 220, 50, 47
-replacetag = 133, 153, 0
-modifier = 181, 137, 0
-texthighlight = 181, 137, 0, 96
diff --git a/novelwriter/assets/themes/cyberpunk_night.conf b/novelwriter/assets/themes/cyberpunk_night.conf
index 125cab45..1ee10ca8 100644
--- a/novelwriter/assets/themes/cyberpunk_night.conf
+++ b/novelwriter/assets/themes/cyberpunk_night.conf
@@ -1,48 +1,71 @@
[Main]
name = Cyberpunk Night
+mode = dark
description = A taste of the future 80s
author = Anders Lemvigh
url = https://github.com/alemvigh
license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
-[Icons]
-default = 136, 136, 136
-faded = 97, 97, 97
-red = 242, 72, 23
-orange = 255, 150, 10
-yellow = 255, 255, 0
-green = 0, 255, 0
-aqua = 0, 255, 255
-blue = 77, 77, 255
-purple = 50, 0, 180
+[Base]
+default = #888888
+faded = #616161
+red = #f24817
+orange = #ff960a
+yellow = #ffff00
+green = #00ff00
+aqua = #00ffff
+blue = #4d4dff
+purple = #320064
[Project]
-root = 77, 77, 255
-folder = 255, 255, 0
-file = 136, 136, 136
-title = 0, 255, 0
-chapter = 242, 72, 23
-scene = 77, 77, 255
-note = 255, 255, 0
+root = #4d4dff
+folder = #ffff00
+file = #888888
+title = #00ff00
+chapter = #f24817
+scene = #4d4dff
+note = #ffff00
[Palette]
-window = 0, 0, 0
-windowtext = 150, 150, 150
-base = 0, 0, 0
-alternatebase = 40, 40, 40
-text = 150, 150, 150
-tooltipbase = 40, 20, 70
-tooltiptext = 255, 255, 255
-button = 5, 0, 10
-buttontext = 150, 150, 150
-brighttext = 255, 255, 255
-highlight = 50, 30, 80
-highlightedtext = 255, 255, 255
-link = 77, 77, 255
-linkvisited = 50, 0, 80
+window = #000000
+windowtext = #969696
+base = #000000
+alternatebase = #282828
+text = #969696
+tooltipbase = #281446
+tooltiptext = #ffffff
+button = #05000a
+buttontext = #969696
+brighttext = #ffffff
+highlight = #321e50
+highlightedtext = #ffffff
+link = #4d4dff
+linkvisited = #320050
[GUI]
-helptext = 97, 97, 97
-fadedtext = 97, 97, 97
-errortext = 242, 72, 23
+helptext = #616161
+fadedtext = #616161
+errortext = #f24817
+
+[Syntax]
+background = #000000
+text = #969696
+link = #4d4dff
+headertext = #ffffff
+headertag = #320064
+emphasis = #00ffff
+dialog = #00ff00
+altdialog = #008cff
+note = #969696
+hidden = #4d4d64
+shortcode = #ffff00
+keyword = #ff64ff
+tag = #ff960a
+value = #ff960a
+spellcheckline = #f24817
+errorline = #bada04
+replacetag = #0000b4
+modifier = #908eb0
+optional = #b4b4b4
+texthighlight = #ffff8460
diff --git a/novelwriter/assets/themes/dracula.conf b/novelwriter/assets/themes/dracula.conf
index 5805b84b..49f30501 100644
--- a/novelwriter/assets/themes/dracula.conf
+++ b/novelwriter/assets/themes/dracula.conf
@@ -1,5 +1,6 @@
[Main]
name = Dracula
+mode = dark
description = A dark theme with bright colours
author = Veronica Berglyd Olsen (adaptation)
credit = Zeno Rocha
@@ -9,9 +10,10 @@ licenseurl = https://github.com/dracula/dracula-theme/blob/main/LICENSE
##
# Colours:
+# See: https://spec.draculatheme.com/
# Background = 282a36 : 40, 42, 54
# Foreground = f8f8f2 : 248, 248, 242
-# Current Line = 44475A : 68, 71, 90
+# Current Line = 44475a : 68, 71, 90
# Comment = 6272a4 : 98, 114, 164
# Cyan = 8be9fd : 139, 233, 253
# Green = 50fa7b : 80, 250, 123
@@ -22,43 +24,65 @@ licenseurl = https://github.com/dracula/dracula-theme/blob/main/LICENSE
# Yellow = f1fa8c : 241, 250, 140
##
-[Icons]
-default = 230, 230, 224
-faded = 98, 114, 164
-red = 255, 85, 85
-orange = 255, 184, 108
-yellow = 241, 250, 140
-green = 80, 250, 123
-aqua = 139, 233, 253
-blue = 147, 207, 249
-purple = 189, 147, 249
+[Base]
+default = #e6e6e0
+faded = #6272a4
+red = #ff5555
+orange = #ffb86c
+yellow = #f1fa8c
+green = #50fa7b
+aqua = #8be9fd
+blue = #93cff9
+purple = #bd93f9
[Project]
-root = 189, 147, 249
-folder = 241, 250, 140
-file = 230, 230, 224
-title = 80, 250, 123
-chapter = 255, 85, 85
-scene = 139, 233, 253
-note = 241, 250, 140
+root = #bd93f9
+folder = #f1fa8c
+file = #e6e6e0
+title = #50fa7b
+chapter = #ff5555
+scene = #8be9fd
+note = #f1fa8c
[Palette]
-window = 68, 71, 90
-windowtext = 248, 248, 242
-base = 40, 42, 54
-alternatebase = 51, 54, 69
-text = 248, 248, 242
-tooltipbase = 241, 250, 140
-tooltiptext = 40, 42, 54
-button = 80, 83, 105
-buttontext = 248, 248, 242
-brighttext = 68, 71, 90
-highlight = 166, 129, 218
-highlightedtext = 248, 248, 242
-link = 139, 233, 253
-linkvisited = 139, 233, 253
+window = #44475a
+windowtext = #f8f8f2
+base = #282a36
+alternatebase = #333645
+text = #f8f8f2
+tooltipbase = #f1fa8c
+tooltiptext = #282a36
+button = #505369
+buttontext = #f8f8f2
+brighttext = #44475a
+highlight = #a681da
+highlightedtext = #f8f8f2
+link = #8be9fd
+linkvisited = #8be9fd
[GUI]
-helptext = 204, 172, 249
-fadedtext = 98, 114, 164
-errortext = 255, 85, 85
+helptext = #ccacf9
+fadedtext = #6272a4
+errortext = #ff5555
+
+[Syntax]
+background = #282a36
+text = #f8f8f2
+link = #ff79c6
+headertext = #bd93f9
+headertag = #bd93f9a0
+emphasis = #ffb86c
+dialog = #50fa7b
+altdialog = #f1fa8c
+note = #ffcce9
+hidden = #6272a4
+shortcode = #8be9fd
+keyword = #ff79c6
+tag = #ffb86c
+value = #ffb86c
+optional = #50fa7b
+spellcheckline = #ff5555
+errorline = #50fa7b
+replacetag = #f1fa8c
+modifier = #8be9fd
+texthighlight = #f1fa8c60
diff --git a/novelwriter/assets/themes/snazzy.conf b/novelwriter/assets/themes/snazzy.conf
index 39ec3c87..19a662c0 100644
--- a/novelwriter/assets/themes/snazzy.conf
+++ b/novelwriter/assets/themes/snazzy.conf
@@ -1,48 +1,84 @@
[Main]
name = Snazzy Light
+mode = light
author = Veronica Berglyd Olsen (adaptation)
credit = Florian Reuschel (color theme)
url = https://github.com/loilo/vscode-snazzy-light
license = MIT License
licenseurl = https://github.com/loilo/vscode-snazzy-light/blob/master/LICENSE
-[Icons]
-default = 86, 88, 105
-faded = 84, 85, 84
-red = 255, 92, 87
-orange = 245, 185, 0
-yellow = 207, 156, 0
-green = 45, 174, 88
-aqua = 19, 187, 183
-blue = 9, 161, 237
-purple = 247, 103, 187
+##
+# Colours:
+# Background = fafbfc : 250, 251, 252
+# Foreground = 565869 : 86, 88, 105
+# Comment = 9194a2 : 145, 148, 162
+# Yellow = cf9c00 : 207, 156, 0
+# Red = ff5c57 : 255, 92, 87
+# Pink = f767bb : 247, 103, 187
+# Blue = 09a1ed : 9, 161, 237
+# Cyan = 13bbb7 : 19, 187, 183
+# Green = 2dae58 : 45, 174, 88
+##
+
+[Base]
+default = #565869
+faded = #545554
+red = #ff5c57
+orange = #f5b900
+yellow = #cf9c00
+green = #2dae58
+aqua = #13bbb7
+blue = #09a1ed
+purple = #f767bb
[Project]
-root = 9, 161, 237
-folder = 207, 156, 0
-file = 84, 85, 84
-title = 45, 174, 88
-chapter = 255, 92, 87
-scene = 9, 161, 237
-note = 207, 156, 0
+root = #09a1ed
+folder = #cf9c00
+file = #545554
+title = #2dae58
+chapter = #ff5c57
+scene = #09a1ed
+note = #cf9c00
[Palette]
-window = 243, 244, 245
-windowtext = 86, 88, 105
-base = 250, 251, 252
-alternatebase = 234, 234, 235
-text = 86, 88, 105
-tooltipbase = 245, 233, 194
-tooltiptext = 86, 88, 105
-button = 250, 251, 252
-buttontext = 86, 88, 105
-brighttext = 255, 255, 255
-highlight = 9, 161, 237
-highlightedtext = 255, 255, 255
-link = 9, 161, 237
-linkvisited = 9, 161, 237
+window = #f3f4f5
+windowtext = #565869
+base = #fafbfc
+alternatebase = #eaeaeb
+text = #565869
+tooltipbase = #f5e9c2
+tooltiptext = #565869
+button = #fafbfc
+buttontext = #565869
+brighttext = #ffffff
+highlight = #09a1ed
+highlightedtext = #ffffff
+link = #09a1ed
+linkvisited = #09a1ed
[GUI]
-helptext = 9, 161, 237
-fadedtext = 84, 85, 84
-errortext = 255, 92, 87
+helptext = #09a1ed
+fadedtext = #545554
+errortext = #ff5c57
+
+[Syntax]
+background = #fafbfc
+text = #565869
+link = #09a1ed
+headertext = #2dae58
+headertag = #2dae58a0
+emphasis = #f767bb
+dialog = #09a1ed
+altdialog = #cf9c00
+note = #78bbb9
+hidden = #9194a2
+shortcode = #f767bb
+keyword = #09a1ed
+tag = #2dae58
+value = #cf9c00
+optional = #cf9c00
+spellcheckline = #ff5c57
+errorline = #2dae58
+replacetag = #13bbb7
+modifier = #f767bb
+texthighlight = #cf9c0060
diff --git a/novelwriter/assets/themes/solarized_dark.conf b/novelwriter/assets/themes/solarized_dark.conf
index 0f401eb9..354b2be9 100644
--- a/novelwriter/assets/themes/solarized_dark.conf
+++ b/novelwriter/assets/themes/solarized_dark.conf
@@ -1,48 +1,90 @@
[Main]
name = Solarized Dark
+mode = dark
author = nullbasis
credit = Ethan Schoonover
url = https://ethanschoonover.com/solarized/
license = MIT
licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
-[Icons]
-default = 230, 223, 206
-faded = 166, 161, 149
-red = 220, 50, 47
-orange = 203, 75, 22
-yellow = 181, 137, 0
-green = 133, 153, 0
-aqua = 42, 161, 152
-blue = 38, 139, 210
-purple = 211, 54, 130
+##
+# base03: #002b36
+# base02: #073642
+# base01: #586e75
+# base00: #657b83
+# base0: #839496
+# base1: #93a1a1
+# base2: #eee8d5
+# base3: #fdf6e3
+# yellow: #b58900
+# orange: #cb4b16
+# red: #dc322f
+# magenta: #d33682
+# violet: #6c71c4
+# blue: #268bd2
+# cyan: #2aa198
+# green: #859900
+##
+
+[Base]
+default = #e6dfce
+faded = #a6a195
+red = #dc322f
+orange = #cb4b16
+yellow = #b58900
+green = #859900
+aqua = #2aa198
+blue = #268bd2
+purple = #6c71c4
[Project]
-root = 42, 161, 152
-folder = 42, 161, 152
-file = 230, 223, 206
-title = 133, 153, 0
-chapter = 220, 50, 47
-scene = 38, 139, 210
-note = 181, 137, 0
+root = #2aa198
+folder = #2aa198
+file = #e6dfce
+title = #859900
+chapter = #dc322f
+scene = #268bd2
+note = #b58900
[Palette]
-window = 0, 43, 54
-windowtext = 253, 246, 227
-base = 7, 54, 66
-alternatebase = 88, 110, 117
-text = 253, 246, 227
-tooltipbase = 133, 153, 0
-tooltiptext = 0, 43, 54
-button = 7, 54, 66
-buttontext = 253, 246, 227
-brighttext = 7, 54, 66
-highlight = 42, 161, 152
-highlightedtext = 0, 43, 54
-link = 38, 139, 210
-linkvisited = 38, 139, 210
+window = #002b36
+windowtext = #fdf6e3
+base = #073642
+alternatebase = #586e75
+text = #fdf6e3
+tooltipbase = #859900
+tooltiptext = #002b36
+button = #073642
+buttontext = #fdf6e3
+brighttext = #073642
+highlight = #2aa198
+highlightedtext = #002b36
+link = #268bd2
+linkvisited = #268bd2
[GUI]
-helptext = 101, 123, 131
-fadedtext = 101, 123, 131
-errortext = 220, 50, 47
+helptext = #657b83
+fadedtext = #657b83
+errortext = #dc322f
+
+[Syntax]
+background = #073642
+text = #fdf6e3
+link = #268bd2
+headertext = #93a1a1
+headertag = #2aa198
+emphasis = #268bd2
+dialog = #2aa198
+altdialog = #2aa198
+note = #65a19c
+hidden = #93a1a1
+shortcode = #93a1a1
+keyword = #859900
+tag = #cb4b16
+value = #cb4b16
+optional = #93a1a1
+spellcheckline = #cb4b16
+errorline = #dc322f
+replacetag = #859900
+modifier = #b58900
+texthighlight = #b5890060
diff --git a/novelwriter/assets/themes/solarized_light.conf b/novelwriter/assets/themes/solarized_light.conf
index 06f4cb20..7d7427f5 100644
--- a/novelwriter/assets/themes/solarized_light.conf
+++ b/novelwriter/assets/themes/solarized_light.conf
@@ -1,48 +1,90 @@
[Main]
name = Solarized Light
+mode = light
author = nullbasis
credit = Ethan Schoonover
url = https://ethanschoonover.com/solarized/
license = MIT
licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
-[Icons]
-default = 59, 68, 71
-faded = 78, 91, 95
-red = 220, 50, 47
-orange = 203, 75, 22
-yellow = 181, 137, 0
-green = 133, 153, 0
-aqua = 42, 161, 152
-blue = 38, 139, 210
-purple = 211, 54, 130
+##
+# base03: #002b36
+# base02: #073642
+# base01: #586e75
+# base00: #657b83
+# base0: #839496
+# base1: #93a1a1
+# base2: #eee8d5
+# base3: #fdf6e3
+# yellow: #b58900
+# orange: #cb4b16
+# red: #dc322f
+# magenta: #d33682
+# violet: #6c71c4
+# blue: #268bd2
+# cyan: #2aa198
+# green: #859900
+##
+
+[Base]
+default = #3b4447
+faded = #4e5b5f
+red = #dc322f
+orange = #cb4b16
+yellow = #b58900
+green = #859900
+aqua = #2aa198
+blue = #268bd2
+purple = #6c71c4
[Project]
-root = 42, 161, 152
-folder = 42, 161, 152
-file = 59, 68, 71
-title = 133, 153, 0
-chapter = 220, 50, 47
-scene = 38, 139, 210
-note = 181, 137, 0
+root = #2aa198
+folder = #2aa198
+file = #3b4447
+title = #859900
+chapter = #dc322f
+scene = #268bd2
+note = #b58900
[Palette]
-window = 238, 232, 213
-windowtext = 0, 43, 54
-base = 253, 246, 227
-alternatebase = 147, 161, 161
-text = 0, 43, 54
-tooltipbase = 133, 153, 0
-tooltiptext = 0, 43, 54
-button = 238, 232, 213
-buttontext = 0, 43, 54
-brighttext = 253, 246, 227
-highlight = 42, 161, 152
-highlightedtext = 253, 246, 227
-link = 38, 139, 210
-linkvisited = 38, 139, 210
+window = #eee8d5
+windowtext = #002b36
+base = #fdf6e3
+alternatebase = #93a1a1
+text = #002b36
+tooltipbase = #859900
+tooltiptext = #002b36
+button = #eee8d5
+buttontext = #002b36
+brighttext = #fdf6e3
+highlight = #2aa198
+highlightedtext = #fdf6e3
+link = #268bd2
+linkvisited = #268bd2
[GUI]
-helptext = 78, 91, 95
-fadedtext = 78, 91, 95
-errortext = 220, 50, 47
+helptext = #4e5b5f
+fadedtext = #4e5b5f
+errortext = #dc322f
+
+[Syntax]
+background = #fdf6e3
+text = #002b36
+link = #268bd2
+headertext = #586e75
+headertag = #2aa198
+emphasis = #268bd2
+dialog = #2aa198
+altdialog = #2aa198
+note = #1b6660
+hidden = #586e75
+shortcode = #586e75
+keyword = #859900
+tag = #cb4b16
+value = #cb4b16
+optional = #586e75
+spellcheckline = #cb4b16
+errorline = #dc322f
+replacetag = #859900
+modifier = #b58900
+texthighlight = #b5890060
From 072d0f0acdd5f6f583f9b96d37556186e91fb94a Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 18:40:58 +0200
Subject: [PATCH 06/96] Merge and update the default themes
---
novelwriter/assets/syntax/default_dark.conf | 42 ---------
novelwriter/assets/syntax/default_light.conf | 42 ---------
novelwriter/assets/themes/default_dark.conf | 90 ++++++++++++--------
novelwriter/assets/themes/default_light.conf | 90 ++++++++++++--------
novelwriter/gui/theme.py | 2 +-
5 files changed, 113 insertions(+), 153 deletions(-)
delete mode 100644 novelwriter/assets/syntax/default_dark.conf
delete mode 100644 novelwriter/assets/syntax/default_light.conf
diff --git a/novelwriter/assets/syntax/default_dark.conf b/novelwriter/assets/syntax/default_dark.conf
deleted file mode 100644
index 8e467189..00000000
--- a/novelwriter/assets/syntax/default_dark.conf
+++ /dev/null
@@ -1,42 +0,0 @@
-[Main]
-name = Default Dark
-author = Veronica Berglyd Olsen
-credit = Veronica Berglyd Olsen
-url = https://github.com/vkbo/novelWriter
-license = CC BY-SA 4.0
-licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
-
-##
-# Colours:
-# Background = 2a2a2a : 42, 42, 42
-# Foreground = cccccc : 204, 204, 204
-# Comment = 969696 : 150, 150, 150
-# Red = e64250 : 230, 66, 80
-# Orange = f99157 : 249, 145, 87
-# Yellow = ffff84 : 255, 255, 132
-# Green = 99cc99 : 153, 204, 153
-# Blue = 288ed7 : 102, 153, 204
-# Purple = 8c56d7 : 140, 86, 215
-##
-
-[Syntax]
-background = 42, 42, 42
-text = 204, 204, 204
-link = 102, 153, 204
-headertext = 153, 204, 153
-headertag = 153, 204, 153, 160
-emphasis = 249, 145, 87
-dialog = 102, 153, 204
-altdialog = 102, 153, 204
-note = 255, 255, 216
-hidden = 150, 150, 150
-shortcode = 153, 204, 153
-keyword = 230, 66, 80
-tag = 153, 204, 153
-value = 102, 153, 204
-optional = 153, 204, 153
-spellcheckline = 230, 66, 80
-errorline = 153, 204, 153
-replacetag = 153, 204, 153
-modifier = 153, 204, 153
-texthighlight = 255, 255, 132, 96
diff --git a/novelwriter/assets/syntax/default_light.conf b/novelwriter/assets/syntax/default_light.conf
deleted file mode 100644
index 2e5ae404..00000000
--- a/novelwriter/assets/syntax/default_light.conf
+++ /dev/null
@@ -1,42 +0,0 @@
-[Main]
-name = Default Light
-author = Veronica Berglyd Olsen
-credit = Veronica Berglyd Olsen
-url = https://github.com/vkbo/novelWriter
-license = CC BY-SA 4.0
-licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
-
-##
-# Colours:
-# Background = ffffff : 255, 255, 255
-# Foreground = 000000 : 0, 0, 0
-# Comment = 646464 : 100, 100, 100
-# Red = c83232 : 200, 50, 50
-# Orange = ae6e1e : 174, 110, 30
-# Yellow = 969600 : 150, 150, 0
-# Green = 006400 : 0, 100, 0
-# Blue = 0000c8 : 0, 0, 200
-# Purple = 6400ae : 100, 0, 174
-##
-
-[Syntax]
-background = 255, 255, 255
-text = 0, 0, 0
-link = 0, 0, 200
-headertext = 0, 100, 0
-headertag = 0, 100, 0, 160
-emphasis = 174, 110, 30
-dialog = 0, 0, 200
-altdialog = 0, 0, 200
-note = 100, 100, 0
-hidden = 100, 100, 100
-shortcode = 0, 100, 0
-keyword = 200, 50, 50
-tag = 0, 100, 0
-value = 0, 0, 200
-optional = 0, 100, 0
-spellcheckline = 200, 50, 50
-errorline = 0, 100, 0
-replacetag = 0, 100, 0
-modifier = 0, 100, 0
-texthighlight = 150, 150, 0, 96
diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf
index 6ccf6ede..a8e6a3de 100644
--- a/novelwriter/assets/themes/default_dark.conf
+++ b/novelwriter/assets/themes/default_dark.conf
@@ -8,43 +8,65 @@ url = https://github.com/vkbo/novelWriter
license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
-[Icons]
-default = 184, 184, 184
-faded = 148, 148, 148
-red = 242, 119, 122
-orange = 249, 145, 57
-yellow = 255, 204, 102
-green = 153, 204, 153
-aqua = 102, 204, 204
-blue = 102, 153, 204
-purple = 204, 153, 204
+[Base]
+default = #cccccc
+faded = #949494
+red = #f2777a
+orange = #f99139
+yellow = #ffcc66
+green = #99cc99
+aqua = #66cccc
+blue = #6699cc
+purple = #cc99cc
[Project]
-root = 102, 153, 204
-folder = 255, 204, 102
-file = 184, 184, 184
-title = 153, 204, 153
-chapter = 242, 119, 122
-scene = 102, 153, 204
-note = 255, 204, 102
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
[Palette]
-window = 54, 54, 54
-windowtext = 204, 204, 204
-base = 62, 62, 62
-alternatebase = 78, 78, 78
-text = 204, 204, 204
-tooltipbase = 255, 255, 192
-tooltiptext = 21, 21, 13
-button = 62, 62, 62
-buttontext = 204, 204, 204
-brighttext = 62, 62, 62
-highlight = 44, 152, 247
-highlightedtext = 255, 255, 255
-link = 102, 153, 204
-linkvisited = 102, 153, 204
+window = #363636
+windowtext = default
+base = #3e3e3e
+alternatebase = #4e4e4e
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #3e3e3e
+buttontext = default
+brighttext = #3e3e3e
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
[GUI]
-helptext = 164, 164, 164
-fadedtext = 148, 148, 148
-errortext = 255, 164, 164
+helptext = #a4a4a4
+fadedtext = #949494
+errortext = red
+
+[Syntax]
+background = #363636
+text = default
+link = blue
+headertext = green
+headertag = green, 160
+emphasis = orange
+dialog = blue
+altdialog = blue
+note = yellow
+hidden = faded
+shortcode = green
+keyword = red
+tag = green
+value = blue
+optional = green
+spellcheckline = red
+errorline = green
+replacetag = green
+modifier = green
+texthighlight = yellow, 72
diff --git a/novelwriter/assets/themes/default_light.conf b/novelwriter/assets/themes/default_light.conf
index f236f404..c4496d13 100644
--- a/novelwriter/assets/themes/default_light.conf
+++ b/novelwriter/assets/themes/default_light.conf
@@ -8,43 +8,65 @@ url = https://github.com/vkbo/novelWriter
license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
-[Icons]
-default = 72, 72, 72
-faded = 108, 108, 108
-red = 240, 40, 41
-orange = 245, 135, 31
-yellow = 234, 183, 0
-green = 113, 140, 0
-aqua = 62, 153, 159
-blue = 66, 113, 174
-purple = 137, 89, 168
+[Base]
+default = #303030
+faded = #6c6c6c
+red = #a62a2d
+orange = #b36829
+yellow = #a68542
+green = #296629
+aqua = #269999
+blue = #3a70a6
+purple = #b35ab3
[Project]
-root = 66, 113, 174
-folder = 234, 183, 0
-file = 72, 72, 72
-title = 113, 140, 0
-chapter = 240, 40, 41
-scene = 66, 113, 174
-note = 234, 183, 0
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
[Palette]
-window = 239, 239, 239
-windowtext = 0, 0, 0
-base = 255, 255, 255
-alternatebase = 224, 224, 224
-text = 0, 0, 0
-tooltipbase = 255, 255, 220
-tooltiptext = 0, 0, 0
-button = 239, 239, 239
-buttontext = 0, 0, 0
-brighttext = 255, 255, 255
-highlight = 48, 135, 198
-highlightedtext = 255, 255, 255
-link = 66, 113, 174
-linkvisited = 66, 113, 174
+window = #efefef
+windowtext = #000000
+base = #ffffff
+alternatebase = #e0e0e0
+text = #000000
+tooltipbase = #ffffdc
+tooltiptext = #000000
+button = #efefef
+buttontext = #000000
+brighttext = #ffffff
+highlight = #3087c6
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
[GUI]
-helptext = 92, 92, 92
-fadedtext = 108, 108, 108
-errortext = 255, 92, 92
+helptext = #5c5c5c
+fadedtext = #6c6c6c
+errortext = red
+
+[Syntax]
+background = #ffffff
+text = #000000
+link = blue
+headertext = green
+headertag = green, 160
+emphasis = orange
+dialog = blue
+altdialog = blue
+note = yellow
+hidden = faded
+shortcode = green
+keyword = red
+tag = green
+value = blue
+optional = green
+spellcheckline = red
+errorline = green
+replacetag = green
+modifier = green
+texthighlight = #c8c80060
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 437531a3..4a53f453 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -244,7 +244,7 @@ class GuiTheme:
entries = len(data)
if entries == 2:
# Assume name, alpha
- color = self._qColors.get(data[0].strip(), default)
+ color = QColor(self._qColors.get(data[0].strip(), default))
color.setAlpha(checkInt(data[1], 255))
return color
else:
From 3258b8806f74ff89cd421bbea3be0f67350cdac4 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 19:15:33 +0200
Subject: [PATCH 07/96] Merge and update the tomorrow themes
---
novelwriter/assets/syntax/tomorrow.conf | 49 ----------
novelwriter/assets/syntax/tomorrow_night.conf | 49 ----------
.../assets/syntax/tomorrow_night_blue.conf | 49 ----------
.../assets/syntax/tomorrow_night_bright.conf | 49 ----------
.../syntax/tomorrow_night_eighties.conf | 49 ----------
novelwriter/assets/themes/default_dark.conf | 40 ++++----
novelwriter/assets/themes/tomorrow.conf | 91 +++++++++++++++++++
novelwriter/assets/themes/tomorrow_night.conf | 90 ++++++++++++++++++
.../assets/themes/tomorrow_night_blue.conf | 91 +++++++++++++++++++
.../assets/themes/tomorrow_night_bright.conf | 91 +++++++++++++++++++
.../themes/tomorrow_night_eighties.conf | 91 +++++++++++++++++++
11 files changed, 474 insertions(+), 265 deletions(-)
delete mode 100644 novelwriter/assets/syntax/tomorrow.conf
delete mode 100644 novelwriter/assets/syntax/tomorrow_night.conf
delete mode 100644 novelwriter/assets/syntax/tomorrow_night_blue.conf
delete mode 100644 novelwriter/assets/syntax/tomorrow_night_bright.conf
delete mode 100644 novelwriter/assets/syntax/tomorrow_night_eighties.conf
create mode 100644 novelwriter/assets/themes/tomorrow.conf
create mode 100644 novelwriter/assets/themes/tomorrow_night.conf
create mode 100644 novelwriter/assets/themes/tomorrow_night_blue.conf
create mode 100644 novelwriter/assets/themes/tomorrow_night_bright.conf
create mode 100644 novelwriter/assets/themes/tomorrow_night_eighties.conf
diff --git a/novelwriter/assets/syntax/tomorrow.conf b/novelwriter/assets/syntax/tomorrow.conf
deleted file mode 100644
index d3d2b8e4..00000000
--- a/novelwriter/assets/syntax/tomorrow.conf
+++ /dev/null
@@ -1,49 +0,0 @@
-##
-# Theme: Tomorrow
-# Source: https://github.com/chriskempson/tomorrow-theme
-# Credit: Chris Kempson
-##
-# Colours:
-# Background = ffffff : 255, 255, 255
-# Current Line = efefef : 239, 239, 239
-# Selection = d6d6d6 : 214, 214, 214
-# Foreground = 4d4d4c : 77, 77, 76
-# Comment = 8e908c : 142, 144, 140
-# Red = c82829 : 240, 40, 41
-# Orange = f5871f : 245, 135, 31
-# Yellow = eab700 : 234, 183, 0
-# Green = 718c00 : 113, 140, 0
-# Aqua = 3e999f : 62, 153, 159
-# Blue = 4271ae : 66, 113, 174
-# Purple = 8959a8 : 137, 89, 168
-##
-
-[Main]
-name = Tomorrow
-author = Veronica Berglyd Olsen (adaptation)
-credit = Chris Kempson (color theme)
-url = https://github.com/chriskempson/tomorrow-theme
-license = MIT License
-licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
-
-[Syntax]
-background = 255, 255, 255
-text = 77, 77, 76
-link = 66, 113, 174
-headertext = 66, 113, 174
-headertag = 66, 113, 174, 160
-emphasis = 245, 135, 31
-dialog = 113, 140, 0
-altdialog = 234, 183, 0
-note = 115, 90, 0
-hidden = 142, 144, 140
-shortcode = 66, 113, 174
-keyword = 240, 40, 41
-tag = 137, 89, 168
-value = 234, 183, 0
-optional = 66, 113, 174
-spellcheckline = 240, 40, 41
-errorline = 113, 140, 0
-replacetag = 62, 153, 159
-modifier = 245, 135, 31
-texthighlight = 234, 183, 0, 96
diff --git a/novelwriter/assets/syntax/tomorrow_night.conf b/novelwriter/assets/syntax/tomorrow_night.conf
deleted file mode 100644
index 15257945..00000000
--- a/novelwriter/assets/syntax/tomorrow_night.conf
+++ /dev/null
@@ -1,49 +0,0 @@
-##
-# Theme: Tomorrow Night
-# Source: https://github.com/chriskempson/tomorrow-theme
-# Credit: Chris Kempson
-##
-# Colours:
-# Background = 1d1f21 : 29, 31, 33
-# Current Line = 282a2e : 40, 42, 46
-# Selection = 373b41 : 55, 59. 65
-# Foreground = c5c8c6 : 197, 200, 198
-# Comment = 969896 : 150, 152, 150
-# Red = cc6666 : 204, 102, 102
-# Orange = de935f : 222, 147, 95
-# Yellow = f0c674 : 240, 198, 116
-# Green = b5bd68 : 181, 189, 104
-# Aqua = 8abeb7 : 138, 190, 183
-# Blue = 81a2be : 129, 162, 190
-# Purple = b294bb : 178, 148, 187
-##
-
-[Main]
-name = Tomorrow Night
-author = Veronica Berglyd Olsen (adaptation)
-credit = Chris Kempson (color theme)
-url = https://github.com/chriskempson/tomorrow-theme
-license = MIT License
-licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
-
-[Syntax]
-background = 29, 31, 33
-text = 197, 200, 198
-link = 197, 200, 198, 160
-headertext = 129, 162, 190
-headertag = 94, 119, 139
-emphasis = 222, 147, 95
-dialog = 181, 189, 104
-altdialog = 240, 198, 116
-note = 240, 219, 178
-hidden = 150, 152, 150
-shortcode = 129, 162, 190
-keyword = 204, 102, 102
-tag = 178, 148, 187
-value = 240, 198, 116
-optional = 129, 162, 190
-spellcheckline = 204, 102, 102
-errorline = 181, 189, 104
-replacetag = 138, 190, 183
-modifier = 222, 147, 95
-texthighlight = 240, 198, 116, 96
diff --git a/novelwriter/assets/syntax/tomorrow_night_blue.conf b/novelwriter/assets/syntax/tomorrow_night_blue.conf
deleted file mode 100644
index 89067e4f..00000000
--- a/novelwriter/assets/syntax/tomorrow_night_blue.conf
+++ /dev/null
@@ -1,49 +0,0 @@
-##
-# Theme: Tomorrow Night Blue
-# Source: https://github.com/chriskempson/tomorrow-theme
-# Credit: Chris Kempson
-##
-# Colours:
-# Background = 002451 : 0, 36, 81
-# Current Line = 00346e : 0, 52, 110
-# Selection = 003f8e : 0, 63, 142
-# Foreground = ffffff : 255, 255, 255
-# Comment = 7285b7 : 114, 133, 183
-# Red = ff9da4 : 255, 157, 164
-# Orange = ffc58f : 255, 197, 143
-# Yellow = ffeead : 255, 238, 173
-# Green = d1f1a9 : 209, 241, 169
-# Aqua = 99ffff : 153, 255, 255
-# Blue = bbdaff : 187, 218, 255
-# Purple = ebbbff : 235, 187, 255
-##
-
-[Main]
-name = Tomorrow Night Blue
-author = Veronica Berglyd Olsen (adaptation)
-credit = Chris Kempson (color theme)
-url = https://github.com/chriskempson/tomorrow-theme
-license = MIT License
-licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
-
-[Syntax]
-background = 0, 36, 81
-text = 255, 255, 255
-link = 187, 218, 255
-headertext = 187, 218, 255
-headertag = 187, 218, 255, 160
-emphasis = 255, 197, 143
-dialog = 209, 241, 169
-altdialog = 255, 238, 173
-note = 255, 247, 214
-hidden = 114, 133, 183
-shortcode = 187, 218, 255
-keyword = 255, 157, 164
-tag = 235, 187, 255
-value = 255, 238, 173
-optional = 187, 218, 255
-spellcheckline = 255, 157, 164
-errorline = 209, 241, 169
-replacetag = 153, 255, 255
-modifier = 255, 197, 143
-texthighlight = 255, 238, 173, 96
diff --git a/novelwriter/assets/syntax/tomorrow_night_bright.conf b/novelwriter/assets/syntax/tomorrow_night_bright.conf
deleted file mode 100644
index 0f1fd055..00000000
--- a/novelwriter/assets/syntax/tomorrow_night_bright.conf
+++ /dev/null
@@ -1,49 +0,0 @@
-##
-# Theme: Tomorrow Night Bright
-# Source: https://github.com/chriskempson/tomorrow-theme
-# Credit: Chris Kempson
-##
-# Colours:
-# Background = 000000 : 0, 0, 0
-# Current Line = 2a2a2a : 42, 42, 42
-# Selection = 424242 : 66, 66, 66
-# Foreground = eaeaea : 234, 234, 234
-# Comment = 969896 : 150, 152, 150
-# Red = d54e53 : 213, 78, 83
-# Orange = e78c45 : 231, 140, 69
-# Yellow = e7c547 : 231, 197, 71
-# Green = b9ca4a : 185, 202, 74
-# Aqua = 70c0b1 : 112, 192, 177
-# Blue = 7aa6da : 122, 166, 218
-# Purple = c397d8 : 195, 151, 216
-##
-
-[Main]
-name = Tomorrow Night Bright
-author = Veronica Berglyd Olsen (adaptation)
-credit = Chris Kempson (color theme)
-url = https://github.com/chriskempson/tomorrow-theme
-license = MIT License
-licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
-
-[Syntax]
-background = 0, 0, 0
-text = 234, 234, 234
-link = 122, 166, 218
-headertext = 122, 166, 218
-headertag = 122, 166, 218, 160
-emphasis = 231, 140, 69
-dialog = 185, 202, 74
-altdialog = 231, 197, 71
-note = 231, 214, 150
-hidden = 150, 152, 150
-shortcode = 122, 166, 218
-keyword = 213, 78, 83
-tag = 195, 151, 216
-value = 231, 197, 71
-optional = 122, 166, 218
-spellcheckline = 213, 78, 83
-errorline = 185, 202, 74
-replacetag = 112, 192, 177
-modifier = 231, 140, 69
-texthighlight = 231, 197, 71, 128
diff --git a/novelwriter/assets/syntax/tomorrow_night_eighties.conf b/novelwriter/assets/syntax/tomorrow_night_eighties.conf
deleted file mode 100644
index a638a871..00000000
--- a/novelwriter/assets/syntax/tomorrow_night_eighties.conf
+++ /dev/null
@@ -1,49 +0,0 @@
-##
-# Theme: Tomorrow Night Eighties
-# Source: https://github.com/chriskempson/tomorrow-theme
-# Credit: Chris Kempson
-##
-# Colours:
-# Background = 2d2d2d : 45, 45, 45
-# Current Line = 393939 : 57, 57, 57
-# Selection = 515151 : 81, 81, 81
-# Foreground = cccccc : 204, 204, 204
-# Comment = 999999 : 153, 153, 153
-# Red = f2777a : 242, 119, 122
-# Orange = f99157 : 249, 145, 57
-# Yellow = ffcc66 : 255, 204, 102
-# Green = 99cc99 : 153, 204, 153
-# Aqua = 66cccc : 102, 204, 204
-# Blue = 6699cc : 102, 153, 204
-# Purple = cc99cc : 204, 153, 204
-##
-
-[Main]
-name = Tomorrow Night Eighties
-author = Veronica Berglyd Olsen (adaptation)
-credit = Chris Kempson (color theme)
-url = https://github.com/chriskempson/tomorrow-theme
-license = MIT License
-licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
-
-[Syntax]
-background = 45, 45, 45
-text = 204, 204, 204
-link = 102, 153, 204
-headertext = 102, 153, 204
-headertag = 102, 153, 204, 160
-emphasis = 249, 145, 57
-dialog = 153, 204, 153
-altdialog = 255, 204, 102
-note = 255, 230, 179
-hidden = 153, 153, 153
-shortcode = 102, 153, 204
-keyword = 242, 119, 122
-tag = 204, 153, 204
-value = 255, 204, 102
-optional = 102, 153, 204
-spellcheckline = 242, 119, 122
-errorline = 153, 204, 153
-replacetag = 102, 204, 204
-modifier = 249, 145, 57
-texthighlight = 255, 204, 102, 96
diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf
index a8e6a3de..7b0c3922 100644
--- a/novelwriter/assets/themes/default_dark.conf
+++ b/novelwriter/assets/themes/default_dark.conf
@@ -50,23 +50,23 @@ fadedtext = #949494
errortext = red
[Syntax]
-background = #363636
-text = default
-link = blue
-headertext = green
-headertag = green, 160
-emphasis = orange
-dialog = blue
-altdialog = blue
-note = yellow
-hidden = faded
-shortcode = green
-keyword = red
-tag = green
-value = blue
-optional = green
-spellcheckline = red
-errorline = green
-replacetag = green
-modifier = green
-texthighlight = yellow, 72
+background = #363636
+text = default
+link = blue
+headertext = green
+headertag = green, 160
+emphasis = orange
+dialog = blue
+altdialog = blue
+note = yellow
+hidden = faded
+shortcode = green
+keyword = red
+tag = green
+value = blue
+optional = green
+spellcheckline = red
+errorline = green
+replacetag = green
+modifier = green
+texthighlight = yellow, 72
diff --git a/novelwriter/assets/themes/tomorrow.conf b/novelwriter/assets/themes/tomorrow.conf
new file mode 100644
index 00000000..ab49754b
--- /dev/null
+++ b/novelwriter/assets/themes/tomorrow.conf
@@ -0,0 +1,91 @@
+[Main]
+name = Tomorrow
+mode = light
+author = Veronica Berglyd Olsen (adaptation)
+credit = Chris Kempson (color theme)
+url = https://github.com/chriskempson/tomorrow-theme
+license = MIT License
+licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
+
+##
+# Theme: Tomorrow
+# Source: https://github.com/chriskempson/tomorrow-theme
+# Credit: Chris Kempson
+##
+# Colours:
+# Background = #ffffff
+# Current Line = #efefef
+# Selection = #d6d6d6
+# Foreground = #4d4d4c
+# Comment = #8e908c
+# Red = #c82829
+# Orange = #f5871f
+# Yellow = #eab700
+# Green = #718c00
+# Aqua = #3e999f
+# Blue = #4271ae
+# Purple = #8959a8
+##
+
+[Base]
+default = #484848
+faded = #6c6c6c
+red = #c82829
+orange = #f5871f
+yellow = #eab700
+green = #718c00
+aqua = #3e999f
+blue = #4271ae
+purple = #8959a8
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+
+[Palette]
+window = #efefef
+windowtext = #000000
+base = #ffffff
+alternatebase = #d6d6d6
+text = #000000
+tooltipbase = #ffffdc
+tooltiptext = #000000
+button = #ffffff
+buttontext = #000000
+brighttext = #ffffff
+highlight = blue
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+
+[GUI]
+helptext = #5c5c5c
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = #ffffff
+text = #4d4d4c
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #b38c00
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = yellow
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = aqua
+modifier = orange
+texthighlight = yellow, 96
diff --git a/novelwriter/assets/themes/tomorrow_night.conf b/novelwriter/assets/themes/tomorrow_night.conf
new file mode 100644
index 00000000..16290bf0
--- /dev/null
+++ b/novelwriter/assets/themes/tomorrow_night.conf
@@ -0,0 +1,90 @@
+[Main]
+name = Tomorrow Night
+mode = dark
+author = Veronica Berglyd Olsen (adaptation)
+credit = Chris Kempson (color theme)
+url = https://github.com/chriskempson/tomorrow-theme
+license = MIT License
+licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
+##
+# Theme: Tomorrow Night
+# Source: https://github.com/chriskempson/tomorrow-theme
+# Credit: Chris Kempson
+##
+# Colours:
+# Background = #1d1f21
+# Current Line = #282a2e
+# Selection = #373b41
+# Foreground = #c5c8c6
+# Comment = #969896
+# Red = #cc6666
+# Orange = #de935f
+# Yellow = #f0c674
+# Green = #b5bd68
+# Aqua = #8abeb7
+# Blue = #81a2be
+# Purple = #b294bb
+##
+
+[Base]
+default = #c5c8c6
+faded = #969896
+red = #cc6666
+orange = #de935f
+yellow = #f0c674
+green = #b5bd68
+aqua = #8abeb7
+blue = #81a2be
+purple = #b294bb
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+
+[Palette]
+window = #282a2e
+windowtext = default
+base = #1d1f21
+alternatebase = #373b41
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #1d1f21
+buttontext = default
+brighttext = #1d1f21
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+
+[GUI]
+helptext = #a4a4a4
+fadedtext = #949494
+errortext = red
+
+[Syntax]
+background = #1d1f21
+text = default
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #f0dbb2
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = yellow
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = aqua
+modifier = orange
+texthighlight = yellow, 96
diff --git a/novelwriter/assets/themes/tomorrow_night_blue.conf b/novelwriter/assets/themes/tomorrow_night_blue.conf
new file mode 100644
index 00000000..93b6592e
--- /dev/null
+++ b/novelwriter/assets/themes/tomorrow_night_blue.conf
@@ -0,0 +1,91 @@
+[Main]
+name = Tomorrow Night Blue
+mode = dark
+author = Veronica Berglyd Olsen (adaptation)
+credit = Chris Kempson (color theme)
+url = https://github.com/chriskempson/tomorrow-theme
+license = MIT License
+licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
+
+##
+# Theme: Tomorrow Night Blue
+# Source: https://github.com/chriskempson/tomorrow-theme
+# Credit: Chris Kempson
+##
+# Colours:
+# Background = #002451
+# Current Line = #00346e
+# Selection = #003f8e
+# Foreground = #ffffff
+# Comment = #7285b7
+# Red = #ff9da4
+# Orange = #ffc58f
+# Yellow = #ffeead
+# Green = #d1f1a9
+# Aqua = #99ffff
+# Blue = #bbdaff
+# Purple = #ebbbff
+##
+
+[Base]
+default = #ffffff
+faded = #7285b7
+red = #ff9da4
+orange = #ffc58f
+yellow = #ffeead
+green = #d1f1a9
+aqua = #99ffff
+blue = #bbdaff
+purple = #ebbbff
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+
+[Palette]
+window = #00346e
+windowtext = default
+base = #002451
+alternatebase = #003f8e
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #002451
+buttontext = default
+brighttext = #002451
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+
+[GUI]
+helptext = #a4a4a4
+fadedtext = #949494
+errortext = red
+
+[Syntax]
+background = #002451
+text = default
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #fff4cc
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = yellow
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = aqua
+modifier = orange
+texthighlight = yellow, 96
diff --git a/novelwriter/assets/themes/tomorrow_night_bright.conf b/novelwriter/assets/themes/tomorrow_night_bright.conf
new file mode 100644
index 00000000..de76e694
--- /dev/null
+++ b/novelwriter/assets/themes/tomorrow_night_bright.conf
@@ -0,0 +1,91 @@
+[Main]
+name = Tomorrow Night Bright
+mode = dark
+author = Veronica Berglyd Olsen (adaptation)
+credit = Chris Kempson (color theme)
+url = https://github.com/chriskempson/tomorrow-theme
+license = MIT License
+licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
+
+##
+# Theme: Tomorrow Night Bright
+# Source: https://github.com/chriskempson/tomorrow-theme
+# Credit: Chris Kempson
+##
+# Colours:
+# Background = #000000
+# Current Line = #2a2a2a
+# Selection = #424242
+# Foreground = #eaeaea
+# Comment = #969896
+# Red = #d54e53
+# Orange = #e78c45
+# Yellow = #e7c547
+# Green = #b9ca4a
+# Aqua = #70c0b1
+# Blue = #7aa6da
+# Purple = #c397d8
+##
+
+[Base]
+default = #eaeaea
+faded = #969896
+red = #d54e53
+orange = #e78c45
+yellow = #e7c547
+green = #b9ca4a
+aqua = #70c0b1
+blue = #7aa6da
+purple = #c397d8
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+
+[Palette]
+window = #2a2a2a
+windowtext = default
+base = #000000
+alternatebase = #424242
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #000000
+buttontext = default
+brighttext = #000000
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+
+[GUI]
+helptext = #a4a4a4
+fadedtext = #949494
+errortext = red
+
+[Syntax]
+background = #000000
+text = default
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #e7d696
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = yellow
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = aqua
+modifier = orange
+texthighlight = yellow, 96
diff --git a/novelwriter/assets/themes/tomorrow_night_eighties.conf b/novelwriter/assets/themes/tomorrow_night_eighties.conf
new file mode 100644
index 00000000..e3e8bbec
--- /dev/null
+++ b/novelwriter/assets/themes/tomorrow_night_eighties.conf
@@ -0,0 +1,91 @@
+[Main]
+name = Tomorrow Night Eighties
+mode = dark
+author = Veronica Berglyd Olsen (adaptation)
+credit = Chris Kempson (color theme)
+url = https://github.com/chriskempson/tomorrow-theme
+license = MIT License
+licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
+
+##
+# Theme: Tomorrow Night Eighties
+# Source: https://github.com/chriskempson/tomorrow-theme
+# Credit: Chris Kempson
+##
+# Colours:
+# Background = #2d2d2d
+# Current Line = #393939
+# Selection = #515151
+# Foreground = #cccccc
+# Comment = #999999
+# Red = #f2777a
+# Orange = #f99157
+# Yellow = #ffcc66
+# Green = #99cc99
+# Aqua = #66cccc
+# Blue = #6699cc
+# Purple = #cc99cc
+##
+
+[Base]
+default = #cccccc
+faded = #999999
+red = #f2777a
+orange = #f99157
+yellow = #ffcc66
+green = #99cc99
+aqua = #66cccc
+blue = #6699cc
+purple = #cc99cc
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+
+[Palette]
+window = #393939
+windowtext = default
+base = #2d2d2d
+alternatebase = #515151
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #2d2d2d
+buttontext = default
+brighttext = #2d2d2d
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+
+[GUI]
+helptext = #a4a4a4
+fadedtext = #949494
+errortext = red
+
+[Syntax]
+background = #2d2d2d
+text = default
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #ffe6b3
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = yellow
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = aqua
+modifier = orange
+texthighlight = yellow, 96
From eed5cd187738ce43a30a6e0b2b73b711b68deb41 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 20:02:00 +0200
Subject: [PATCH 08/96] Merge and update the owl and tango themes
---
novelwriter/assets/syntax/light_owl.conf | 49 ------------
novelwriter/assets/syntax/night_owl.conf | 49 ------------
novelwriter/assets/syntax/tango.conf | 39 ----------
novelwriter/assets/themes/light_owl.conf | 91 ++++++++++++++++++++++
novelwriter/assets/themes/night_owl.conf | 91 ++++++++++++++++++++++
novelwriter/assets/themes/tango_dark.conf | 81 +++++++++++++++++++
novelwriter/assets/themes/tango_light.conf | 81 +++++++++++++++++++
7 files changed, 344 insertions(+), 137 deletions(-)
delete mode 100644 novelwriter/assets/syntax/light_owl.conf
delete mode 100644 novelwriter/assets/syntax/night_owl.conf
delete mode 100644 novelwriter/assets/syntax/tango.conf
create mode 100644 novelwriter/assets/themes/light_owl.conf
create mode 100644 novelwriter/assets/themes/night_owl.conf
create mode 100644 novelwriter/assets/themes/tango_dark.conf
create mode 100644 novelwriter/assets/themes/tango_light.conf
diff --git a/novelwriter/assets/syntax/light_owl.conf b/novelwriter/assets/syntax/light_owl.conf
deleted file mode 100644
index ce65e704..00000000
--- a/novelwriter/assets/syntax/light_owl.conf
+++ /dev/null
@@ -1,49 +0,0 @@
-##
-# Theme: Light Owl
-# Source: https://github.com/sdras/night-owl-vscode-theme
-# Credit: Sarah Drasner
-##
-# Colours:
-# Background = fbfbfb : 251, 251, 251
-# Current Line = e0e0e0 : 224, 224, 224
-# Selection = e0e0e0 : 224, 224, 224
-# Foreground = 403f52 : 64, 63, 82
-# Comment = 989fb1 : 152, 159, 177
-# Red = de3d3a : 222, 61, 58
-# Orange = e0af05 : 224, 175, 5
-# Yellow = daaa01 : 218, 170, 1
-# Green = 08916a : 8, 145, 106
-# Aqua = 2aa298 : 42, 162, 152
-# Blue = 288ed7 : 40, 142, 215
-# Purple = 964ac1 : 150, 74, 193
-##
-
-[Main]
-name = Light Owl
-author = Veronica Berglyd Olsen (adaptation)
-credit = Sarah Drasner (color theme)
-url = https://github.com/sdras/night-owl-vscode-theme
-license = MIT License
-licenseurl = https://github.com/sdras/night-owl-vscode-theme/blob/master/LICENSE
-
-[Syntax]
-background = 251, 251, 251
-text = 64, 63, 82
-link = 40, 142, 215
-headertext = 40, 142, 215
-headertag = 40, 142, 215, 160
-emphasis = 224, 175, 5
-dialog = 8, 145, 106
-altdialog = 218, 170, 1
-note = 128, 118, 75
-hidden = 152, 159, 177
-shortcode = 40, 142, 215
-keyword = 222, 61, 58
-tag = 150, 74, 193
-value = 224, 175, 5
-optional = 40, 142, 215
-spellcheckline = 222, 61, 58
-errorline = 8, 145, 106
-replacetag = 42, 162, 152
-modifier = 8, 145, 106
-texthighlight = 218, 170, 1, 96
diff --git a/novelwriter/assets/syntax/night_owl.conf b/novelwriter/assets/syntax/night_owl.conf
deleted file mode 100644
index 337cdfb8..00000000
--- a/novelwriter/assets/syntax/night_owl.conf
+++ /dev/null
@@ -1,49 +0,0 @@
-##
-# Theme: Night Owl
-# Source: https://github.com/sdras/night-owl-vscode-theme
-# Credit: Sarah Drasner
-##
-# Colours:
-# Background = 011627 : 1, 22, 39
-# Current Line = 32374d : 50, 55, 77
-# Selection = 32374d : 50, 55, 77
-# Foreground = d6deeb : 214, 222, 235
-# Comment = 637777 : 99, 119, 119
-# Red = f78c6c : 247, 140, 108
-# Orange = ecc48d : 236, 196, 141
-# Yellow = ffeb95 : 255, 235, 149
-# Green = addb67 : 173, 219, 103
-# Aqua = 7fdbca : 127, 219, 202
-# Blue = 82aaff : 130, 170, 255
-# Purple = c792ea : 199, 146, 234
-##
-
-[Main]
-name = Night Owl
-author = Veronica Berglyd Olsen (adaptation)
-credit = Sarah Drasner (color theme)
-url = https://github.com/sdras/night-owl-vscode-theme
-license = MIT License
-licenseurl = https://github.com/sdras/night-owl-vscode-theme/blob/master/LICENSE
-
-[Syntax]
-background = 1, 22, 39
-text = 214, 222, 235
-link = 130, 170, 255
-headertext = 130, 170, 255
-headertag = 130, 170, 255, 160
-emphasis = 236, 196, 141
-dialog = 173, 219, 103
-altdialog = 255, 235, 149
-note = 255, 249, 202
-hidden = 99, 119, 119
-shortcode = 130, 170, 255
-keyword = 247, 140, 108
-tag = 199, 146, 234
-value = 255, 235, 149
-optional = 130, 170, 255
-spellcheckline = 247, 140, 108
-errorline = 173, 219, 103
-replacetag = 127, 219, 202
-modifier = 173, 219, 103
-texthighlight = 255, 235, 149, 96
diff --git a/novelwriter/assets/syntax/tango.conf b/novelwriter/assets/syntax/tango.conf
deleted file mode 100644
index 7c5e9daa..00000000
--- a/novelwriter/assets/syntax/tango.conf
+++ /dev/null
@@ -1,39 +0,0 @@
-[Main]
-name = Tango
-author = Veronica Berglyd Olsen (adaptation)
-
-##
-# Colours:
-# Background = 30302f : 48, 48, 47
-# Foreground = eeeeec : 238, 238, 236
-# Comment = 8f8f8d : 143, 143, 141
-# Red = ef2929 : 239, 41, 41
-# Orange = c4a000 : 196, 160, 0
-# Yellow = fce94f : 252, 233, 79
-# Green = 8ae234 : 138, 226, 52
-# Cyan = 34e2e2 : 52, 226, 226
-# Blue = 3465a4 : 114, 159, 207
-# Purple = ad7fa8 : 173, 127, 168
-##
-
-[Syntax]
-background = 48, 48, 47
-text = 238, 238, 236
-link = 114, 159, 207
-headertext = 114, 159, 207
-headertag = 114, 159, 207, 160
-emphasis = 196, 160, 0
-dialog = 138, 226, 52
-altdialog = 252, 233, 79
-note = 252, 242, 164
-hidden = 143, 143, 141
-shortcode = 114, 159, 207
-keyword = 239, 41, 41
-tag = 173, 127, 168
-value = 239, 41, 41
-optional = 114, 159, 207
-spellcheckline = 239, 41, 41
-errorline = 138, 226, 52
-replacetag = 52, 226, 226
-modifier = 114, 159, 207
-texthighlight = 252, 233, 79, 96
diff --git a/novelwriter/assets/themes/light_owl.conf b/novelwriter/assets/themes/light_owl.conf
new file mode 100644
index 00000000..d91cf0ba
--- /dev/null
+++ b/novelwriter/assets/themes/light_owl.conf
@@ -0,0 +1,91 @@
+[Main]
+name = Light Owl
+mode = light
+author = Veronica Berglyd Olsen (adaptation)
+credit = Sarah Drasner (color theme)
+url = https://github.com/sdras/night-owl-vscode-theme
+license = MIT License
+licenseurl = https://github.com/sdras/night-owl-vscode-theme/blob/master/LICENSE
+
+##
+# Theme: Light Owl
+# Source: https://github.com/sdras/night-owl-vscode-theme
+# Credit: Sarah Drasner
+##
+# Colours:
+# Background = #fbfbfb
+# Current Line = #e0e0e0
+# Selection = #e0e0e0
+# Foreground = #403f52
+# Comment = #989fb1
+# Red = #de3d3a
+# Orange = #e0af05
+# Yellow = #daaa01
+# Green = #08916a
+# Aqua = #2aa298
+# Blue = #288ed7
+# Purple = #964ac1
+##
+
+[Base]
+default = #403f52
+faded = #989fb1
+red = #de3d3a
+orange = #e0af05
+yellow = #daaa01
+green = #08916a
+aqua = #2aa298
+blue = #288ed7
+purple = #964ac1
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+
+[Palette]
+window = #e0e0e0
+windowtext = default
+base = #fbfbfb
+alternatebase = #e0e0e0
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #e0e0e0
+buttontext = default
+brighttext = #e0e0e0
+highlight = #3087c6
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+
+[GUI]
+helptext = blue
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = #fbfbfb
+text = default
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #80764b
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = orange
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = aqua
+modifier = green
+texthighlight = yellow, 96
diff --git a/novelwriter/assets/themes/night_owl.conf b/novelwriter/assets/themes/night_owl.conf
new file mode 100644
index 00000000..a5f0a02b
--- /dev/null
+++ b/novelwriter/assets/themes/night_owl.conf
@@ -0,0 +1,91 @@
+[Main]
+name = Night Owl
+mode = dark
+author = Veronica Berglyd Olsen (adaptation)
+credit = Sarah Drasner (color theme)
+url = https://github.com/sdras/night-owl-vscode-theme
+license = MIT License
+licenseurl = https://github.com/sdras/night-owl-vscode-theme/blob/master/LICENSE
+
+##
+# Theme: Night Owl
+# Source: https://github.com/sdras/night-owl-vscode-theme
+# Credit: Sarah Drasner
+##
+# Colours:
+# Background = #011627
+# Current Line = #32374d
+# Selection = #32374d
+# Foreground = #d6deeb
+# Comment = #637777
+# Red = #f78c6c
+# Orange = #ecc48d
+# Yellow = #ffeb95
+# Green = #addb67
+# Aqua = #7fdbca
+# Blue = #82aaff
+# Purple = #c792ea
+##
+
+[Base]
+default = #d6deeb
+faded = #637777
+red = #f78c6c
+orange = #ecc48d
+yellow = #addb67
+green = #addb67
+aqua = #7fdbca
+blue = #82aaff
+purple = #c792ea
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+
+[Palette]
+window = #32374d
+windowtext = default
+base = #011627
+alternatebase = #32374d
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #32374d
+buttontext = default
+brighttext = #32374d
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+
+[GUI]
+helptext = blue
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = #011627
+text = default
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #fff9ca
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = orange
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = aqua
+modifier = green
+texthighlight = yellow, 96
diff --git a/novelwriter/assets/themes/tango_dark.conf b/novelwriter/assets/themes/tango_dark.conf
new file mode 100644
index 00000000..873e7f11
--- /dev/null
+++ b/novelwriter/assets/themes/tango_dark.conf
@@ -0,0 +1,81 @@
+[Main]
+name = Tango Dark
+mode = dark
+author = Veronica Berglyd Olsen (adaptation)
+
+##
+# Colours:
+# Background = 30302f : 48, 48, 47
+# Foreground = eeeeec : 238, 238, 236
+# Comment = 8f8f8d : 143, 143, 141
+# Red = ef2929 : 239, 41, 41
+# Orange = c4a000 : 196, 160, 0
+# Yellow = fce94f : 252, 233, 79
+# Green = 8ae234 : 138, 226, 52
+# Cyan = 34e2e2 : 52, 226, 226
+# Blue = 3465a4 : 114, 159, 207
+# Purple = ad7fa8 : 173, 127, 168
+##
+
+[Base]
+default = #eeeeec
+faded = #babdb6
+red = #ef2929
+orange = #fcaf3e
+yellow = #fce94f
+green = #8ae234
+aqua = #34e2e2
+blue = #729fcf
+purple = #ad7fa8
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+
+[Palette]
+window = #555753
+windowtext = default
+base = #2e3436
+alternatebase = #888a85
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #555753
+buttontext = default
+brighttext = #555753
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+
+[GUI]
+helptext = #e9b96e
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = #2e3436
+text = default
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #fcf2a4
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = red
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = blue
+texthighlight = yellow, 96
diff --git a/novelwriter/assets/themes/tango_light.conf b/novelwriter/assets/themes/tango_light.conf
new file mode 100644
index 00000000..6ba5566a
--- /dev/null
+++ b/novelwriter/assets/themes/tango_light.conf
@@ -0,0 +1,81 @@
+[Main]
+name = Tango Light
+mode = light
+author = Veronica Berglyd Olsen (adaptation)
+
+##
+# Colours:
+# Background = #eeeeec
+# Foreground = #2e3436
+# Comment = 8f8f8d : 143, 143, 141
+# Red = #a40000
+# Orange = c4a000 : 196, 160, 0
+# Yellow = fce94f : 252, 233, 79
+# Green = 8ae234 : 138, 226, 52
+# Cyan = 34e2e2 : 52, 226, 226
+# Blue = 3465a4 : 114, 159, 207
+# Purple = ad7fa8 : 173, 127, 168
+##
+
+[Base]
+default = #2e3436
+faded = #888a85
+red = #a40000
+orange = #ce5c00
+yellow = #c4a000
+green = #4e9a06
+aqua = #069a9a
+blue = #204a87
+purple = #5c3566
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+
+[Palette]
+window = #d3d7cf
+windowtext = default
+base = #eeeeec
+alternatebase = #e0e0e0
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #d3d7cf
+buttontext = default
+brighttext = #d3d7cf
+highlight = #3087c6
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+
+[GUI]
+helptext = #8f5902
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = #eeeeec
+text = default
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #a78800
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = red
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = blue
+texthighlight = yellow, 96
From d0f11a25956b94717c42f1eae67e7b6e1b7eaea9 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 20:05:52 +0200
Subject: [PATCH 09/96] Fix a few inconsistencies
---
novelwriter/assets/themes/default.conf | 3 --
novelwriter/assets/themes/default_light.conf | 44 +++++++++----------
novelwriter/assets/themes/light_owl.conf | 40 ++++++++---------
novelwriter/assets/themes/night_owl.conf | 40 ++++++++---------
novelwriter/assets/themes/tango_dark.conf | 40 ++++++++---------
novelwriter/assets/themes/tango_light.conf | 40 ++++++++---------
novelwriter/assets/themes/tomorrow.conf | 8 ++--
novelwriter/assets/themes/tomorrow_night.conf | 1 +
8 files changed, 107 insertions(+), 109 deletions(-)
delete mode 100644 novelwriter/assets/themes/default.conf
diff --git a/novelwriter/assets/themes/default.conf b/novelwriter/assets/themes/default.conf
deleted file mode 100644
index 9cd9cd5b..00000000
--- a/novelwriter/assets/themes/default.conf
+++ /dev/null
@@ -1,3 +0,0 @@
-[Main]
-name = Qt Default Theme
-description = Qt standard colours
diff --git a/novelwriter/assets/themes/default_light.conf b/novelwriter/assets/themes/default_light.conf
index c4496d13..0c309a3c 100644
--- a/novelwriter/assets/themes/default_light.conf
+++ b/novelwriter/assets/themes/default_light.conf
@@ -34,8 +34,8 @@ windowtext = #000000
base = #ffffff
alternatebase = #e0e0e0
text = #000000
-tooltipbase = #ffffdc
-tooltiptext = #000000
+tooltipbase = #ffffc0
+tooltiptext = #15150d
button = #efefef
buttontext = #000000
brighttext = #ffffff
@@ -50,23 +50,23 @@ fadedtext = #6c6c6c
errortext = red
[Syntax]
-background = #ffffff
-text = #000000
-link = blue
-headertext = green
-headertag = green, 160
-emphasis = orange
-dialog = blue
-altdialog = blue
-note = yellow
-hidden = faded
-shortcode = green
-keyword = red
-tag = green
-value = blue
-optional = green
-spellcheckline = red
-errorline = green
-replacetag = green
-modifier = green
-texthighlight = #c8c80060
+background = #ffffff
+text = #000000
+link = blue
+headertext = green
+headertag = green, 160
+emphasis = orange
+dialog = blue
+altdialog = blue
+note = yellow
+hidden = faded
+shortcode = green
+keyword = red
+tag = green
+value = blue
+optional = green
+spellcheckline = red
+errorline = green
+replacetag = green
+modifier = green
+texthighlight = #c8c80060
diff --git a/novelwriter/assets/themes/light_owl.conf b/novelwriter/assets/themes/light_owl.conf
index d91cf0ba..f72b8d70 100644
--- a/novelwriter/assets/themes/light_owl.conf
+++ b/novelwriter/assets/themes/light_owl.conf
@@ -69,23 +69,23 @@ fadedtext = faded
errortext = red
[Syntax]
-background = #fbfbfb
-text = default
-link = blue
-headertext = blue
-headertag = blue, 160
-emphasis = orange
-dialog = green
-altdialog = yellow
-note = #80764b
-hidden = faded
-shortcode = blue
-keyword = red
-tag = purple
-value = orange
-optional = blue
-spellcheckline = red
-errorline = green
-replacetag = aqua
-modifier = green
-texthighlight = yellow, 96
+background = #fbfbfb
+text = default
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #80764b
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = orange
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = aqua
+modifier = green
+texthighlight = yellow, 96
diff --git a/novelwriter/assets/themes/night_owl.conf b/novelwriter/assets/themes/night_owl.conf
index a5f0a02b..0d176c45 100644
--- a/novelwriter/assets/themes/night_owl.conf
+++ b/novelwriter/assets/themes/night_owl.conf
@@ -69,23 +69,23 @@ fadedtext = faded
errortext = red
[Syntax]
-background = #011627
-text = default
-link = blue
-headertext = blue
-headertag = blue, 160
-emphasis = orange
-dialog = green
-altdialog = yellow
-note = #fff9ca
-hidden = faded
-shortcode = blue
-keyword = red
-tag = purple
-value = orange
-optional = blue
-spellcheckline = red
-errorline = green
-replacetag = aqua
-modifier = green
-texthighlight = yellow, 96
+background = #011627
+text = default
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #fff9ca
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = orange
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = aqua
+modifier = green
+texthighlight = yellow, 96
diff --git a/novelwriter/assets/themes/tango_dark.conf b/novelwriter/assets/themes/tango_dark.conf
index 873e7f11..ca0ba841 100644
--- a/novelwriter/assets/themes/tango_dark.conf
+++ b/novelwriter/assets/themes/tango_dark.conf
@@ -59,23 +59,23 @@ fadedtext = faded
errortext = red
[Syntax]
-background = #2e3436
-text = default
-link = blue
-headertext = blue
-headertag = blue, 160
-emphasis = orange
-dialog = green
-altdialog = yellow
-note = #fcf2a4
-hidden = faded
-shortcode = blue
-keyword = red
-tag = purple
-value = red
-optional = blue
-spellcheckline = red
-errorline = green
-replacetag = cyan
-modifier = blue
-texthighlight = yellow, 96
+background = #2e3436
+text = default
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #fcf2a4
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = red
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = blue
+texthighlight = yellow, 96
diff --git a/novelwriter/assets/themes/tango_light.conf b/novelwriter/assets/themes/tango_light.conf
index 6ba5566a..174b0571 100644
--- a/novelwriter/assets/themes/tango_light.conf
+++ b/novelwriter/assets/themes/tango_light.conf
@@ -59,23 +59,23 @@ fadedtext = faded
errortext = red
[Syntax]
-background = #eeeeec
-text = default
-link = blue
-headertext = blue
-headertag = blue, 160
-emphasis = orange
-dialog = green
-altdialog = yellow
-note = #a78800
-hidden = faded
-shortcode = blue
-keyword = red
-tag = purple
-value = red
-optional = blue
-spellcheckline = red
-errorline = green
-replacetag = cyan
-modifier = blue
-texthighlight = yellow, 96
+background = #eeeeec
+text = default
+link = blue
+headertext = blue
+headertag = blue, 160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #a78800
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = red
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = blue
+texthighlight = yellow, 96
diff --git a/novelwriter/assets/themes/tomorrow.conf b/novelwriter/assets/themes/tomorrow.conf
index ab49754b..2b734209 100644
--- a/novelwriter/assets/themes/tomorrow.conf
+++ b/novelwriter/assets/themes/tomorrow.conf
@@ -53,11 +53,11 @@ windowtext = #000000
base = #ffffff
alternatebase = #d6d6d6
text = #000000
-tooltipbase = #ffffdc
-tooltiptext = #000000
-button = #ffffff
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #efefef
buttontext = #000000
-brighttext = #ffffff
+brighttext = #efefef
highlight = blue
highlightedtext = #ffffff
link = blue
diff --git a/novelwriter/assets/themes/tomorrow_night.conf b/novelwriter/assets/themes/tomorrow_night.conf
index 16290bf0..27abd454 100644
--- a/novelwriter/assets/themes/tomorrow_night.conf
+++ b/novelwriter/assets/themes/tomorrow_night.conf
@@ -6,6 +6,7 @@ credit = Chris Kempson (color theme)
url = https://github.com/chriskempson/tomorrow-theme
license = MIT License
licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
+
##
# Theme: Tomorrow Night
# Source: https://github.com/chriskempson/tomorrow-theme
From 55e601cf9d04689682ccce645217af3ef20c00d3 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 20:18:19 +0200
Subject: [PATCH 10/96] Merge and update the grey themes
---
novelwriter/assets/syntax/grey_dark.conf | 29 ---------
novelwriter/assets/syntax/grey_light.conf | 29 ---------
novelwriter/assets/themes/default_dark.conf | 2 +-
novelwriter/assets/themes/grey_dark.conf | 71 +++++++++++++++++++++
novelwriter/assets/themes/grey_light.conf | 71 +++++++++++++++++++++
5 files changed, 143 insertions(+), 59 deletions(-)
delete mode 100644 novelwriter/assets/syntax/grey_dark.conf
delete mode 100644 novelwriter/assets/syntax/grey_light.conf
create mode 100644 novelwriter/assets/themes/grey_dark.conf
create mode 100644 novelwriter/assets/themes/grey_light.conf
diff --git a/novelwriter/assets/syntax/grey_dark.conf b/novelwriter/assets/syntax/grey_dark.conf
deleted file mode 100644
index cc36478c..00000000
--- a/novelwriter/assets/syntax/grey_dark.conf
+++ /dev/null
@@ -1,29 +0,0 @@
-[Main]
-name = Grey Dark
-author = Veronica Berglyd Olsen
-credit = Veronica Berglyd Olsen
-url = https://github.com/vkbo/novelWriter
-license = CC BY-SA 4.0
-licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
-
-[Syntax]
-background = 54, 54, 54
-text = 200, 200, 200
-link = 200, 200, 200
-headertext = 225, 225, 225
-headertag = 225, 225, 225, 160
-emphasis = 200, 200, 200
-dialog = 200, 200, 200
-altdialog = 200, 200, 200
-note = 200, 200, 200
-hidden = 150, 150, 150
-shortcode = 225, 225, 225
-keyword = 225, 225, 225
-tag = 200, 200, 200
-value = 200, 200, 200
-optional = 225, 225, 225
-spellcheckline = 200, 46, 0
-errorline = 46, 200, 0
-replacetag = 225, 225, 225
-modifier = 225, 225, 225
-texthighlight = 255, 255, 255, 64
diff --git a/novelwriter/assets/syntax/grey_light.conf b/novelwriter/assets/syntax/grey_light.conf
deleted file mode 100644
index 80a8a01c..00000000
--- a/novelwriter/assets/syntax/grey_light.conf
+++ /dev/null
@@ -1,29 +0,0 @@
-[Main]
-name = Grey Light
-author = Veronica Berglyd Olsen
-credit = Veronica Berglyd Olsen
-url = https://github.com/vkbo/novelWriter
-license = CC BY-SA 4.0
-licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
-
-[Syntax]
-background = 255, 255, 255
-text = 20, 20, 20
-link = 20, 20, 20
-headertext = 0, 0, 0
-headertag = 0, 0, 0, 160
-emphasis = 20, 20, 20
-dialog = 20, 20, 20
-altdialog = 20, 20, 20
-note = 20, 20, 20
-hidden = 100, 100, 100
-shortcode = 0, 0, 0
-keyword = 0, 0, 0
-tag = 20, 20, 20
-value = 20, 20, 20
-optional = 0, 0, 0
-spellcheckline = 200, 0, 0
-errorline = 0, 150, 0
-replacetag = 0, 0, 0
-modifier = 0, 0, 0
-texthighlight = 0, 0, 0, 64
diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf
index 7b0c3922..35acdb4c 100644
--- a/novelwriter/assets/themes/default_dark.conf
+++ b/novelwriter/assets/themes/default_dark.conf
@@ -46,7 +46,7 @@ linkvisited = blue
[GUI]
helptext = #a4a4a4
-fadedtext = #949494
+fadedtext = faded
errortext = red
[Syntax]
diff --git a/novelwriter/assets/themes/grey_dark.conf b/novelwriter/assets/themes/grey_dark.conf
new file mode 100644
index 00000000..d8c4a1fd
--- /dev/null
+++ b/novelwriter/assets/themes/grey_dark.conf
@@ -0,0 +1,71 @@
+[Main]
+name = Grey Dark
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Veronica Berglyd Olsen
+url = https://github.com/vkbo/novelWriter
+license = CC BY-SA 4.0
+licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
+
+[Base]
+default = #cccccc
+faded = #949494
+red = #f2777a
+orange = #f99139
+yellow = #ffcc66
+green = #99cc99
+aqua = #66cccc
+blue = #6699cc
+purple = #cc99cc
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+
+[Palette]
+window = #363636
+windowtext = default
+base = #3e3e3e
+alternatebase = #4e4e4e
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #3e3e3e
+buttontext = default
+brighttext = #3e3e3e
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+
+[GUI]
+helptext = #a4a4a4
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = #363636
+text = #c8c8c8
+link = #c8c8c8
+headertext = #e1e1e1
+headertag = #e1e1e1a0
+emphasis = #c8c8c8
+dialog = #c8c8c8
+altdialog = #c8c8c8
+note = #c8c8c8
+hidden = faded
+shortcode = #e1e1e1
+keyword = #e1e1e1
+tag = #c8c8c8
+value = #c8c8c8
+optional = #e1e1e1
+spellcheckline = red
+errorline = green
+replacetag = #e1e1e1
+modifier = #e1e1e1
+texthighlight = #ffffff40
diff --git a/novelwriter/assets/themes/grey_light.conf b/novelwriter/assets/themes/grey_light.conf
new file mode 100644
index 00000000..ffa8fe61
--- /dev/null
+++ b/novelwriter/assets/themes/grey_light.conf
@@ -0,0 +1,71 @@
+[Main]
+name = Grey Light
+mode = light
+author = Veronica Berglyd Olsen
+credit = Veronica Berglyd Olsen
+url = https://github.com/vkbo/novelWriter
+license = CC BY-SA 4.0
+licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
+
+[Base]
+default = #303030
+faded = #6c6c6c
+red = #a62a2d
+orange = #b36829
+yellow = #a68542
+green = #296629
+aqua = #269999
+blue = #3a70a6
+purple = #b35ab3
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+
+[Palette]
+window = #efefef
+windowtext = default
+base = #ffffff
+alternatebase = #e0e0e0
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #efefef
+buttontext = default
+brighttext = #ffffff
+highlight = #3087c6
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+
+[GUI]
+helptext = #5c5c5c
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = #ffffff
+text = #141414
+link = #141414
+headertext = #000000
+headertag = #000000a0
+emphasis = #141414
+dialog = #141414
+altdialog = #141414
+note = #141414
+hidden = faded
+shortcode = #000000
+keyword = #000000
+tag = #141414
+value = #141414
+optional = #000000
+spellcheckline = red
+errorline = green
+replacetag = #000000
+modifier = #000000
+texthighlight = #00000040
From 10475f32fbee62724b3d65832700f295f23968a8 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 20:21:35 +0200
Subject: [PATCH 11/96] Remove references to syntax themes
---
CREDITS.md | 4 ++--
novelwriter/assets/text/credits_en.htm | 4 ++--
novelwriter/config.py | 3 +--
3 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/CREDITS.md b/CREDITS.md
index 9ac3b34a..b3c527a5 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -62,8 +62,8 @@ Some of the assets bundled with novelWriter were adapted from the following sour
* **Material Symbols** icons by Google Inc (Apache 2.0)
* **Remix** icons by RemixIcon (Apache 2.0)
* **Font Awesome** icons by Fonticons Inc (CC BY 4.0)
-* **Tomorrow** syntax themes by Chris Kempson (MIT License)
-* **Owl** syntax themes by Sarah Drasner (MIT License)
+* **Tomorrow** themes by Chris Kempson (MIT License)
+* **Owl** themes by Sarah Drasner (MIT License)
* **Solarized** themes by Ethan Schoonover (MIT License)
* **Cyberpunk Night** theme by Anders Lemvigh (CC BY-SA 4.0)
* **Dracula** theme by Zeno Rocha (MIT License)
diff --git a/novelwriter/assets/text/credits_en.htm b/novelwriter/assets/text/credits_en.htm
index 88b3887d..33903616 100644
--- a/novelwriter/assets/text/credits_en.htm
+++ b/novelwriter/assets/text/credits_en.htm
@@ -75,8 +75,8 @@ more contributions are listed on the project's Members page.
Material Symbols icons by Google Inc (Apache 2.0)
Remix icons by RemixIcon (Apache 2.0)
Font Awesome icons by Fonticons Inc (CC BY 4.0)
- Tomorrow syntax themes by Chris Kempson (MIT License)
- Owl syntax themes by Sarah Drasner (MIT License)
+ Tomorrow themes by Chris Kempson (MIT License)
+ Owl themes by Sarah Drasner (MIT License)
Solarized themes by Ethan Schoonover (MIT License)
Cyberpunk Night theme by Anders Lemvigh (CC BY-SA 4.0)
Dracula theme by Zeno Rocha (MIT License)
diff --git a/novelwriter/config.py b/novelwriter/config.py
index 9b8752fc..db173019 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -553,11 +553,10 @@ class Config:
self._confPath.mkdir(exist_ok=True)
self._dataPath.mkdir(exist_ok=True)
- # Also create the syntax, themes and icons folders if possible
+ # Also create the themes and icons folders if possible
if self._dataPath.is_dir():
(self._dataPath / "cache").mkdir(exist_ok=True)
(self._dataPath / "icons").mkdir(exist_ok=True)
- (self._dataPath / "syntax").mkdir(exist_ok=True)
(self._dataPath / "themes").mkdir(exist_ok=True)
self._recentPaths.loadCache()
From 89a7c3e5cb4914441e607b0f48ddc43fdb4c8ba6 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 23:08:01 +0200
Subject: [PATCH 12/96] Add dark/light toggle button to sidebar
---
novelwriter/constants.py | 13 ++++++++-
novelwriter/gui/sidebar.py | 43 ++++++++++++++++++++++++++---
novelwriter/gui/theme.py | 33 ++++++++++++-----------
novelwriter/guimain.py | 55 ++++++++++++++++++++++++++------------
4 files changed, 107 insertions(+), 37 deletions(-)
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index c20b9c47..336c6318 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -28,7 +28,8 @@ from typing import Final
from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
from novelwriter.enum import (
- nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape
+ nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape,
+ nwTheme
)
@@ -453,6 +454,16 @@ class nwLabels:
"blue": QT_TRANSLATE_NOOP("Constant", "Blue"),
"purple": QT_TRANSLATE_NOOP("Constant", "Purple"),
}
+ THEME_MODE_ICON: Final[dict[nwTheme, str]] = {
+ nwTheme.AUTO: "theme_auto",
+ nwTheme.LIGHT: "theme_light",
+ nwTheme.DARK: "theme_dark",
+ }
+ THEME_MODE_LABEL: Final[dict[nwTheme, str]] = {
+ nwTheme.AUTO: QT_TRANSLATE_NOOP("Constant", "System Theme"),
+ nwTheme.LIGHT: QT_TRANSLATE_NOOP("Constant", "Light Theme"),
+ nwTheme.DARK: QT_TRANSLATE_NOOP("Constant", "Dark Theme"),
+ }
class nwHeadFmt:
diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py
index f784623f..a4dd3c9c 100644
--- a/novelwriter/gui/sidebar.py
+++ b/novelwriter/gui/sidebar.py
@@ -27,12 +27,13 @@ import logging
from typing import TYPE_CHECKING
-from PyQt6.QtCore import QEvent, QPoint, QSize, pyqtSignal
+from PyQt6.QtCore import QEvent, QPoint, QSize, pyqtSignal, pyqtSlot
from PyQt6.QtWidgets import QMenu, QVBoxLayout, QWidget
-from novelwriter import SHARED
+from novelwriter import CONFIG, SHARED
from novelwriter.common import qtLambda
-from novelwriter.enum import nwView
+from novelwriter.constants import nwLabels, trConst
+from novelwriter.enum import nwTheme, nwView
from novelwriter.extensions.eventfilters import StatusTipFilter
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.gui.theme import STYLES_BIG_TOOLBUTTON
@@ -77,6 +78,10 @@ class GuiSideBar(QWidget):
self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
self.tbOutline.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.OUTLINE))
+ self.tbTheme = NIconToolButton(self, iSz)
+ self.tbTheme.setToolTip(self.tr("Switch Colour Theme"))
+ self.tbTheme.clicked.connect(self._cycleColurTheme)
+
self.tbBuild = NIconToolButton(self, iSz)
self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
@@ -109,6 +114,7 @@ class GuiSideBar(QWidget):
self.outerBox.addWidget(self.tbOutline)
self.outerBox.addWidget(self.tbBuild)
self.outerBox.addStretch(1)
+ self.outerBox.addWidget(self.tbTheme)
self.outerBox.addWidget(self.tbDetails)
self.outerBox.addWidget(self.tbStats)
self.outerBox.addWidget(self.tbSettings)
@@ -131,6 +137,7 @@ class GuiSideBar(QWidget):
self.tbSearch.setStyleSheet(buttonStyle)
self.tbOutline.setStyleSheet(buttonStyle)
self.tbBuild.setStyleSheet(buttonStyle)
+ self.tbTheme.setStyleSheet(buttonStyle)
self.tbDetails.setStyleSheet(buttonStyle)
self.tbStats.setStyleSheet(buttonStyle)
self.tbSettings.setStyleSheet(buttonStyle)
@@ -144,6 +151,36 @@ class GuiSideBar(QWidget):
self.tbStats.setThemeIcon("sb_stats")
self.tbSettings.setThemeIcon("settings")
+ self._setThemeModeIcon()
+
+ return
+
+ ##
+ # Private Slots
+ ##
+
+ @pyqtSlot()
+ def _cycleColurTheme(self) -> None:
+ """Go to nex colour theme."""
+ match CONFIG.themeMode:
+ case nwTheme.AUTO:
+ CONFIG.themeMode = nwTheme.LIGHT
+ case nwTheme.LIGHT:
+ CONFIG.themeMode = nwTheme.DARK
+ case nwTheme.DARK:
+ CONFIG.themeMode = nwTheme.AUTO
+ self.mainGui.checkThemeUpdate()
+ self._setThemeModeIcon()
+ return
+
+ ##
+ # Internal Functions
+ ##
+
+ def _setThemeModeIcon(self) -> None:
+ """Set the theme button icon."""
+ self.tbTheme.setThemeIcon(nwLabels.THEME_MODE_ICON[CONFIG.themeMode])
+ self.tbTheme.setToolTip(trConst(nwLabels.THEME_MODE_LABEL[CONFIG.themeMode]))
return
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 4a53f453..43a6ffe3 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -100,22 +100,22 @@ class GuiTheme:
"""
__slots__ = (
- "_availSyntax", "_availThemes", "_darkThemes", "_guiPalette", "_lightThemes", "_qColors",
- "_styleSheets", "_svgColors", "_syntaxList", "_themeList", "baseButtonHeight",
- "baseIconHeight", "baseIconSize", "buttonIconSize", "errorText", "fadedText",
- "fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration",
+ "_availSyntax", "_availThemes", "_currentTheme", "_darkThemes", "_guiPalette",
+ "_lightThemes", "_qColors", "_styleSheets", "_svgColors", "_syntaxList", "_themeList",
+ "baseButtonHeight", "baseIconHeight", "baseIconSize", "buttonIconSize", "errorText",
+ "fadedText", "fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration",
"getHeaderDecorationNarrow", "getIcon", "getItemIcon", "getPixmap", "getToggleIcon",
"guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", "helpText",
- "iconCache", "isDarkTheme", "syntaxMeta", "syntaxTheme", "textNHeight", "textNWidth",
+ "iconCache", "isDarkTheme", "syntaxTheme", "textNHeight", "textNWidth",
"themeMeta",
)
def __init__(self) -> None:
- self.iconCache = GuiIcons(self)
-
- # GUI Theme
+ # Theme Objects
self.themeMeta = ThemeMeta()
+ self.iconCache = GuiIcons(self)
+ self.syntaxTheme = SyntaxColors()
self.isDarkTheme = False
# Special Text Colours
@@ -123,11 +123,8 @@ class GuiTheme:
self.fadedText = QColor(0, 0, 0)
self.errorText = QColor(255, 0, 0)
- # Syntax Theme
- self.syntaxMeta = ThemeMeta()
- self.syntaxTheme = SyntaxColors()
-
# Load Themes
+ self._currentTheme = ""
self._guiPalette = QPalette()
self._themeList: list[T_ThemeEntry] = []
self._availThemes: dict[str, Path] = {}
@@ -275,6 +272,10 @@ class GuiTheme:
theme = DEF_GUI_LIGHT
CONFIG.lightTheme = DEF_GUI_LIGHT
+ if theme == self._currentTheme:
+ logger.info("Theme '%s' is already loaded", theme)
+ return False
+
if not (file := self._availThemes.get(theme)):
logger.error("Could not load GUI theme")
return False
@@ -387,7 +388,6 @@ class GuiTheme:
text = self._guiPalette.text().color()
window = self._guiPalette.window().color()
highlight = self._guiPalette.highlight().color()
- isDark = text.lightnessF() > window.lightnessF()
QtColActive = QPalette.ColorGroup.Active
QtColInactive = QPalette.ColorGroup.Inactive
@@ -407,8 +407,8 @@ class GuiTheme:
darkOff = dark.darker(150)
shadowOff = ref.darker(150)
- grey = QColor(120, 120, 120) if isDark else QColor(140, 140, 140)
- dimmed = QColor(130, 130, 130) if isDark else QColor(190, 190, 190)
+ grey = QColor(120, 120, 120) if darkMode else QColor(140, 140, 140)
+ dimmed = QColor(130, 130, 130) if darkMode else QColor(190, 190, 190)
placeholder = QColor(text)
placeholder.setAlpha(128)
@@ -453,10 +453,11 @@ class GuiTheme:
self.iconCache.loadTheme(CONFIG.iconTheme)
# Finalise
- self.isDarkTheme = isDark
+ self.isDarkTheme = darkMode
QApplication.setPalette(self._guiPalette)
self._buildStyleSheets(self._guiPalette)
+ self._currentTheme = theme
CONFIG.splashMessage(f"Loaded GUI theme: {meta.name}")
return True
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 601540f0..6af7b3c7 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -30,7 +30,7 @@ from datetime import datetime
from pathlib import Path
from time import time
-from PyQt6.QtCore import Qt, QTimer, pyqtSlot
+from PyQt6.QtCore import QEvent, Qt, QTimer, pyqtSlot
from PyQt6.QtGui import QCloseEvent, QCursor, QIcon, QShortcut
from PyQt6.QtWidgets import (
QApplication, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox,
@@ -902,10 +902,46 @@ class GuiMain(QMainWindow):
return not self.splitView.isVisible()
+ def checkThemeUpdate(self) -> None:
+ """Load theme if mode changed."""
+ if SHARED.theme.loadTheme():
+ self.refreshThemeColors(syntax=True)
+ self.docEditor.initEditor()
+ self.docViewer.initViewer()
+ return
+
+ def refreshThemeColors(self, syntax: bool) -> None:
+ """Refresh the GUI theme."""
+ SHARED.theme.loadTheme()
+ self.setPalette(QApplication.palette())
+ self.docEditor.updateTheme()
+ self.docViewer.updateTheme()
+ self.docViewerPanel.updateTheme()
+ self.sideBar.updateTheme()
+ self.projView.updateTheme()
+ self.novelView.updateTheme()
+ self.projSearch.updateTheme()
+ self.outlineView.updateTheme()
+ self.itemDetails.updateTheme()
+ self.mainStatus.updateTheme()
+ SHARED.project.tree.refreshAllItems()
+
+ if syntax:
+ self.docEditor.updateSyntaxColors()
+
+ return
+
##
# Events
##
+ def changeEvent(self, event: QEvent) -> None:
+ """Capture application change events."""
+ if int(event.type()) == 210:
+ # ThemeChange
+ self.checkThemeUpdate()
+ return
+
def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the closing event of the GUI and call the close
function to handle all the close process steps.
@@ -1054,22 +1090,7 @@ class GuiMain(QMainWindow):
self.novelView.refreshCurrentTree()
if theme:
- SHARED.theme.loadTheme()
- self.setPalette(QApplication.palette())
- self.docEditor.updateTheme()
- self.docViewer.updateTheme()
- self.docViewerPanel.updateTheme()
- self.sideBar.updateTheme()
- self.projView.updateTheme()
- self.novelView.updateTheme()
- self.projSearch.updateTheme()
- self.outlineView.updateTheme()
- self.itemDetails.updateTheme()
- self.mainStatus.updateTheme()
- SHARED.project.tree.refreshAllItems()
-
- if syntax:
- self.docEditor.updateSyntaxColors()
+ self.refreshThemeColors(syntax=syntax)
self.docEditor.initEditor()
self.docViewer.initViewer()
From 059085a21a50d53f5b7d543bcbc14b00068033bc Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 23:13:30 +0200
Subject: [PATCH 13/96] Add theme icons
---
novelwriter/assets/icons/font_awesome.icons | 3 +++
novelwriter/assets/icons/material_filled_bold.icons | 3 +++
novelwriter/assets/icons/material_filled_normal.icons | 3 +++
novelwriter/assets/icons/material_filled_thin.icons | 3 +++
novelwriter/assets/icons/material_rounded_bold.icons | 3 +++
novelwriter/assets/icons/material_rounded_normal.icons | 3 +++
novelwriter/assets/icons/material_rounded_thin.icons | 3 +++
novelwriter/assets/icons/remix_filled.icons | 3 +++
novelwriter/assets/icons/remix_outline.icons | 3 +++
utils/icon_themes.py | 4 ++++
utils/icon_themes/font_awesome.json | 4 ++++
utils/icon_themes/material_symbols.json | 4 ++++
utils/icon_themes/remix.json | 4 ++++
13 files changed, 43 insertions(+)
diff --git a/novelwriter/assets/icons/font_awesome.icons b/novelwriter/assets/icons/font_awesome.icons
index 550fd857..75204942 100644
--- a/novelwriter/assets/icons/font_awesome.icons
+++ b/novelwriter/assets/icons/font_awesome.icons
@@ -56,6 +56,9 @@ icon:sb_outline =
icon:sb_search =
icon:sb_stats =
+icon:theme_light =
+icon:theme_dark =
+icon:theme_auto =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/material_filled_bold.icons b/novelwriter/assets/icons/material_filled_bold.icons
index bc902698..584a04bb 100644
--- a/novelwriter/assets/icons/material_filled_bold.icons
+++ b/novelwriter/assets/icons/material_filled_bold.icons
@@ -56,6 +56,9 @@ icon:sb_outline =
icon:sb_search =
icon:sb_stats =
+icon:theme_light =
+icon:theme_dark =
+icon:theme_auto =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/material_filled_normal.icons b/novelwriter/assets/icons/material_filled_normal.icons
index ce666d14..a6d7933e 100644
--- a/novelwriter/assets/icons/material_filled_normal.icons
+++ b/novelwriter/assets/icons/material_filled_normal.icons
@@ -56,6 +56,9 @@ icon:sb_outline =
icon:sb_search =
icon:sb_stats =
+icon:theme_light =
+icon:theme_dark =
+icon:theme_auto =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/material_filled_thin.icons b/novelwriter/assets/icons/material_filled_thin.icons
index 70c4f364..a405ea3e 100644
--- a/novelwriter/assets/icons/material_filled_thin.icons
+++ b/novelwriter/assets/icons/material_filled_thin.icons
@@ -56,6 +56,9 @@ icon:sb_outline =
icon:sb_search =
icon:sb_stats =
+icon:theme_light =
+icon:theme_dark =
+icon:theme_auto =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/material_rounded_bold.icons b/novelwriter/assets/icons/material_rounded_bold.icons
index 08564be2..02d83821 100644
--- a/novelwriter/assets/icons/material_rounded_bold.icons
+++ b/novelwriter/assets/icons/material_rounded_bold.icons
@@ -56,6 +56,9 @@ icon:sb_outline =
icon:sb_search =
icon:sb_stats =
+icon:theme_light =
+icon:theme_dark =
+icon:theme_auto =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/material_rounded_normal.icons b/novelwriter/assets/icons/material_rounded_normal.icons
index 3da2739e..06887aa7 100644
--- a/novelwriter/assets/icons/material_rounded_normal.icons
+++ b/novelwriter/assets/icons/material_rounded_normal.icons
@@ -56,6 +56,9 @@ icon:sb_outline =
icon:sb_search =
icon:sb_stats =
+icon:theme_light =
+icon:theme_dark =
+icon:theme_auto =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/material_rounded_thin.icons b/novelwriter/assets/icons/material_rounded_thin.icons
index 61fcd5fa..1002fcbf 100644
--- a/novelwriter/assets/icons/material_rounded_thin.icons
+++ b/novelwriter/assets/icons/material_rounded_thin.icons
@@ -56,6 +56,9 @@ icon:sb_outline =
icon:sb_search =
icon:sb_stats =
+icon:theme_light =
+icon:theme_dark =
+icon:theme_auto =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/remix_filled.icons b/novelwriter/assets/icons/remix_filled.icons
index b2305187..51e73a78 100644
--- a/novelwriter/assets/icons/remix_filled.icons
+++ b/novelwriter/assets/icons/remix_filled.icons
@@ -56,6 +56,9 @@ icon:sb_outline =
icon:sb_search =
icon:sb_stats =
+icon:theme_light =
+icon:theme_dark =
+icon:theme_auto =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/novelwriter/assets/icons/remix_outline.icons b/novelwriter/assets/icons/remix_outline.icons
index 0fae7c91..02ce0243 100644
--- a/novelwriter/assets/icons/remix_outline.icons
+++ b/novelwriter/assets/icons/remix_outline.icons
@@ -56,6 +56,9 @@ icon:sb_outline =
icon:sb_search =
icon:sb_stats =
+icon:theme_light =
+icon:theme_dark =
+icon:theme_auto =
icon:add =
icon:bookmarks =
icon:browse =
diff --git a/utils/icon_themes.py b/utils/icon_themes.py
index 00d75714..752b29e5 100644
--- a/utils/icon_themes.py
+++ b/utils/icon_themes.py
@@ -90,6 +90,10 @@ ICONS = [
"sb_search",
"sb_stats",
+ "theme_light",
+ "theme_dark",
+ "theme_auto",
+
"add",
"bookmarks",
"browse",
diff --git a/utils/icon_themes/font_awesome.json b/utils/icon_themes/font_awesome.json
index 5ed09376..d98dc9e2 100644
--- a/utils/icon_themes/font_awesome.json
+++ b/utils/icon_themes/font_awesome.json
@@ -56,6 +56,10 @@
"sb_search": "magnifying-glass",
"sb_stats": "chart-simple",
+ "theme_light": "sun",
+ "theme_dark": "moon",
+ "theme_auto": "circle-half-stroke",
+
"add": "plus",
"bookmarks": "bookmark",
"browse": "folder-open",
diff --git a/utils/icon_themes/material_symbols.json b/utils/icon_themes/material_symbols.json
index 230f5c60..61c1975a 100644
--- a/utils/icon_themes/material_symbols.json
+++ b/utils/icon_themes/material_symbols.json
@@ -56,6 +56,10 @@
"sb_search": "search",
"sb_stats": "bar_chart",
+ "theme_light": "light_mode",
+ "theme_dark": "dark_mode",
+ "theme_auto": "contrast",
+
"add": "add",
"bookmarks": "bookmarks",
"browse": "folder_open",
diff --git a/utils/icon_themes/remix.json b/utils/icon_themes/remix.json
index 300b8bbe..0ac173ec 100644
--- a/utils/icon_themes/remix.json
+++ b/utils/icon_themes/remix.json
@@ -56,6 +56,10 @@
"sb_search": "file-search",
"sb_stats": "bar-chart-fill",
+ "theme_light": "sun",
+ "theme_dark": "moon",
+ "theme_auto": "contrast",
+
"add": "add",
"bookmarks": "bookmark",
"browse": "folder-2",
From 44528b61735f0a4e161262799cf9fa1698f83f6a Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 23:20:51 +0200
Subject: [PATCH 14/96] Fix icon theme reload
---
novelwriter/gui/theme.py | 4 ++--
novelwriter/guimain.py | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 43a6ffe3..bcdb1108 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -252,7 +252,7 @@ class GuiTheme:
return QColor(*result)
return default
- def loadTheme(self) -> bool:
+ def loadTheme(self, force: bool = False) -> bool:
"""Load the currently specified GUI theme."""
match CONFIG.themeMode:
case nwTheme.LIGHT:
@@ -272,7 +272,7 @@ class GuiTheme:
theme = DEF_GUI_LIGHT
CONFIG.lightTheme = DEF_GUI_LIGHT
- if theme == self._currentTheme:
+ if theme == self._currentTheme and not force:
logger.info("Theme '%s' is already loaded", theme)
return False
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 6af7b3c7..41edcf3e 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -910,9 +910,9 @@ class GuiMain(QMainWindow):
self.docViewer.initViewer()
return
- def refreshThemeColors(self, syntax: bool) -> None:
+ def refreshThemeColors(self, syntax: bool = False, force: bool = False) -> None:
"""Refresh the GUI theme."""
- SHARED.theme.loadTheme()
+ SHARED.theme.loadTheme(force=force)
self.setPalette(QApplication.palette())
self.docEditor.updateTheme()
self.docViewer.updateTheme()
@@ -1090,7 +1090,7 @@ class GuiMain(QMainWindow):
self.novelView.refreshCurrentTree()
if theme:
- self.refreshThemeColors(syntax=syntax)
+ self.refreshThemeColors(syntax=syntax, force=True)
self.docEditor.initEditor()
self.docViewer.initViewer()
From b0ea1781e7cc3b90c32d609cf7d401e0ac08fdbf Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 2 Jun 2025 23:28:28 +0200
Subject: [PATCH 15/96] Fix test
---
tests/test_gui/test_gui_theme.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py
index ff4308f2..316f029b 100644
--- a/tests/test_gui/test_gui_theme.py
+++ b/tests/test_gui/test_gui_theme.py
@@ -137,8 +137,9 @@ def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 127, 255, 255)
# Non-existing value should return default colour
+ theme._setBaseColor("default", QColor(64, 64, 64, 255))
theme._setPalette(parser, "Palette", "stuff", QPalette.ColorRole.Window)
- assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 255)
+ assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (64, 64, 64, 255)
# qtbot.stop()
From 82188d156bb6e7bf57e4e83a6c97f8c8bf2aa2b6 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 3 Jun 2025 12:02:38 +0200
Subject: [PATCH 16/96] Change the name and alpha format of colour themes
---
novelwriter/assets/themes/default_dark.conf | 4 +-
novelwriter/assets/themes/default_light.conf | 2 +-
novelwriter/assets/themes/light_owl.conf | 4 +-
novelwriter/assets/themes/night_owl.conf | 4 +-
novelwriter/assets/themes/tango_dark.conf | 6 +-
novelwriter/assets/themes/tango_light.conf | 10 +--
novelwriter/assets/themes/tomorrow.conf | 4 +-
novelwriter/assets/themes/tomorrow_night.conf | 4 +-
.../assets/themes/tomorrow_night_blue.conf | 4 +-
.../assets/themes/tomorrow_night_bright.conf | 4 +-
.../themes/tomorrow_night_eighties.conf | 4 +-
novelwriter/gui/sidebar.py | 12 ++--
novelwriter/gui/theme.py | 36 +++++-----
tests/test_gui/test_gui_theme.py | 72 +++++++++----------
14 files changed, 84 insertions(+), 86 deletions(-)
diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf
index 35acdb4c..71943bd8 100644
--- a/novelwriter/assets/themes/default_dark.conf
+++ b/novelwriter/assets/themes/default_dark.conf
@@ -54,7 +54,7 @@ background = #363636
text = default
link = blue
headertext = green
-headertag = green, 160
+headertag = green:160
emphasis = orange
dialog = blue
altdialog = blue
@@ -69,4 +69,4 @@ spellcheckline = red
errorline = green
replacetag = green
modifier = green
-texthighlight = yellow, 72
+texthighlight = yellow:72
diff --git a/novelwriter/assets/themes/default_light.conf b/novelwriter/assets/themes/default_light.conf
index 0c309a3c..a67759dc 100644
--- a/novelwriter/assets/themes/default_light.conf
+++ b/novelwriter/assets/themes/default_light.conf
@@ -54,7 +54,7 @@ background = #ffffff
text = #000000
link = blue
headertext = green
-headertag = green, 160
+headertag = green:160
emphasis = orange
dialog = blue
altdialog = blue
diff --git a/novelwriter/assets/themes/light_owl.conf b/novelwriter/assets/themes/light_owl.conf
index f72b8d70..3544b6cd 100644
--- a/novelwriter/assets/themes/light_owl.conf
+++ b/novelwriter/assets/themes/light_owl.conf
@@ -73,7 +73,7 @@ background = #fbfbfb
text = default
link = blue
headertext = blue
-headertag = blue, 160
+headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
@@ -88,4 +88,4 @@ spellcheckline = red
errorline = green
replacetag = aqua
modifier = green
-texthighlight = yellow, 96
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/night_owl.conf b/novelwriter/assets/themes/night_owl.conf
index 0d176c45..36ec3432 100644
--- a/novelwriter/assets/themes/night_owl.conf
+++ b/novelwriter/assets/themes/night_owl.conf
@@ -73,7 +73,7 @@ background = #011627
text = default
link = blue
headertext = blue
-headertag = blue, 160
+headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
@@ -88,4 +88,4 @@ spellcheckline = red
errorline = green
replacetag = aqua
modifier = green
-texthighlight = yellow, 96
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tango_dark.conf b/novelwriter/assets/themes/tango_dark.conf
index ca0ba841..9c4dc59e 100644
--- a/novelwriter/assets/themes/tango_dark.conf
+++ b/novelwriter/assets/themes/tango_dark.conf
@@ -63,7 +63,7 @@ background = #2e3436
text = default
link = blue
headertext = blue
-headertag = blue, 160
+headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
@@ -76,6 +76,6 @@ value = red
optional = blue
spellcheckline = red
errorline = green
-replacetag = cyan
+replacetag = aqua
modifier = blue
-texthighlight = yellow, 96
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tango_light.conf b/novelwriter/assets/themes/tango_light.conf
index 174b0571..3c66d1e9 100644
--- a/novelwriter/assets/themes/tango_light.conf
+++ b/novelwriter/assets/themes/tango_light.conf
@@ -1,6 +1,6 @@
[Main]
-name = Tango Light
-mode = light
+name = Tango Light
+mode = light
author = Veronica Berglyd Olsen (adaptation)
##
@@ -63,7 +63,7 @@ background = #eeeeec
text = default
link = blue
headertext = blue
-headertag = blue, 160
+headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
@@ -76,6 +76,6 @@ value = red
optional = blue
spellcheckline = red
errorline = green
-replacetag = cyan
+replacetag = aqua
modifier = blue
-texthighlight = yellow, 96
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow.conf b/novelwriter/assets/themes/tomorrow.conf
index 2b734209..f8b9d5df 100644
--- a/novelwriter/assets/themes/tomorrow.conf
+++ b/novelwriter/assets/themes/tomorrow.conf
@@ -73,7 +73,7 @@ background = #ffffff
text = #4d4d4c
link = blue
headertext = blue
-headertag = blue, 160
+headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
@@ -88,4 +88,4 @@ spellcheckline = red
errorline = green
replacetag = aqua
modifier = orange
-texthighlight = yellow, 96
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow_night.conf b/novelwriter/assets/themes/tomorrow_night.conf
index 27abd454..d0e91fbd 100644
--- a/novelwriter/assets/themes/tomorrow_night.conf
+++ b/novelwriter/assets/themes/tomorrow_night.conf
@@ -73,7 +73,7 @@ background = #1d1f21
text = default
link = blue
headertext = blue
-headertag = blue, 160
+headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
@@ -88,4 +88,4 @@ spellcheckline = red
errorline = green
replacetag = aqua
modifier = orange
-texthighlight = yellow, 96
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow_night_blue.conf b/novelwriter/assets/themes/tomorrow_night_blue.conf
index 93b6592e..79fa3ea4 100644
--- a/novelwriter/assets/themes/tomorrow_night_blue.conf
+++ b/novelwriter/assets/themes/tomorrow_night_blue.conf
@@ -73,7 +73,7 @@ background = #002451
text = default
link = blue
headertext = blue
-headertag = blue, 160
+headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
@@ -88,4 +88,4 @@ spellcheckline = red
errorline = green
replacetag = aqua
modifier = orange
-texthighlight = yellow, 96
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow_night_bright.conf b/novelwriter/assets/themes/tomorrow_night_bright.conf
index de76e694..3c2ad22d 100644
--- a/novelwriter/assets/themes/tomorrow_night_bright.conf
+++ b/novelwriter/assets/themes/tomorrow_night_bright.conf
@@ -73,7 +73,7 @@ background = #000000
text = default
link = blue
headertext = blue
-headertag = blue, 160
+headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
@@ -88,4 +88,4 @@ spellcheckline = red
errorline = green
replacetag = aqua
modifier = orange
-texthighlight = yellow, 96
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow_night_eighties.conf b/novelwriter/assets/themes/tomorrow_night_eighties.conf
index e3e8bbec..8f969073 100644
--- a/novelwriter/assets/themes/tomorrow_night_eighties.conf
+++ b/novelwriter/assets/themes/tomorrow_night_eighties.conf
@@ -73,7 +73,7 @@ background = #2d2d2d
text = default
link = blue
headertext = blue
-headertag = blue, 160
+headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
@@ -88,4 +88,4 @@ spellcheckline = red
errorline = green
replacetag = aqua
modifier = orange
-texthighlight = yellow, 96
+texthighlight = yellow:96
diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py
index a4dd3c9c..e0ccb9e5 100644
--- a/novelwriter/gui/sidebar.py
+++ b/novelwriter/gui/sidebar.py
@@ -82,10 +82,6 @@ class GuiSideBar(QWidget):
self.tbTheme.setToolTip(self.tr("Switch Colour Theme"))
self.tbTheme.clicked.connect(self._cycleColurTheme)
- self.tbBuild = NIconToolButton(self, iSz)
- self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
- self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
-
self.tbDetails = NIconToolButton(self, iSz)
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Novel Details")))
self.tbDetails.clicked.connect(self.mainGui.showNovelDetailsDialog)
@@ -94,6 +90,10 @@ class GuiSideBar(QWidget):
self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
+ self.tbBuild = NIconToolButton(self, iSz)
+ self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
+ self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
+
# Settings Menu
self.tbSettings = NIconToolButton(self, iSz)
self.tbSettings.setToolTip(self.tr("Settings"))
@@ -114,9 +114,9 @@ class GuiSideBar(QWidget):
self.outerBox.addWidget(self.tbOutline)
self.outerBox.addWidget(self.tbBuild)
self.outerBox.addStretch(1)
- self.outerBox.addWidget(self.tbTheme)
self.outerBox.addWidget(self.tbDetails)
self.outerBox.addWidget(self.tbStats)
+ self.outerBox.addWidget(self.tbTheme)
self.outerBox.addWidget(self.tbSettings)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.outerBox.setSpacing(6)
@@ -137,9 +137,9 @@ class GuiSideBar(QWidget):
self.tbSearch.setStyleSheet(buttonStyle)
self.tbOutline.setStyleSheet(buttonStyle)
self.tbBuild.setStyleSheet(buttonStyle)
- self.tbTheme.setStyleSheet(buttonStyle)
self.tbDetails.setStyleSheet(buttonStyle)
self.tbStats.setStyleSheet(buttonStyle)
+ self.tbTheme.setStyleSheet(buttonStyle)
self.tbSettings.setStyleSheet(buttonStyle)
self.tbProject.setThemeIcon("sb_project")
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index bcdb1108..236bebe7 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -229,27 +229,25 @@ class GuiTheme:
if value in self._qColors:
# Named colour
return self._qColors[value]
- elif value.startswith("#"):
- if len(value) >= 9:
- # Convert from #RRGGBBAA to #AARRGGBB
- return QColor.fromString(f"#{value[7:9]}{value[1:7]}")
- else:
- # Assume #RRGGBB
- return QColor.fromString(value[:7])
+ elif value.startswith("#") and len(value) == 7:
+ # Assume #RRGGBB
+ return QColor.fromString(value)
+ elif value.startswith("#") and len(value) == 9:
+ # Assume #RRGGBBAA and convert to #AARRGGBB
+ return QColor.fromString(f"#{value[7:9]}{value[1:7]}")
+ elif ":" in value:
+ # Colour name and alpha
+ name, _, alpha = value.partition(":")
+ color = QColor(self._qColors.get(name.strip(), default))
+ color.setAlpha(checkInt(alpha, 255))
+ return color
elif "," in value:
+ # Integer red, green, blue, alpha
data = value.split(",")
- entries = len(data)
- if entries == 2:
- # Assume name, alpha
- color = QColor(self._qColors.get(data[0].strip(), default))
- color.setAlpha(checkInt(data[1], 255))
- return color
- else:
- # Assume red, green, blue, alpha
- result = [0, 0, 0, 255]
- for i in range(min(entries, 4)):
- result[i] = checkInt(data[i].strip(), result[i])
- return QColor(*result)
+ result = [0, 0, 0, 255]
+ for i in range(min(len(data), 4)):
+ result[i] = checkInt(data[i].strip(), result[i])
+ return QColor(*result)
return default
def loadTheme(self, force: bool = False) -> bool:
diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py
index 316f029b..f57a2c30 100644
--- a/tests/test_gui/test_gui_theme.py
+++ b/tests/test_gui/test_gui_theme.py
@@ -39,40 +39,6 @@ from tests.mocked import causeOSError
from tests.tools import writeFile
-@pytest.mark.gui
-def testGuiTheme_ParseColor(qtbot, nwGUI):
- """Test the colour parsing."""
- theme = SHARED.theme
-
- # Pre-Populate
- theme._qColors["red"] = QColor(255, 0, 0)
- theme._qColors["green"] = QColor(0, 255, 0)
- theme._qColors["blue"] = QColor(0, 0, 255)
-
- # By Name
- assert theme.parseColor("red").getRgb() == (255, 0, 0, 255)
- assert theme.parseColor("green").getRgb() == (0, 255, 0, 255)
- assert theme.parseColor("blue").getRgb() == (0, 0, 255, 255)
- assert theme.parseColor("bob").getRgb() == (0, 0, 0, 255)
-
- # CSS Format
- assert theme.parseColor("#ff0000").getRgb() == (255, 0, 0, 255)
- assert theme.parseColor("#ff00007f").getRgb() == (255, 0, 0, 127)
- assert theme.parseColor("#ff00").getRgb() == (0, 0, 0, 255) # Too short -> ignored
- assert theme.parseColor("#ff00007f15").getRgb() == (255, 0, 0, 127) # Too long -> truncated
-
- # Name + Alpha
- assert theme.parseColor("red, 255").getRgb() == (255, 0, 0, 255)
- assert theme.parseColor("red, 127").getRgb() == (255, 0, 0, 127)
- assert theme.parseColor("red, 512").getRgb() == (255, 0, 0, 255) # Value truncated
-
- # Values
- assert theme.parseColor("255, 0, 0").getRgb() == (255, 0, 0, 255)
- assert theme.parseColor("255, 0, 0, 255").getRgb() == (255, 0, 0, 255)
- assert theme.parseColor("255, 0, 0, 127").getRgb() == (255, 0, 0, 127)
- assert theme.parseColor("255, 0, 0, 127, 42").getRgb() == (255, 0, 0, 127) # Truncated
-
-
@pytest.mark.gui
def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
"""Test the theme class init."""
@@ -118,7 +84,7 @@ def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
assert theme._readColor(parser, "Palette", "colour1").getRgb() == (100, 150, 200, 255)
assert theme._readColor(parser, "Palette", "colour2").getRgb() == (100, 150, 200, 250)
assert theme._readColor(parser, "Palette", "colour3").getRgb() == (100, 150, 200, 250)
- assert theme._readColor(parser, "Palette", "colour4").getRgb() == (0, 0, 0, 250)
+ assert theme._readColor(parser, "Palette", "colour4").getRgb() == (250, 250, 0, 255)
assert theme._readColor(parser, "Palette", "colour5").getRgb() == (0, 0, 0, 0)
assert theme._readColor(parser, "Palette", "colour6").getRgb() == (0, 127, 255, 255)
@@ -130,7 +96,7 @@ def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
theme._setPalette(parser, "Palette", "colour3", QPalette.ColorRole.Window)
assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 250)
theme._setPalette(parser, "Palette", "colour4", QPalette.ColorRole.Window)
- assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 250)
+ assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (250, 250, 0, 255)
theme._setPalette(parser, "Palette", "colour5", QPalette.ColorRole.Window)
assert theme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 0)
theme._setPalette(parser, "Palette", "colour6", QPalette.ColorRole.Window)
@@ -144,6 +110,40 @@ def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
# qtbot.stop()
+@pytest.mark.gui
+def testGuiTheme_ParseColor(qtbot, nwGUI):
+ """Test the colour parsing."""
+ theme = SHARED.theme
+
+ # Pre-Populate
+ theme._qColors["red"] = QColor(255, 0, 0)
+ theme._qColors["green"] = QColor(0, 255, 0)
+ theme._qColors["blue"] = QColor(0, 0, 255)
+
+ # By Name
+ assert theme.parseColor("red").getRgb() == (255, 0, 0, 255)
+ assert theme.parseColor("green").getRgb() == (0, 255, 0, 255)
+ assert theme.parseColor("blue").getRgb() == (0, 0, 255, 255)
+ assert theme.parseColor("bob").getRgb() == (0, 0, 0, 255)
+
+ # CSS Format
+ assert theme.parseColor("#ff0000").getRgb() == (255, 0, 0, 255)
+ assert theme.parseColor("#ff00007f").getRgb() == (255, 0, 0, 127)
+ assert theme.parseColor("#ff00").getRgb() == (0, 0, 0, 255) # Too short -> ignored
+ assert theme.parseColor("#ff00007f15").getRgb() == (0, 0, 0, 255) # Too long -> ignored
+
+ # Name + Alpha
+ assert theme.parseColor("red:255").getRgb() == (255, 0, 0, 255)
+ assert theme.parseColor("red:127").getRgb() == (255, 0, 0, 127)
+ assert theme.parseColor("red:512").getRgb() == (255, 0, 0, 255) # Value truncated
+
+ # Values
+ assert theme.parseColor("255, 0, 0").getRgb() == (255, 0, 0, 255)
+ assert theme.parseColor("255, 0, 0, 255").getRgb() == (255, 0, 0, 255)
+ assert theme.parseColor("255, 0, 0, 127").getRgb() == (255, 0, 0, 127)
+ assert theme.parseColor("255, 0, 0, 127, 42").getRgb() == (255, 0, 0, 127) # Truncated
+
+
@pytest.mark.gui
@pytest.mark.skip
def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths):
From 60b5f660e8d899db5704251a9417add2049364f0 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 3 Jun 2025 17:30:13 +0200
Subject: [PATCH 17/96] Rename colour aqua to cyan in themes
---
novelwriter/assets/themes/cyberpunk_night.conf | 2 +-
novelwriter/assets/themes/default_dark.conf | 2 +-
novelwriter/assets/themes/default_light.conf | 2 +-
novelwriter/assets/themes/dracula.conf | 2 +-
novelwriter/assets/themes/grey_dark.conf | 2 +-
novelwriter/assets/themes/grey_light.conf | 2 +-
novelwriter/assets/themes/light_owl.conf | 4 ++--
novelwriter/assets/themes/night_owl.conf | 4 ++--
novelwriter/assets/themes/snazzy.conf | 2 +-
novelwriter/assets/themes/solarized_dark.conf | 2 +-
novelwriter/assets/themes/solarized_light.conf | 2 +-
novelwriter/assets/themes/tango_dark.conf | 4 ++--
novelwriter/assets/themes/tango_light.conf | 4 ++--
novelwriter/assets/themes/tomorrow.conf | 4 ++--
novelwriter/assets/themes/tomorrow_night.conf | 4 ++--
novelwriter/assets/themes/tomorrow_night_blue.conf | 4 ++--
novelwriter/assets/themes/tomorrow_night_bright.conf | 4 ++--
novelwriter/assets/themes/tomorrow_night_eighties.conf | 4 ++--
novelwriter/constants.py | 2 +-
novelwriter/gui/theme.py | 6 +++---
20 files changed, 31 insertions(+), 31 deletions(-)
diff --git a/novelwriter/assets/themes/cyberpunk_night.conf b/novelwriter/assets/themes/cyberpunk_night.conf
index 1ee10ca8..40b5dd53 100644
--- a/novelwriter/assets/themes/cyberpunk_night.conf
+++ b/novelwriter/assets/themes/cyberpunk_night.conf
@@ -14,7 +14,7 @@ red = #f24817
orange = #ff960a
yellow = #ffff00
green = #00ff00
-aqua = #00ffff
+cyan = #00ffff
blue = #4d4dff
purple = #320064
diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf
index 71943bd8..e559de9e 100644
--- a/novelwriter/assets/themes/default_dark.conf
+++ b/novelwriter/assets/themes/default_dark.conf
@@ -15,7 +15,7 @@ red = #f2777a
orange = #f99139
yellow = #ffcc66
green = #99cc99
-aqua = #66cccc
+cyan = #66cccc
blue = #6699cc
purple = #cc99cc
diff --git a/novelwriter/assets/themes/default_light.conf b/novelwriter/assets/themes/default_light.conf
index a67759dc..638d6879 100644
--- a/novelwriter/assets/themes/default_light.conf
+++ b/novelwriter/assets/themes/default_light.conf
@@ -15,7 +15,7 @@ red = #a62a2d
orange = #b36829
yellow = #a68542
green = #296629
-aqua = #269999
+cyan = #269999
blue = #3a70a6
purple = #b35ab3
diff --git a/novelwriter/assets/themes/dracula.conf b/novelwriter/assets/themes/dracula.conf
index 49f30501..04e32450 100644
--- a/novelwriter/assets/themes/dracula.conf
+++ b/novelwriter/assets/themes/dracula.conf
@@ -31,7 +31,7 @@ red = #ff5555
orange = #ffb86c
yellow = #f1fa8c
green = #50fa7b
-aqua = #8be9fd
+cyan = #8be9fd
blue = #93cff9
purple = #bd93f9
diff --git a/novelwriter/assets/themes/grey_dark.conf b/novelwriter/assets/themes/grey_dark.conf
index d8c4a1fd..21d95224 100644
--- a/novelwriter/assets/themes/grey_dark.conf
+++ b/novelwriter/assets/themes/grey_dark.conf
@@ -14,7 +14,7 @@ red = #f2777a
orange = #f99139
yellow = #ffcc66
green = #99cc99
-aqua = #66cccc
+cyan = #66cccc
blue = #6699cc
purple = #cc99cc
diff --git a/novelwriter/assets/themes/grey_light.conf b/novelwriter/assets/themes/grey_light.conf
index ffa8fe61..651ab6d0 100644
--- a/novelwriter/assets/themes/grey_light.conf
+++ b/novelwriter/assets/themes/grey_light.conf
@@ -14,7 +14,7 @@ red = #a62a2d
orange = #b36829
yellow = #a68542
green = #296629
-aqua = #269999
+cyan = #269999
blue = #3a70a6
purple = #b35ab3
diff --git a/novelwriter/assets/themes/light_owl.conf b/novelwriter/assets/themes/light_owl.conf
index 3544b6cd..b18f5fb7 100644
--- a/novelwriter/assets/themes/light_owl.conf
+++ b/novelwriter/assets/themes/light_owl.conf
@@ -34,7 +34,7 @@ red = #de3d3a
orange = #e0af05
yellow = #daaa01
green = #08916a
-aqua = #2aa298
+cyan = #2aa298
blue = #288ed7
purple = #964ac1
@@ -86,6 +86,6 @@ value = orange
optional = blue
spellcheckline = red
errorline = green
-replacetag = aqua
+replacetag = cyan
modifier = green
texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/night_owl.conf b/novelwriter/assets/themes/night_owl.conf
index 36ec3432..8aae2b62 100644
--- a/novelwriter/assets/themes/night_owl.conf
+++ b/novelwriter/assets/themes/night_owl.conf
@@ -34,7 +34,7 @@ red = #f78c6c
orange = #ecc48d
yellow = #addb67
green = #addb67
-aqua = #7fdbca
+cyan = #7fdbca
blue = #82aaff
purple = #c792ea
@@ -86,6 +86,6 @@ value = orange
optional = blue
spellcheckline = red
errorline = green
-replacetag = aqua
+replacetag = cyan
modifier = green
texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/snazzy.conf b/novelwriter/assets/themes/snazzy.conf
index 19a662c0..6220dcc3 100644
--- a/novelwriter/assets/themes/snazzy.conf
+++ b/novelwriter/assets/themes/snazzy.conf
@@ -27,7 +27,7 @@ red = #ff5c57
orange = #f5b900
yellow = #cf9c00
green = #2dae58
-aqua = #13bbb7
+cyan = #13bbb7
blue = #09a1ed
purple = #f767bb
diff --git a/novelwriter/assets/themes/solarized_dark.conf b/novelwriter/assets/themes/solarized_dark.conf
index 354b2be9..5cf4e157 100644
--- a/novelwriter/assets/themes/solarized_dark.conf
+++ b/novelwriter/assets/themes/solarized_dark.conf
@@ -33,7 +33,7 @@ red = #dc322f
orange = #cb4b16
yellow = #b58900
green = #859900
-aqua = #2aa198
+cyan = #2aa198
blue = #268bd2
purple = #6c71c4
diff --git a/novelwriter/assets/themes/solarized_light.conf b/novelwriter/assets/themes/solarized_light.conf
index 7d7427f5..f185bd73 100644
--- a/novelwriter/assets/themes/solarized_light.conf
+++ b/novelwriter/assets/themes/solarized_light.conf
@@ -33,7 +33,7 @@ red = #dc322f
orange = #cb4b16
yellow = #b58900
green = #859900
-aqua = #2aa198
+cyan = #2aa198
blue = #268bd2
purple = #6c71c4
diff --git a/novelwriter/assets/themes/tango_dark.conf b/novelwriter/assets/themes/tango_dark.conf
index 9c4dc59e..33e758f7 100644
--- a/novelwriter/assets/themes/tango_dark.conf
+++ b/novelwriter/assets/themes/tango_dark.conf
@@ -24,7 +24,7 @@ red = #ef2929
orange = #fcaf3e
yellow = #fce94f
green = #8ae234
-aqua = #34e2e2
+cyan = #34e2e2
blue = #729fcf
purple = #ad7fa8
@@ -76,6 +76,6 @@ value = red
optional = blue
spellcheckline = red
errorline = green
-replacetag = aqua
+replacetag = cyan
modifier = blue
texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tango_light.conf b/novelwriter/assets/themes/tango_light.conf
index 3c66d1e9..877f71ac 100644
--- a/novelwriter/assets/themes/tango_light.conf
+++ b/novelwriter/assets/themes/tango_light.conf
@@ -24,7 +24,7 @@ red = #a40000
orange = #ce5c00
yellow = #c4a000
green = #4e9a06
-aqua = #069a9a
+cyan = #069a9a
blue = #204a87
purple = #5c3566
@@ -76,6 +76,6 @@ value = red
optional = blue
spellcheckline = red
errorline = green
-replacetag = aqua
+replacetag = cyan
modifier = blue
texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow.conf b/novelwriter/assets/themes/tomorrow.conf
index f8b9d5df..974816c1 100644
--- a/novelwriter/assets/themes/tomorrow.conf
+++ b/novelwriter/assets/themes/tomorrow.conf
@@ -34,7 +34,7 @@ red = #c82829
orange = #f5871f
yellow = #eab700
green = #718c00
-aqua = #3e999f
+cyan = #3e999f
blue = #4271ae
purple = #8959a8
@@ -86,6 +86,6 @@ value = yellow
optional = blue
spellcheckline = red
errorline = green
-replacetag = aqua
+replacetag = cyan
modifier = orange
texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow_night.conf b/novelwriter/assets/themes/tomorrow_night.conf
index d0e91fbd..94fecbc0 100644
--- a/novelwriter/assets/themes/tomorrow_night.conf
+++ b/novelwriter/assets/themes/tomorrow_night.conf
@@ -34,7 +34,7 @@ red = #cc6666
orange = #de935f
yellow = #f0c674
green = #b5bd68
-aqua = #8abeb7
+cyan = #8abeb7
blue = #81a2be
purple = #b294bb
@@ -86,6 +86,6 @@ value = yellow
optional = blue
spellcheckline = red
errorline = green
-replacetag = aqua
+replacetag = cyan
modifier = orange
texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow_night_blue.conf b/novelwriter/assets/themes/tomorrow_night_blue.conf
index 79fa3ea4..88aac2e3 100644
--- a/novelwriter/assets/themes/tomorrow_night_blue.conf
+++ b/novelwriter/assets/themes/tomorrow_night_blue.conf
@@ -34,7 +34,7 @@ red = #ff9da4
orange = #ffc58f
yellow = #ffeead
green = #d1f1a9
-aqua = #99ffff
+cyan = #99ffff
blue = #bbdaff
purple = #ebbbff
@@ -86,6 +86,6 @@ value = yellow
optional = blue
spellcheckline = red
errorline = green
-replacetag = aqua
+replacetag = cyan
modifier = orange
texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow_night_bright.conf b/novelwriter/assets/themes/tomorrow_night_bright.conf
index 3c2ad22d..ab3c4766 100644
--- a/novelwriter/assets/themes/tomorrow_night_bright.conf
+++ b/novelwriter/assets/themes/tomorrow_night_bright.conf
@@ -34,7 +34,7 @@ red = #d54e53
orange = #e78c45
yellow = #e7c547
green = #b9ca4a
-aqua = #70c0b1
+cyan = #70c0b1
blue = #7aa6da
purple = #c397d8
@@ -86,6 +86,6 @@ value = yellow
optional = blue
spellcheckline = red
errorline = green
-replacetag = aqua
+replacetag = cyan
modifier = orange
texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow_night_eighties.conf b/novelwriter/assets/themes/tomorrow_night_eighties.conf
index 8f969073..ed67eb95 100644
--- a/novelwriter/assets/themes/tomorrow_night_eighties.conf
+++ b/novelwriter/assets/themes/tomorrow_night_eighties.conf
@@ -34,7 +34,7 @@ red = #f2777a
orange = #f99157
yellow = #ffcc66
green = #99cc99
-aqua = #66cccc
+cyan = #66cccc
blue = #6699cc
purple = #cc99cc
@@ -86,6 +86,6 @@ value = yellow
optional = blue
spellcheckline = red
errorline = green
-replacetag = aqua
+replacetag = cyan
modifier = orange
texthighlight = yellow:96
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index 336c6318..5b7b260d 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -450,7 +450,7 @@ class nwLabels:
"orange": QT_TRANSLATE_NOOP("Constant", "Orange"),
"yellow": QT_TRANSLATE_NOOP("Constant", "Yellow"),
"green": QT_TRANSLATE_NOOP("Constant", "Green"),
- "aqua": QT_TRANSLATE_NOOP("Constant", "Aqua"),
+ "cyan": QT_TRANSLATE_NOOP("Constant", "Cyan"),
"blue": QT_TRANSLATE_NOOP("Constant", "Blue"),
"purple": QT_TRANSLATE_NOOP("Constant", "Purple"),
}
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 236bebe7..b5e2d2f2 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -316,7 +316,7 @@ class GuiTheme:
self._setBaseColor("orange", self._readColor(parser, sec, "orange"))
self._setBaseColor("yellow", self._readColor(parser, sec, "yellow"))
self._setBaseColor("green", self._readColor(parser, sec, "green"))
- self._setBaseColor("aqua", self._readColor(parser, sec, "aqua"))
+ self._setBaseColor("cyan", self._readColor(parser, sec, "cyan"))
self._setBaseColor("blue", self._readColor(parser, sec, "blue"))
self._setBaseColor("purple", self._readColor(parser, sec, "purple"))
@@ -505,7 +505,7 @@ class GuiTheme:
orange = QColor(249, 145, 57) if isDark else QColor(245, 135, 31)
yellow = QColor(255, 204, 102) if isDark else QColor(234, 183, 0)
green = QColor(153, 204, 153) if isDark else QColor(113, 140, 0)
- aqua = QColor(102, 204, 204) if isDark else QColor(62, 153, 159)
+ cyan = QColor(102, 204, 204) if isDark else QColor(62, 153, 159)
blue = QColor(102, 153, 204) if isDark else QColor(66, 113, 174)
purple = QColor(204, 153, 204) if isDark else QColor(137, 89, 168)
@@ -526,7 +526,7 @@ class GuiTheme:
self._setBaseColor("orange", orange)
self._setBaseColor("yellow", yellow)
self._setBaseColor("green", green)
- self._setBaseColor("aqua", aqua)
+ self._setBaseColor("cyan", cyan)
self._setBaseColor("blue", blue)
self._setBaseColor("purple", purple)
self._setBaseColor("root", blue)
From df7a7216268cae42938fe92e17cbcc7ac81eb11d Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 3 Jun 2025 17:30:59 +0200
Subject: [PATCH 18/96] Update documentation on colour themes
---
docs/source/index.rst | 1 +
docs/source/more/customise.rst | 353 ++++++++++---------
docs/source/more/dictionaries.rst | 55 +++
docs/source/user_interface/editor_viewer.rst | 2 +-
4 files changed, 242 insertions(+), 169 deletions(-)
create mode 100644 docs/source/more/dictionaries.rst
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 8e863ff8..793d1d38 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -75,6 +75,7 @@ storage for robustness.
more/counting
more/typography
+ more/dictionaries
more/customise
more/handling_errors
more/project_format
diff --git a/docs/source/more/customise.rst b/docs/source/more/customise.rst
index 80aa1201..8f3d2f60 100644
--- a/docs/source/more/customise.rst
+++ b/docs/source/more/customise.rst
@@ -1,94 +1,35 @@
.. _docs_more_custom:
-**************
-Customisations
-**************
+*************
+Custom Themes
+*************
-.. _Enchant: https://rrthomas.github.io/enchant/
-.. _Free Desktop: https://cgit.freedesktop.org/libreoffice/dictionaries/tree/
-
-There are a few ways you can customise novelWriter yourself. Currently, you can add new GUI themes,
-your own syntax themes, and install additional dictionaries.
-
-
-.. _docs_more_custom_dict:
-
-Spell Check Dictionaries
-========================
-
-novelWriter uses Enchant_ as the spell checking tool. Depending on your operating system, it may or
-may not load all installed spell check dictionaries automatically.
-
-
-Linux and MacOS
----------------
-
-On Linux and MacOS, you generally only have to install hunspell, aspell or myspell dictionaries on
-your system like you do for other applications. See your distro or OS documentation for how to do
-this. These dictionaries should show up as available spell check languages in novelWriter.
-
-
-Windows
--------
-
-For Windows, English is included with the installation. For other languages you have to download
-and add dictionaries yourself.
-
-**Install Tool**
-
-A small tool to assist with this can be found under **Tools > Add Dictionaries**. It will import
-spell checking dictionaries from Free Office or Libre Office extensions. The dictionaries are then
-installed in the install location for the Enchant library and should thus work for any application
-that uses Enchant for spell checking.
-
-**Manual Install**
-
-If you prefer to do this manually or want to use a different source than the ones mentioned above,
-You need to get compatible dictionary files for your language. You need two files files ending with
-``.aff`` and ``.dic``. These files must then be copied to the following location:
-
-``C:\Users\\AppData\Local\enchant\hunspell``
-
-This assumes your user profile is stored at ``C:\Users\``. The last one or two folders may
-not exist, so you may need to create them.
-
-You can find the various dictionaries on the `Free Desktop`_ website.
-
-.. note::
- The Free Desktop link points to a repository, and what may look like file links inside the
- dictionary folder are actually links to web pages. If you right-click and download those, you
- get HTML files, not dictionaries!
-
- In order to download the actual dictionary files, right-click the "plain" label at the end of
- each line and download that.
+There are a few ways you can customise novelWriter yourself. Currently, you can relatively easily
+add new GUI themes. You can also add new icon themes, although this is not as straightforward.
.. _docs_more_custom_theme:
-Syntax and GUI Themes
-=====================
+Colour Themes
+=============
-Adding your own GUI and syntax themes is relatively easy, although it requires that you manually
-edit config files with colour values. The themes are defined by simple plain text config files with
-meta data and colour settings.
+Adding your own colour themes is relatively easy, although it requires that you manually edit
+config files with colour values. The themes are defined by simple plain text config files with meta
+data and colour settings.
In order to make your own versions, first copy one of the existing files to your local computer and
modify it as you like.
-* The existing syntax themes are stored in
- `novelwriter/assets/syntax `_.
-* The existing GUI themes are stored in
- `novelwriter/assets/themes `_.
-* The existing icon themes are stored in
- `novelwriter/assets/icons `_.
+The existing colour themes are stored in
+`novelwriter/assets/themes `_.
Remember to also change the name of your theme by modifying the ``name`` setting at the top of the
file, otherwise you may not be able to distinguish them in **Preferences**.
For novelWriter to be able to locate the custom theme files, you must copy them to the
-:ref:`docs_technical_locations_data` location in your home or user area. There should be a folder there named
-``syntax`` for syntax themes, just ``themes`` for GUI themes, and ``icons`` for icon themes. These
-folders are created the first time you start novelWriter.
+:ref:`docs_technical_locations_data` location in your home or user area. There should be a folder
+there named ``themes`` for colour themes. These folders are created the first time you start
+novelWriter.
Once the files are copied there, they should show up in **Preferences** with the label you
set as ``name`` inside the file.
@@ -99,71 +40,123 @@ set as ``name`` inside the file.
files up to date.
-Custom GUI and Icons Theme
---------------------------
+The Theme File Format
+---------------------
-A GUI theme ``.conf`` file consists of the following settings:
+A colour theme ``.conf`` file consists of the following settings:
.. code-block:: cfg
+ :caption: The theme file for the "Default Light Theme"
[Main]
- name = My Custom Theme
- description = A description of my custom theme
- author = Jane Doe
- credit = John Doe
- url = https://example.com
+ name = Default Light Theme
+ mode = light
+ description = The novelWriter standard light theme
+ author = Veronica Berglyd Olsen
+ credit = Veronica Berglyd Olsen
+ url = https://github.com/vkbo/novelWriter
license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
- [Icons]
- default = 100, 100, 100
- faded = 100, 100, 100
- red = 255, 0, 0
- orange = 255, 128, 0
- yellow = 255, 255, 0
- green = 0, 255, 0
- aqua = 0, 255, 255
- blue = 0, 0, 255
- purple = 255, 0, 255
+ [Base]
+ default = #303030
+ faded = #6c6c6c
+ red = #a62a2d
+ orange = #b36829
+ yellow = #a68542
+ green = #296629
+ cyan = #269999
+ blue = #3a70a6
+ purple = #b35ab3
[Project]
- root = 0, 255, 255
- folder = 255, 255, 0
- file = 100, 100, 100
- title = 0, 255, 0
- chapter = 255, 0, 0
- scene = 0, 0, 255
- note = 255, 255, 0
+ root = blue
+ folder = yellow
+ file = default
+ title = green
+ chapter = red
+ scene = blue
+ note = yellow
[Palette]
- window = 100, 100, 100
- windowtext = 100, 100, 100
- base = 100, 100, 100
- alternatebase = 100, 100, 100
- text = 100, 100, 100
- tooltipbase = 100, 100, 100
- tooltiptext = 100, 100, 100
- button = 100, 100, 100
- buttontext = 100, 100, 100
- brighttext = 100, 100, 100
- highlight = 100, 100, 100
- highlightedtext = 100, 100, 100
- link = 100, 100, 100
- linkvisited = 100, 100, 100
+ window = #efefef
+ windowtext = #000000
+ base = #ffffff
+ alternatebase = #e0e0e0
+ text = #000000
+ tooltipbase = #ffffc0
+ tooltiptext = #15150d
+ button = #efefef
+ buttontext = #000000
+ brighttext = #ffffff
+ highlight = #3087c6
+ highlightedtext = #ffffff
+ link = blue
+ linkvisited = blue
[GUI]
- helptext = 0, 0, 0
- fadedtext = 128, 128, 128
- errortext = 255, 0, 0
+ helptext = #5c5c5c
+ fadedtext = #6c6c6c
+ errortext = red
-In the Main section you must at least define the ``name`` settings.
+ [Syntax]
+ background = #ffffff
+ text = #000000
+ link = blue
+ headertext = green
+ headertag = green:160
+ emphasis = orange
+ dialog = blue
+ altdialog = blue
+ note = yellow
+ hidden = faded
+ shortcode = green
+ keyword = red
+ tag = green
+ value = blue
+ optional = green
+ spellcheckline = red
+ errorline = green
+ replacetag = green
+ modifier = green
+ texthighlight = #c8c80060
-The Palette values correspond to the Qt enum values for ``QPalette::ColorRole``, see the
-`Qt documentation `_ for more details. The
-colour values are RGB numbers on the format ``r, g, b`` where each is an integer from ``0`` to
-``255``. Omitted values are not loaded and will use default values. If the ``helptext`` colour is
-not defined, it is computed as a colour between the ``window`` and ``windowtext`` colour.
-Additional shades of some of the colours are also computed. These are mainly used for 3D effects.
+
+Theme Sections
+--------------
+
+.. _ColorRole: https://doc.qt.io/qt-6/qpalette.html#ColorRole-enum
+
+The theme file is made up of different sections depending on what part of novelWriter the theme
+affects.
+
+.. csv-table:: Theme Sections Overview
+ :header: "Section", "Description"
+ :class: "tight-table"
+
+ "``[Main]``", "Meta data about the theme, You must at least set ``name`` and ``mode``, and ``mode`` must be either ``light`` or ``dark``."
+ "``[Base]``", "The base colours of the theme. These are also selectable colours in various places inside the app, like for icon colours in **Preferences**."
+ "``[Project]``", "The colours used for icons and markers for the different project item types."
+ "``[Palette]``", "The colours used for styling the user interface. The values correspond to the ColorRole_ values in the Qt library."
+ "``[GUI]``", "The colours used for styling additional elements of the user interface."
+ "``[Syntax]``", "The colours used for syntax highlighting in documents."
+
+
+Colour Value Formats
+--------------------
+
+There are several ways to enter colour values:
+
+.. csv-table:: Colour Formats
+ :header: "Syntax", "Description"
+ :class: "tight-table"
+
+ "``#RRGGBB``", "A CSS style hexadecimal values, like ``#ff0000`` for red."
+ "``#RRGGBBAA``", "A CSS style hexadecimal values with transparency, like ``#ff00007f`` for half-transparent red."
+ "``name``", "A name referring to one of the colours already specified under the ``[Base]`` section, like ``red``. Note that you should not use named colours in the ``[Base]`` section itself as that may have unintended results."
+ "``name:alpha``", "A name referring to one of the colours already specified under the ``[Base]`` section, with a transparency value added. The alpha value must be in the range ``0`` to ``255``, like ``red:127`` for half-transparent red."
+ "``r, g, b``", "A set of red, green and blue numbers in the range ``0`` to ``255``, like ``255, 0, 0`` for red."
+ "``r, g, b, a``", "A set of red, green, blue and alpha numbers in the range ``0`` to ``255``, like ``255, 0, 0, 127`` for half-transparent red."
.. versionadded:: 2.5
The ``fadedtext`` and ``errortext`` theme colour entries were added.
@@ -172,60 +165,84 @@ Additional shades of some of the colours are also computed. These are mainly use
The ``icontheme`` setting was dropped as the icon theme is now its own setting.
The ``[Icons]`` and ``[Project]`` sections were added, and the ``status*`` settings removed.
+.. versionadded:: 2.8
+ The ``[Syntax]`` section was moved into the main theme file. Previously, these settings were in
+ their own file. The ``[Icons]`` section was renamed to ``[Base]``.
-Custom Syntax Theme
--------------------
-A syntax theme ``.conf`` file consists of the following settings:
+Icon Themes
+===========
+
+Icon themes are *not* straightforward to add, but if you want to make the effort, this section
+describes how to do it.
+
+The existing icon themes are stored in
+`novelwriter/assets/icons `_.
+
+As with colour themes, remember to change the name of your theme by modifying the ``name`` setting
+at the top of the file, otherwise you may not be able to distinguish them in **Preferences**.
+
+For novelWriter to be able to locate the custom theme files, you must copy them to the
+:ref:`docs_technical_locations_data` location in your home or user area. There should be a folder
+there ``icons`` for icon themes. These folders are created the first time you start novelWriter.
+
+
+The Icons File Format
+---------------------
+
+Icon themes are kept in files with the ``.icons`` file extension. The file format is a custom
+format with entries on the form ``section:key = value``.
.. code-block:: cfg
+ :caption: The icons file for "Material Symbols - Rounded Medium" (truncated)
- [Main]
- name = My Syntax Theme
- author = Jane Doe
- credit = John Doe
- url = https://example.com
- license = CC BY-SA 4.0
- licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
+ # Meta
+ meta:name = Material Symbols - Rounded Medium
+ meta:author = Google Inc
+ meta:license = Apache 2.0
- [Syntax]
- background = 255, 255, 255
- text = 0, 0, 0
- link = 0, 0, 0
- headertext = 0, 0, 0
- headertag = 0, 0, 0
- emphasis = 0, 0, 0
- dialog = 0, 0, 0
- altdialog = 0, 0, 0
- note = 0, 0, 0
- hidden = 0, 0, 0
- shortcode = 0, 0, 0
- keyword = 0, 0, 0
- tag = 0, 0, 0
- value = 0, 0, 0
- optional = 0, 0, 0
- spellcheckline = 0, 0, 0
- errorline = 0, 0, 0
- replacetag = 0, 0, 0
- modifier = 0, 0, 0
- texthighlight = 255, 255, 255, 128
+ # Icons
+ icon:alert_error =