Remove syntax theme parsing and add light and dark theme user settings

This commit is contained in:
Veronica Berglyd Olsen
2025-06-02 16:28:06 +02:00
parent e67ae4f3bf
commit 1fafa0dae7
6 changed files with 148 additions and 163 deletions
+11
View File
@@ -31,6 +31,7 @@ import xml.etree.ElementTree as ET
from configparser import ConfigParser from configparser import ConfigParser
from datetime import datetime from datetime import datetime
from enum import Enum
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal, TypeGuard, TypeVar from typing import TYPE_CHECKING, Any, Literal, TypeGuard, TypeVar
from urllib.parse import urljoin from urllib.parse import urljoin
@@ -676,6 +677,9 @@ def openExternalPath(path: Path) -> bool:
# Classes # Classes
## ##
_T_Enum = TypeVar("_T_Enum", bound=Enum)
class NWConfigParser(ConfigParser): class NWConfigParser(ConfigParser):
"""Common: Adapted Config Parser """Common: Adapted Config Parser
@@ -736,3 +740,10 @@ class NWConfigParser(ConfigParser):
for i in range(min(len(data), len(result))): for i in range(min(len(data), len(result))):
result[i] = checkInt(data[i].strip(), result[i]) result[i] = checkInt(data[i].strip(), result[i])
return result 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
+28 -24
View File
@@ -45,6 +45,7 @@ from novelwriter.common import (
formatTimeStamp, processDialogSymbols, simplified formatTimeStamp, processDialogSymbols, simplified
) )
from novelwriter.constants import nwFiles, nwHtmlUnicode, nwQuotes, nwUnicode from novelwriter.constants import nwFiles, nwHtmlUnicode, nwQuotes, nwUnicode
from novelwriter.enum import nwTheme
from novelwriter.error import formatException, logException from novelwriter.error import formatException, logException
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -55,8 +56,8 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
DEF_GUI = "default" DEF_GUI_DARK = "default_dark"
DEF_SYNTAX = "default_light" DEF_GUI_LIGHT = "default_light"
DEF_ICONS = "material_rounded_normal" DEF_ICONS = "material_rounded_normal"
DEF_TREECOL = "theme" DEF_TREECOL = "theme"
@@ -69,22 +70,22 @@ class Config:
"_manuals", "_nwLangPath", "_qLocale", "_qtLangPath", "_qtTrans", "_recentPaths", "_manuals", "_nwLangPath", "_qLocale", "_qtLangPath", "_qtTrans", "_recentPaths",
"_recentProjects", "_splash", "allowOpenDial", "altDialogClose", "altDialogOpen", "_recentProjects", "_splash", "allowOpenDial", "altDialogClose", "altDialogOpen",
"appHandle", "appName", "askBeforeBackup", "askBeforeExit", "autoSaveDoc", "autoSaveProj", "appHandle", "appName", "askBeforeBackup", "askBeforeExit", "autoSaveDoc", "autoSaveProj",
"autoScroll", "autoScrollPos", "autoSelect", "backupOnClose", "cursorWidth", "dialogLine", "autoScroll", "autoScrollPos", "autoSelect", "backupOnClose", "cursorWidth", "darkTheme",
"dialogStyle", "doJustify", "doReplace", "doReplaceDQuote", "doReplaceDash", "dialogLine", "dialogStyle", "doJustify", "doReplace", "doReplaceDQuote", "doReplaceDash",
"doReplaceDots", "doReplaceSQuote", "emphLabels", "fmtApostrophe", "fmtDQuoteClose", "doReplaceDots", "doReplaceSQuote", "emphLabels", "fmtApostrophe", "fmtDQuoteClose",
"fmtDQuoteOpen", "fmtPadAfter", "fmtPadBefore", "fmtPadThin", "fmtSQuoteClose", "fmtDQuoteOpen", "fmtPadAfter", "fmtPadBefore", "fmtPadThin", "fmtSQuoteClose",
"fmtSQuoteOpen", "focusWidth", "guiFont", "guiLocale", "guiSyntax", "guiTheme", "fmtSQuoteOpen", "focusWidth", "guiFont", "guiLocale", "hasEnchant", "hideFocusFooter",
"hasEnchant", "hideFocusFooter", "hideHScroll", "hideVScroll", "highlightEmph", "hostName", "hideHScroll", "hideVScroll", "highlightEmph", "hostName", "iconColDocs", "iconColTree",
"iconColDocs", "iconColTree", "iconTheme", "incNotesWCount", "isDebug", "kernelVer", "iconTheme", "incNotesWCount", "isDebug", "kernelVer", "lastNotes", "lightTheme",
"lastNotes", "mainPanePos", "mainWinSize", "memInfo", "narratorBreak", "narratorDialog", "mainPanePos", "mainWinSize", "memInfo", "narratorBreak", "narratorDialog", "nativeFont",
"nativeFont", "osDarwin", "osLinux", "osType", "osUnknown", "osWindows", "outlinePanePos", "osDarwin", "osLinux", "osType", "osUnknown", "osWindows", "outlinePanePos",
"prefsWinSize", "scrollPastEnd", "searchCase", "searchLoop", "searchMatchCap", "prefsWinSize", "scrollPastEnd", "searchCase", "searchLoop", "searchMatchCap",
"searchNextFile", "searchProjCase", "searchProjRegEx", "searchProjWord", "searchRegEx", "searchNextFile", "searchProjCase", "searchProjRegEx", "searchProjWord", "searchRegEx",
"searchWord", "showEditToolBar", "showFullPath", "showLineEndings", "showMultiSpaces", "searchWord", "showEditToolBar", "showFullPath", "showLineEndings", "showMultiSpaces",
"showSessionTime", "showTabsNSpaces", "showViewerPanel", "spellLanguage", "stopWhenIdle", "showSessionTime", "showTabsNSpaces", "showViewerPanel", "spellLanguage", "stopWhenIdle",
"tabWidth", "textFont", "textMargin", "textWidth", "useCharCount", "userIdleTime", "tabWidth", "textFont", "textMargin", "textWidth", "themeMode", "useCharCount",
"verPyQtString", "verPyQtValue", "verPyString", "verQtString", "verQtValue", "userIdleTime", "verPyQtString", "verPyQtValue", "verPyString", "verQtString",
"viewComments", "viewPanePos", "viewSynopsis", "welcomeWinSize", "verQtValue", "viewComments", "viewPanePos", "viewSynopsis", "welcomeWinSize",
) )
LANG_NW = 1 LANG_NW = 1
@@ -153,14 +154,15 @@ class Config:
# General GUI Settings # General GUI Settings
self.guiLocale = self._qLocale.name() self.guiLocale = self._qLocale.name()
self.guiTheme = DEF_GUI # GUI theme self.lightTheme = DEF_GUI_LIGHT # Light GUI theme
self.guiSyntax = DEF_SYNTAX # Syntax theme self.darkTheme = DEF_GUI_DARK # Dark GUI theme
self.guiFont = QFont() # Main GUI font self.themeMode = nwTheme.AUTO # Colour theme mode
self.hideVScroll = False # Hide vertical scroll bars on main widgets self.guiFont = QFont() # Main GUI font
self.hideHScroll = False # Hide horizontal scroll bars on main widgets self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.lastNotes = "0x0" # The latest release notes that have been shown self.hideHScroll = False # Hide horizontal scroll bars on main widgets
self.nativeFont = True # Use native font dialog self.lastNotes = "0x0" # The latest release notes that have been shown
self.useCharCount = False # Use character count as primary count self.nativeFont = True # Use native font dialog
self.useCharCount = False # Use character count as primary count
# Icons # Icons
self.iconTheme = DEF_ICONS # Icons theme self.iconTheme = DEF_ICONS # Icons theme
@@ -626,8 +628,9 @@ class Config:
# Main # Main
sec = "Main" sec = "Main"
self.setGuiFont(conf.rdStr(sec, "font", "")) self.setGuiFont(conf.rdStr(sec, "font", ""))
self.guiTheme = conf.rdStr(sec, "theme", self.guiTheme) self.lightTheme = conf.rdStr(sec, "lighttheme", self.lightTheme)
self.guiSyntax = conf.rdStr(sec, "syntax", self.guiSyntax) 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.iconTheme = conf.rdStr(sec, "icons", self.iconTheme)
self.iconColTree = conf.rdStr(sec, "iconcoltree", self.iconColTree) self.iconColTree = conf.rdStr(sec, "iconcoltree", self.iconColTree)
self.iconColDocs = conf.rdBool(sec, "iconcoldocs", self.iconColDocs) self.iconColDocs = conf.rdBool(sec, "iconcoldocs", self.iconColDocs)
@@ -751,8 +754,9 @@ class Config:
conf["Main"] = { conf["Main"] = {
"font": self.guiFont.toString(), "font": self.guiFont.toString(),
"theme": str(self.guiTheme), "lighttheme": str(self.lightTheme),
"syntax": str(self.guiSyntax), "darktheme": str(self.darkTheme),
"thememode": self.themeMode.name,
"icons": str(self.iconTheme), "icons": str(self.iconTheme),
"iconcoltree": str(self.iconColTree), "iconcoltree": str(self.iconColTree),
"iconcoldocs": str(self.iconColDocs), "iconcoldocs": str(self.iconColDocs),
+25 -27
View File
@@ -35,7 +35,7 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import compact, describeFont, processDialogSymbols, uniqueCompact 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.constants import nwLabels, nwQuotes, nwUnicode, trConst
from novelwriter.dialogs.quotes import GuiQuoteSelect from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.extensions.configlayout import NColorLabel, NScrollableForm from novelwriter.extensions.configlayout import NColorLabel, NScrollableForm
@@ -165,14 +165,24 @@ class GuiPreferences(NDialog):
) )
# Colour Theme # Colour Theme
self.guiTheme = NComboBox(self) self.lightTheme = NComboBox(self)
self.guiTheme.setMinimumWidth(200) self.lightTheme.setMinimumWidth(200)
for theme, name in SHARED.theme.listThemes(): self.darkTheme = NComboBox(self)
self.guiTheme.addItem(name, theme) self.darkTheme.setMinimumWidth(200)
self.guiTheme.setCurrentData(CONFIG.guiTheme, DEF_GUI) 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.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) self.tr("User interface colour theme."), stretch=(3, 2)
) )
@@ -242,18 +252,6 @@ class GuiPreferences(NDialog):
self.sidebar.addButton(title, section) self.sidebar.addButton(title, section)
self.mainForm.addGroupLabel(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 # Document Font Family
self.textFont = QLineEdit(self) self.textFont = QLineEdit(self)
self.textFont.setReadOnly(True) self.textFont.setReadOnly(True)
@@ -966,18 +964,23 @@ class GuiPreferences(NDialog):
# Appearance # Appearance
guiLocale = self.guiLocale.currentData() guiLocale = self.guiLocale.currentData()
guiTheme = self.guiTheme.currentData() lightTheme = self.lightTheme.currentData()
darkTheme = self.darkTheme.currentData()
iconTheme = self.iconTheme.currentData() iconTheme = self.iconTheme.currentData()
useCharCount = self.useCharCount.isChecked() useCharCount = self.useCharCount.isChecked()
updateTheme |= CONFIG.guiTheme != guiTheme updateTheme |= CONFIG.lightTheme != lightTheme
updateTheme |= CONFIG.darkTheme != darkTheme
updateTheme |= CONFIG.iconTheme != iconTheme updateTheme |= CONFIG.iconTheme != iconTheme
needsRestart |= CONFIG.guiLocale != guiLocale needsRestart |= CONFIG.guiLocale != guiLocale
needsRestart |= CONFIG.guiFont != self._guiFont needsRestart |= CONFIG.guiFont != self._guiFont
refreshTree |= CONFIG.useCharCount != useCharCount refreshTree |= CONFIG.useCharCount != useCharCount
updateSyntax |= CONFIG.lightTheme != lightTheme
updateSyntax |= CONFIG.darkTheme != darkTheme
CONFIG.guiLocale = guiLocale CONFIG.guiLocale = guiLocale
CONFIG.guiTheme = guiTheme CONFIG.lightTheme = lightTheme
CONFIG.darkTheme = darkTheme
CONFIG.iconTheme = iconTheme CONFIG.iconTheme = iconTheme
CONFIG.hideVScroll = self.hideVScroll.isChecked() CONFIG.hideVScroll = self.hideVScroll.isChecked()
CONFIG.hideHScroll = self.hideHScroll.isChecked() CONFIG.hideHScroll = self.hideHScroll.isChecked()
@@ -986,11 +989,6 @@ class GuiPreferences(NDialog):
CONFIG.setGuiFont(self._guiFont) CONFIG.setGuiFont(self._guiFont)
# Document Style # Document Style
guiSyntax = self.guiSyntax.currentData()
updateSyntax |= CONFIG.guiSyntax != guiSyntax
CONFIG.guiSyntax = guiSyntax
CONFIG.showFullPath = self.showFullPath.isChecked() CONFIG.showFullPath = self.showFullPath.isChecked()
CONFIG.incNotesWCount = self.incNotesWCount.isChecked() CONFIG.incNotesWCount = self.incNotesWCount.isChecked()
CONFIG.setTextFont(self._textFont) CONFIG.setTextFont(self._textFont)
+7
View File
@@ -156,6 +156,13 @@ class nwFocus(Enum):
OUTLINE = 3 OUTLINE = 3
class nwTheme(Enum):
AUTO = 0
LIGHT = 1
DARK = 2
class nwOutline(Enum): class nwOutline(Enum):
TITLE = 0 TITLE = 0
+77 -111
View File
@@ -31,16 +31,16 @@ from typing import TYPE_CHECKING, Final
from PyQt6.QtCore import QSize, Qt from PyQt6.QtCore import QSize, Qt
from PyQt6.QtGui import ( from PyQt6.QtGui import (
QColor, QFont, QFontDatabase, QFontMetrics, QIcon, QPainter, QPainterPath, QColor, QFont, QFontDatabase, QFontMetrics, QGuiApplication, QIcon,
QPalette, QPixmap QPainter, QPainterPath, QPalette, QPixmap
) )
from PyQt6.QtWidgets import QApplication from PyQt6.QtWidgets import QApplication
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.common import NWConfigParser, minmax 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.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.error import logException
from novelwriter.types import QtBlack, QtHexArgb, QtPaintAntiAlias, QtTransparent from novelwriter.types import QtBlack, QtHexArgb, QtPaintAntiAlias, QtTransparent
@@ -49,6 +49,8 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
T_ThemeEntry = tuple[str, str, bool]
STYLES_FLAT_TABS = "flatTabWidget" STYLES_FLAT_TABS = "flatTabWidget"
STYLES_MIN_TOOLBUTTON = "minimalToolButton" STYLES_MIN_TOOLBUTTON = "minimalToolButton"
STYLES_BIG_TOOLBUTTON = "bigToolButton" STYLES_BIG_TOOLBUTTON = "bigToolButton"
@@ -57,6 +59,7 @@ STYLES_BIG_TOOLBUTTON = "bigToolButton"
class ThemeMeta: class ThemeMeta:
name: str = "" name: str = ""
mode: str = ""
description: str = "" description: str = ""
author: str = "" author: str = ""
credit: str = "" credit: str = ""
@@ -96,13 +99,13 @@ class GuiTheme:
""" """
__slots__ = ( __slots__ = (
"_availSyntax", "_availThemes", "_guiPalette", "_styleSheets", "_syntaxList", "_themeList", "_availSyntax", "_availThemes", "_darkThemes", "_guiPalette", "_lightThemes",
"baseButtonHeight", "baseIconHeight", "baseIconSize", "buttonIconSize", "errorText", "_styleSheets", "_syntaxList", "_themeList", "baseButtonHeight", "baseIconHeight",
"fadedText", "fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration", "baseIconSize", "buttonIconSize", "errorText", "fadedText", "fontPixelSize",
"getHeaderDecorationNarrow", "getIcon", "getIconColor", "getItemIcon", "getPixmap", "fontPointSize", "getDecoration", "getHeaderDecoration", "getHeaderDecorationNarrow",
"getToggleIcon", "guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", "getIcon", "getIconColor", "getItemIcon", "getPixmap", "getToggleIcon", "guiFont",
"helpText", "iconCache", "isDarkTheme", "syntaxMeta", "syntaxTheme", "textNHeight", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", "helpText", "iconCache",
"textNWidth", "themeMeta", "isDarkTheme", "syntaxMeta", "syntaxTheme", "textNHeight", "textNWidth", "themeMeta",
) )
def __init__(self) -> None: def __init__(self) -> None:
@@ -124,8 +127,7 @@ class GuiTheme:
# Load Themes # Load Themes
self._guiPalette = QPalette() self._guiPalette = QPalette()
self._themeList: list[tuple[str, str]] = [] self._themeList: list[T_ThemeEntry] = []
self._syntaxList: list[tuple[str, str]] = []
self._availThemes: dict[str, Path] = {} self._availThemes: dict[str, Path] = {}
self._availSyntax: dict[str, Path] = {} self._availSyntax: dict[str, Path] = {}
self._styleSheets: dict[str, str] = {} self._styleSheets: dict[str, str] = {}
@@ -179,13 +181,10 @@ class GuiTheme:
logger.debug("Text 'N' Width: %d", self.textNWidth) logger.debug("Text 'N' Width: %d", self.textNWidth)
# Process Themes # Process Themes
_listConf(self._availSyntax, CONFIG.assetPath("syntax"), ".conf")
_listConf(self._availThemes, CONFIG.assetPath("themes"), ".conf") _listConf(self._availThemes, CONFIG.assetPath("themes"), ".conf")
_listConf(self._availSyntax, CONFIG.dataPath("syntax"), ".conf")
_listConf(self._availThemes, CONFIG.dataPath("themes"), ".conf") _listConf(self._availThemes, CONFIG.dataPath("themes"), ".conf")
self.loadTheme() self.loadTheme()
self.loadSyntax()
return return
@@ -207,13 +206,35 @@ class GuiTheme:
# Theme Methods # 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: def loadTheme(self) -> bool:
"""Load the currently specified GUI theme.""" """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: if theme not in self._availThemes:
logger.error("Could not find GUI theme '%s'", theme) logger.error("Could not find GUI theme '%s'", theme)
theme = DEF_GUI if darkMode:
CONFIG.guiTheme = theme 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)): if not (file := self._availThemes.get(theme)):
logger.error("Could not load GUI theme") logger.error("Could not load GUI theme")
@@ -238,6 +259,7 @@ class GuiTheme:
meta = ThemeMeta() meta = ThemeMeta()
if parser.has_section(sec): if parser.has_section(sec):
meta.name = parser.rdStr(sec, "name", "") meta.name = parser.rdStr(sec, "name", "")
meta.mode = parser.rdStr(sec, "mode", "light")
meta.description = parser.rdStr(sec, "description", "N/A") meta.description = parser.rdStr(sec, "description", "N/A")
meta.author = parser.rdStr(sec, "author", "N/A") meta.author = parser.rdStr(sec, "author", "N/A")
meta.credit = parser.rdStr(sec, "credit", "N/A") meta.credit = parser.rdStr(sec, "credit", "N/A")
@@ -296,6 +318,31 @@ class GuiTheme:
self.fadedText = self._parseColor(parser, sec, "fadedtext") self.fadedText = self._parseColor(parser, sec, "fadedtext")
self.errorText = self._parseColor(parser, sec, "errortext") 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 # Update Dependant Colours
# Based on: https://github.com/qt/qtbase/blob/dev/src/gui/kernel/qplatformtheme.cpp # Based on: https://github.com/qt/qtbase/blob/dev/src/gui/kernel/qplatformtheme.cpp
text = self._guiPalette.text().color() text = self._guiPalette.text().color()
@@ -363,105 +410,22 @@ class GuiTheme:
return True return True
def loadSyntax(self) -> bool: def listThemes(self) -> list[T_ThemeEntry]:
"""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]]:
"""Scan the GUI themes folder and list all themes.""" """Scan the GUI themes folder and list all themes."""
if self._themeList: if self._themeList:
return self._themeList return self._themeList
themes = [] themes: list[T_ThemeEntry] = []
parser = NWConfigParser() parser = NWConfigParser()
for key, path in self._availThemes.items(): for key, path in self._availThemes.items():
logger.debug("Checking theme config '%s'", key) logger.debug("Checking theme config '%s'", key)
if name := _loadInternalName(parser, path): if meta := _loadInternalName(parser, path):
themes.append((key, name)) themes.append((key, meta[0], meta[1]))
self._themeList = sorted(themes, key=_sortTheme) self._themeList = sorted(themes, key=_sortTheme)
return self._themeList 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: def getStyleSheet(self, name: str) -> str:
"""Load a standard style sheet.""" """Load a standard style sheet."""
return self._styleSheets.get(name, "") return self._styleSheets.get(name, "")
@@ -889,22 +853,24 @@ def _listConf(target: dict, path: Path, extension: str) -> None:
return return
def _sortTheme(data: tuple[str, str]) -> str: def _sortTheme(data: tuple) -> str:
"""Key function for theme sorting.""" """Key function for theme sorting."""
key, name = data key, name = data[:2]
return f"*{name}" if key.startswith("default_") else name 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.""" """Open a conf file and read the 'name' setting."""
try: try:
with open(path, mode="r", encoding="utf-8") as inFile: with open(path, mode="r", encoding="utf-8") as inFile:
parser.read_file(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: except Exception:
logger.error("Could not read file: %s", path) logger.error("Could not read file: %s", path)
logException() logException()
return "" return "", False
def _loadIconName(path: Path) -> str: def _loadIconName(path: Path) -> str:
-1
View File
@@ -1069,7 +1069,6 @@ class GuiMain(QMainWindow):
SHARED.project.tree.refreshAllItems() SHARED.project.tree.refreshAllItems()
if syntax: if syntax:
SHARED.theme.loadSyntax()
self.docEditor.updateSyntaxColors() self.docEditor.updateSyntaxColors()
self.docEditor.initEditor() self.docEditor.initEditor()