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()