Remove syntax theme parsing and add light and dark theme user settings
This commit is contained in:
@@ -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
@@ -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),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user