diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index 9172abd90..b0c102c84 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -216,29 +216,28 @@ def main(sysArgs: list | None = None) -> GuiMain | None: # Import GUI (after dependency checks), and launch from novelwriter.guimain import GuiMain - if testMode: - nwGUI = GuiMain() - return nwGUI - else: - nwApp = QApplication([CONFIG.appName, (f"-style={qtStyle}")]) - nwApp.setApplicationName(CONFIG.appName) - nwApp.setApplicationVersion(__version__) - nwApp.setOrganizationDomain(__domain__) - nwApp.setOrganizationName(__domain__) - nwApp.setDesktopFileName(CONFIG.appName) + if testMode: + # Only used for testing where the test framework creates the app + CONFIG.loadConfig() + return GuiMain() - # Connect the exception handler before making the main GUI - sys.excepthook = exceptionHandler + app = QApplication([CONFIG.appName, (f"-style={qtStyle}")]) + app.setApplicationName(CONFIG.appName) + app.setApplicationVersion(__version__) + app.setOrganizationDomain(__domain__) + app.setOrganizationName(__domain__) + app.setDesktopFileName(CONFIG.appName) - # Run Config steps that require the QApplication - CONFIG.initLocalisation(nwApp) - CONFIG.setTextFont(CONFIG.textFont, CONFIG.textSize) # Makes sure these are valid + # Connect the exception handler before making the main GUI + sys.excepthook = exceptionHandler - # Launch main GUI - nwGUI = GuiMain() - nwGUI.postLaunchTasks(cmdOpen) + # Run Config steps that require the QApplication + CONFIG.loadConfig() + CONFIG.initLocalisation(app) - sys.exit(nwApp.exec()) + # Launch main GUI + nwGUI = GuiMain() + nwGUI.postLaunchTasks(cmdOpen) - return None + sys.exit(app.exec()) diff --git a/novelwriter/common.py b/novelwriter/common.py index b12828673..70c4ce273 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -37,7 +37,7 @@ from urllib.request import pathname2url from PyQt5.QtCore import QCoreApplication, QUrl -from PyQt5.QtGui import QColor, QDesktopServices +from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontInfo from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType @@ -401,6 +401,14 @@ def cssCol(col: QColor, alpha: int | None = None) -> str: return f"rgba({col.red()}, {col.green()}, {col.blue()}, {alpha or col.alpha()})" +def describeFont(font: QFont) -> str: + """Describe a font in a way that can be displayed on the GUI.""" + if isinstance(font, QFont): + info = QFontInfo(font) + return f"{font.family()} {info.styleName()} @ {font.pointSize()} pt" + return "Error" + + ## # Encoder Functions ## diff --git a/novelwriter/config.py b/novelwriter/config.py index af21e7ddd..2bff5d2bc 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -36,10 +36,10 @@ PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo, QLocale, QStandardPaths, QSysInfo, QTranslator ) -from PyQt5.QtGui import QFontDatabase +from PyQt5.QtGui import QFont, QFontDatabase from PyQt5.QtWidgets import QApplication -from novelwriter.common import NWConfigParser, checkInt, checkPath, formatTimeStamp +from novelwriter.common import NWConfigParser, checkInt, checkPath, describeFont, formatTimeStamp from novelwriter.constants import nwFiles, nwUnicode from novelwriter.error import formatException, logException @@ -106,15 +106,14 @@ def __init__(self) -> None: self._recentObj = RecentProjects(self) # General GUI Settings - self.guiLocale = self._qLocale.name() - self.guiTheme = "default" # GUI theme - self.guiSyntax = "default_light" # Syntax theme - self.guiFont = "" # Defaults to system default font in theme class - self.guiFontSize = 11 # Is overridden if system default is loaded - self.guiScale = 1.0 # Set automatically by Theme class - 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.guiLocale = self._qLocale.name() + self.guiTheme = "default" # GUI theme + self.guiSyntax = "default_light" # Syntax theme + self.guiFont = QFont() # Main GUI font + self.guiScale = 1.0 # Set automatically by Theme class + 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 # Size Settings self._mainWinSize = [1200, 650] # Last size of the main GUI window @@ -132,43 +131,42 @@ def __init__(self) -> None: self.askBeforeBackup = True # Flag for asking before running automatic backup # Text Editor Settings - self.textFont = "" # Editor font - self.textSize = 12 # Editor font size - self.textWidth = 700 # Editor text width - self.textMargin = 40 # Editor/viewer text margin - self.tabWidth = 40 # Editor tabulator width - - self.focusWidth = 800 # Focus Mode text width - self.hideFocusFooter = False # Hide document footer in Focus Mode - self.showFullPath = True # Show full document path in editor header - self.autoSelect = True # Auto-select word when applying format with no selection - - self.doJustify = False # Justify text - self.showTabsNSpaces = False # Show tabs and spaces in editor - self.showLineEndings = False # Show line endings in editor - self.showMultiSpaces = True # Highlight multiple spaces in the text - - self.doReplace = True # Enable auto-replace as you type - self.doReplaceSQuote = True # Smart single quotes - self.doReplaceDQuote = True # Smart double quotes - self.doReplaceDash = True # Replace multiple hyphens with dashes - self.doReplaceDots = True # Replace three dots with ellipsis - - self.autoScroll = False # Typewriter-like scrolling - self.autoScrollPos = 30 # Start point for typewriter-like scrolling - self.scrollPastEnd = True # Scroll past end of document, and centre cursor - - self.dialogStyle = 2 # Quote type to use for dialogue - self.allowOpenDial = True # Allow open-ended dialogue quotes - self.narratorBreak = "" # Symbol to use for narrator break - self.dialogLine = "" # Symbol to use for dialogue line - self.altDialogOpen = "" # Alternative dialog symbol, open - self.altDialogClose = "" # Alternative dialog symbol, close - self.highlightEmph = True # Add colour to text emphasis - - self.stopWhenIdle = True # Stop the status bar clock when the user is idle - self.userIdleTime = 300 # Time of inactivity to consider user idle - self.incNotesWCount = True # The status bar word count includes notes + self.textFont = QFont() # Editor font + self.textWidth = 700 # Editor text width + self.textMargin = 40 # Editor/viewer text margin + self.tabWidth = 40 # Editor tabulator width + + self.focusWidth = 800 # Focus Mode text width + self.hideFocusFooter = False # Hide document footer in Focus Mode + self.showFullPath = True # Show full document path in editor header + self.autoSelect = True # Auto-select word when applying format with no selection + + self.doJustify = False # Justify text + self.showTabsNSpaces = False # Show tabs and spaces in editor + self.showLineEndings = False # Show line endings in editor + self.showMultiSpaces = True # Highlight multiple spaces in the text + + self.doReplace = True # Enable auto-replace as you type + self.doReplaceSQuote = True # Smart single quotes + self.doReplaceDQuote = True # Smart double quotes + self.doReplaceDash = True # Replace multiple hyphens with dashes + self.doReplaceDots = True # Replace three dots with ellipsis + + self.autoScroll = False # Typewriter-like scrolling + self.autoScrollPos = 30 # Start point for typewriter-like scrolling + self.scrollPastEnd = True # Scroll past end of document, and centre cursor + + self.dialogStyle = 2 # Quote type to use for dialogue + self.allowOpenDial = True # Allow open-ended dialogue quotes + self.narratorBreak = "" # Symbol to use for narrator break + self.dialogLine = "" # Symbol to use for dialogue line + self.altDialogOpen = "" # Alternative dialog symbol, open + self.altDialogClose = "" # Alternative dialog symbol, close + self.highlightEmph = True # Add colour to text emphasis + + self.stopWhenIdle = True # Stop the status bar clock when the user is idle + self.userIdleTime = 300 # Time of inactivity to consider user idle + self.incNotesWCount = True # The status bar word count includes notes # User-Selected Symbol Settings self.fmtApostrophe = nwUnicode.U_RSQUO @@ -362,23 +360,53 @@ def setBackupPath(self, path: Path | str) -> None: self._backupPath = checkPath(path, self._backPath) return - def setTextFont(self, family: str | None, pointSize: int = 12) -> None: + def setGuiFont(self, value: QFont | str | None) -> None: + """Update the GUI's font style from settings.""" + if isinstance(value, QFont): + self.guiFont = value + elif value and isinstance(value, str): + self.guiFont = QFont() + self.guiFont.fromString(value) + else: + font = QFont() + fontDB = QFontDatabase() + if self.osWindows and "Arial" in fontDB.families(): + # On Windows we default to Arial if possible + font.setFamily("Arial") + font.setPointSize(10) + else: + font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont) + self.guiFont = font + logger.debug("GUI font set to: %s", describeFont(font)) + + QApplication.setFont(self.guiFont) + + return + + def setTextFont(self, value: QFont | str | None) -> None: """Set the text font if it exists. If it doesn't, or is None, set to default font. """ - fontDB = QFontDatabase() - fontFam = fontDB.families() - self.textSize = pointSize - if family is None or family not in fontFam: - logger.warning("Unknown font '%s'", family) + if isinstance(value, QFont): + self.textFont = value + elif value and isinstance(value, str): + self.textFont = QFont() + self.textFont.fromString(value) + else: + fontDB = QFontDatabase() + fontFam = fontDB.families() if self.osWindows and "Arial" in fontFam: - self.textFont = "Arial" + font = QFont() + font.setFamily("Arial") + font.setPointSize(12) elif self.osDarwin and "Helvetica" in fontFam: - self.textFont = "Helvetica" + font = QFont() + font.setFamily("Helvetica") + font.setPointSize(12) else: - self.textFont = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont).family() - else: - self.textFont = family + font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont) + self.textFont = font + logger.debug("Text font set to: %s", describeFont(font)) return ## @@ -502,12 +530,6 @@ def initConfig(self, confPath: str | Path | None = None, (self._dataPath / "syntax").mkdir(exist_ok=True) (self._dataPath / "themes").mkdir(exist_ok=True) - # Check if config file exists, and load it. If not, we save defaults - if (self._confPath / nwFiles.CONF_FILE).is_file(): - self.loadConfig() - else: - self.saveConfig() - self._recentObj.loadCache() self._checkOptionalPackages() @@ -548,6 +570,14 @@ def loadConfig(self) -> bool: conf = NWConfigParser() cnfPath = self._confPath / nwFiles.CONF_FILE + + if not cnfPath.exists(): + # Initial file, so we just create one from defaults + self.setGuiFont(None) + self.setTextFont(None) + self.saveConfig() + return True + try: with open(cnfPath, mode="r", encoding="utf-8") as inFile: conf.read_file(inFile) @@ -561,10 +591,9 @@ def loadConfig(self) -> bool: # 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.guiFont = conf.rdStr(sec, "font", self.guiFont) - self.guiFontSize = conf.rdInt(sec, "fontsize", self.guiFontSize) self.guiLocale = conf.rdStr(sec, "localisation", self.guiLocale) self.hideVScroll = conf.rdBool(sec, "hidevscroll", self.hideVScroll) self.hideHScroll = conf.rdBool(sec, "hidehscroll", self.hideHScroll) @@ -591,8 +620,7 @@ def loadConfig(self) -> bool: # Editor sec = "Editor" - self.textFont = conf.rdStr(sec, "textfont", self.textFont) - self.textSize = conf.rdInt(sec, "textsize", self.textSize) + self.setTextFont(conf.rdStr(sec, "textfont", "")) self.textWidth = conf.rdInt(sec, "width", self.textWidth) self.textMargin = conf.rdInt(sec, "margin", self.textMargin) self.tabWidth = conf.rdInt(sec, "tabwidth", self.tabWidth) @@ -672,10 +700,9 @@ def saveConfig(self) -> bool: } conf["Main"] = { + "font": self.guiFont.toString(), "theme": str(self.guiTheme), "syntax": str(self.guiSyntax), - "font": str(self.guiFont), - "fontsize": str(self.guiFontSize), "localisation": str(self.guiLocale), "hidevscroll": str(self.hideVScroll), "hidehscroll": str(self.hideHScroll), @@ -702,8 +729,7 @@ def saveConfig(self) -> bool: } conf["Editor"] = { - "textfont": str(self.textFont), - "textsize": str(self.textSize), + "textfont": self.textFont.toString(), "width": str(self.textWidth), "margin": str(self.textMargin), "tabwidth": str(self.tabWidth), diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py index 13f4cc078..f7392e26b 100644 --- a/novelwriter/core/buildsettings.py +++ b/novelwriter/core/buildsettings.py @@ -76,7 +76,7 @@ "text.includeBodyText": (bool, True), "text.ignoredKeywords": (str, ""), "text.addNoteHeadings": (bool, True), - "format.textFont": (str, CONFIG.textFont), + "format.textFont": (str, CONFIG.textFont.family()), "format.textSize": (int, 12), "format.lineHeight": (float, 1.15, 0.75, 3.0), "format.justifyText": (bool, False), diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py index 86187b4a5..403137fb0 100644 --- a/novelwriter/core/docbuild.py +++ b/novelwriter/core/docbuild.py @@ -288,7 +288,7 @@ def _setupBuild(self, bldObj: Tokenizer) -> dict: textFont = self._build.getStr("format.textFont") textSize = self._build.getInt("format.textSize") - fontFamily = textFont or CONFIG.textFont + fontFamily = textFont or CONFIG.textFont.family() bldFont = QFont(fontFamily, textSize) fontInfo = QFontInfo(bldFont) textFixed = fontInfo.fixedPitch() diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index af4e6980c..3158fa24c 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -27,7 +27,7 @@ import logging from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot -from PyQt5.QtGui import QCloseEvent, QFont, QKeyEvent, QKeySequence +from PyQt5.QtGui import QCloseEvent, QKeyEvent, QKeySequence from PyQt5.QtWidgets import ( QAbstractButton, QApplication, QCompleter, QDialog, QDialogButtonBox, QFileDialog, QFontDialog, QHBoxLayout, QLineEdit, QPushButton, QVBoxLayout, @@ -35,6 +35,7 @@ ) from novelwriter import CONFIG, SHARED +from novelwriter.common import describeFont from novelwriter.constants import nwConst, nwUnicode from novelwriter.dialogs.quotes import GuiQuoteSelect from novelwriter.extensions.configlayout import NColourLabel, NScrollableForm @@ -135,6 +136,11 @@ def buildForm(self) -> None: iSz = SHARED.theme.baseIconSize boxFixed = 5*SHARED.theme.textNWidth minWidth = CONFIG.pxInt(200) + fontWidth = CONFIG.pxInt(162) + + # Temporary Variables + self._guiFont = CONFIG.guiFont + self._textFont = CONFIG.textFont # Label self.sidebar.addLabel(self.tr("General")) @@ -174,27 +180,17 @@ def buildForm(self) -> None: # Application Font Family self.guiFont = QLineEdit(self) self.guiFont.setReadOnly(True) - self.guiFont.setMinimumWidth(CONFIG.pxInt(162)) - self.guiFont.setText(CONFIG.guiFont) + self.guiFont.setMinimumWidth(fontWidth) + self.guiFont.setText(describeFont(self._guiFont)) + self.guiFont.setCursorPosition(0) self.guiFontButton = NIconToolButton(self, iSz, "more") self.guiFontButton.clicked.connect(self._selectGuiFont) self.mainForm.addRow( - self.tr("Application font family"), self.guiFont, + self.tr("Application font"), self.guiFont, self.tr("Requires restart to take effect."), stretch=(3, 2), button=self.guiFontButton ) - # Application Font Size - self.guiFontSize = NSpinBox(self) - self.guiFontSize.setMinimum(8) - self.guiFontSize.setMaximum(60) - self.guiFontSize.setSingleStep(1) - self.guiFontSize.setValue(CONFIG.guiFontSize) - self.mainForm.addRow( - self.tr("Application font size"), self.guiFontSize, - self.tr("Requires restart to take effect."), unit=self.tr("pt") - ) - # Vertical Scrollbars self.hideVScroll = NSwitch(self) self.hideVScroll.setChecked(CONFIG.hideVScroll) @@ -234,27 +230,17 @@ def buildForm(self) -> None: # Document Font Family self.textFont = QLineEdit(self) self.textFont.setReadOnly(True) - self.textFont.setMinimumWidth(CONFIG.pxInt(162)) - self.textFont.setText(CONFIG.textFont) + self.textFont.setMinimumWidth(fontWidth) + self.textFont.setText(describeFont(CONFIG.textFont)) + self.textFont.setCursorPosition(0) self.textFontButton = NIconToolButton(self, iSz, "more") self.textFontButton.clicked.connect(self._selectTextFont) self.mainForm.addRow( - self.tr("Document font family"), self.textFont, + self.tr("Document font"), self.textFont, self.tr("Applies to both document editor and viewer."), stretch=(3, 2), button=self.textFontButton ) - # Document Font Size - self.textSize = NSpinBox(self) - self.textSize.setMinimum(8) - self.textSize.setMaximum(60) - self.textSize.setSingleStep(1) - self.textSize.setValue(CONFIG.textSize) - self.mainForm.addRow( - self.tr("Document font size"), self.textSize, - self.tr("Applies to both document editor and viewer."), unit=self.tr("pt") - ) - # Emphasise Labels self.emphLabels = NSwitch(self) self.emphLabels.setChecked(CONFIG.emphLabels) @@ -818,25 +804,21 @@ def _gotoSearch(self) -> None: @pyqtSlot() def _selectGuiFont(self) -> None: """Open the QFontDialog and set a font for the font style.""" - current = QFont() - current.setFamily(CONFIG.guiFont) - current.setPointSize(CONFIG.guiFontSize) - font, status = QFontDialog.getFont(current, self) + font, status = QFontDialog.getFont(self._guiFont, self) if status: - self.guiFont.setText(font.family()) - self.guiFontSize.setValue(font.pointSize()) + self.guiFont.setText(describeFont(font)) + self.guiFont.setCursorPosition(0) + self._guiFont = font return @pyqtSlot() def _selectTextFont(self) -> None: """Open the QFontDialog and set a font for the font style.""" - current = QFont() - current.setFamily(CONFIG.textFont) - current.setPointSize(CONFIG.textSize) - font, status = QFontDialog.getFont(current, self) + font, status = QFontDialog.getFont(CONFIG.textFont, self) if status: - self.textFont.setText(font.family()) - self.textSize.setValue(font.pointSize()) + self.textFont.setText(describeFont(font)) + self.textFont.setCursorPosition(0) + self._textFont = font return @pyqtSlot() @@ -892,20 +874,16 @@ def _saveValues(self) -> None: # Appearance guiLocale = self.guiLocale.currentData() guiTheme = self.guiTheme.currentData() - guiFont = self.guiFont.text() - guiFontSize = self.guiFontSize.value() updateTheme |= CONFIG.guiTheme != guiTheme needsRestart |= CONFIG.guiLocale != guiLocale - needsRestart |= CONFIG.guiFont != guiFont - needsRestart |= CONFIG.guiFontSize != guiFontSize + needsRestart |= CONFIG.guiFont != self._guiFont CONFIG.guiLocale = guiLocale CONFIG.guiTheme = guiTheme - CONFIG.guiFont = guiFont - CONFIG.guiFontSize = guiFontSize CONFIG.hideVScroll = self.hideVScroll.isChecked() CONFIG.hideHScroll = self.hideHScroll.isChecked() + CONFIG.setGuiFont(self._guiFont) # Document Style guiSyntax = self.guiSyntax.currentData() @@ -918,7 +896,7 @@ def _saveValues(self) -> None: CONFIG.emphLabels = emphLabels CONFIG.showFullPath = self.showFullPath.isChecked() CONFIG.incNotesWCount = self.incNotesWCount.isChecked() - CONFIG.setTextFont(self.textFont.text(), self.textSize.value()) + CONFIG.setTextFont(self._textFont) # Auto Save CONFIG.autoSaveDoc = self.autoSaveDoc.value() diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 55e6c76a4..7fdbfdf9e 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -377,16 +377,10 @@ def initFont(self) -> None: special attention since there appears to be a bug in Qt 5.15.3. See issues #1862 and #1875. """ - font = self.font() - font.setFamily(CONFIG.textFont) - font.setPointSize(CONFIG.textSize) - self.setFont(font) - - # Reset sub-widget font to GUI font + self.setFont(CONFIG.textFont) self.docHeader.updateFont() self.docFooter.updateFont() self.docSearch.updateFont() - return def loadText(self, tHandle: str, tLine: int | None = None) -> bool: diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py index ddbdcaa3e..0e731032e 100644 --- a/novelwriter/gui/dochighlight.py +++ b/novelwriter/gui/dochighlight.py @@ -474,7 +474,7 @@ def _addCharFormat( charFormat.setBackground(QBrush(color, Qt.BrushStyle.SolidPattern)) if size: - charFormat.setFontPointSize(round(size*CONFIG.textSize)) + charFormat.setFontPointSize(round(size*CONFIG.textFont.pointSize())) self._hStyles[name] = charFormat diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index b6423b297..917a4f7f3 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -186,15 +186,9 @@ def initFont(self) -> None: special attention since there appears to be a bug in Qt 5.15.3. See issues #1862 and #1875. """ - font = self.font() - font.setFamily(CONFIG.textFont) - font.setPointSize(CONFIG.textSize) - self.setFont(font) - - # Reset sub-widget font to GUI font + self.setFont(CONFIG.textFont) self.docHeader.updateFont() self.docFooter.updateFont() - return def loadText(self, tHandle: str, updateHistory: bool = True) -> bool: diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 1dbf268ac..a986f86aa 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -114,9 +114,6 @@ def __init__(self) -> None: # Class Setup # =========== - # Init GUI Font - self._setGuiFont() - # Load Themes self._guiPalette = QPalette() self._themeList: list[tuple[str, str]] = [] @@ -411,27 +408,6 @@ def _resetGuiColors(self) -> None: self.errorText = QColor(255, 0, 0) return - def _setGuiFont(self) -> None: - """Update the GUI's font style from settings.""" - font = QFont() - fontDB = QFontDatabase() - if CONFIG.guiFont not in fontDB.families(): - if CONFIG.osWindows and "Arial" in fontDB.families(): - # On Windows we default to Arial if possible - font.setFamily("Arial") - font.setPointSize(10) - else: - font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont) - CONFIG.guiFont = font.family() - CONFIG.guiFontSize = font.pointSize() - else: - font.setFamily(CONFIG.guiFont) - font.setPointSize(CONFIG.guiFontSize) - - QApplication.setFont(font) - - return - def _listConf(self, targetDict: dict, checkDir: Path) -> bool: """Scan for theme config files and populate the dictionary.""" if not checkDir.is_dir(): diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 5c39b7a56..ca19e6d08 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -787,7 +787,7 @@ def __init__(self, parent: QWidget) -> None: self._updateDocMargins() self._updateBuildAge() - self.setTextFont(CONFIG.textFont, CONFIG.textSize) + self.setTextFont(CONFIG.textFont.family(), CONFIG.textFont.pointSize()) # Age Timer self.ageTimer = QTimer(self) diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 963da221d..dca739f9b 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -1175,7 +1175,7 @@ def loadContent(self) -> None: """Populate the widgets.""" textFont = self._build.getStr("format.textFont") if not textFont: - textFont = str(CONFIG.textFont) + textFont = str(CONFIG.textFont.family()) self.textFont.setText(textFont) self.textSize.setValue(self._build.getInt("format.textSize")) diff --git a/tests/conftest.py b/tests/conftest.py index 1e463f9c5..5ea7cbb2b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -52,6 +52,7 @@ def resetConfigVars(): """ CONFIG.setLastPath(_TMP_ROOT) CONFIG.setBackupPath(_TMP_ROOT) + CONFIG.setGuiFont(None) CONFIG.setTextFont(None) CONFIG._homePath = _TMP_ROOT CONFIG.guiLocale = "en_GB" diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf index 818f42801..99559ed87 100644 --- a/tests/reference/baseConfig_novelwriter.conf +++ b/tests/reference/baseConfig_novelwriter.conf @@ -1,11 +1,10 @@ [Meta] -timestamp = 2024-05-13 15:59:59 +timestamp = 2024-05-20 16:48:20 [Main] +font = theme = default syntax = default_light -font = -fontsize = 11 localisation = en_GB hidevscroll = False hidehscroll = False @@ -30,7 +29,6 @@ askbeforebackup = True [Editor] textfont = -textsize = 12 width = 700 margin = 40 tabwidth = 40 diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py index 0bfa6dada..dba88e9ba 100644 --- a/tests/test_base/test_base_common.py +++ b/tests/test_base/test_base_common.py @@ -28,16 +28,16 @@ import pytest from PyQt5.QtCore import QUrl -from PyQt5.QtGui import QColor, QDesktopServices +from PyQt5.QtGui import QColor, QDesktopServices, QFontDatabase from novelwriter.common import ( NWConfigParser, checkBool, checkFloat, checkInt, checkIntTuple, checkPath, - checkString, checkStringNone, checkUuid, cssCol, elide, formatFileFilter, - formatInt, formatTime, formatTimeStamp, formatVersion, fuzzyTime, - getFileSize, hexToInt, isHandle, isItemClass, isItemLayout, isItemType, - isListInstance, isTitleTag, jsonEncode, makeFileNameSafe, minmax, - numberToRoman, openExternalPath, readTextFile, simplified, transferCase, - xmlIndent, yesNo + checkString, checkStringNone, checkUuid, cssCol, describeFont, elide, + formatFileFilter, formatInt, formatTime, formatTimeStamp, formatVersion, + fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass, isItemLayout, + isItemType, isListInstance, isTitleTag, jsonEncode, makeFileNameSafe, + minmax, numberToRoman, openExternalPath, readTextFile, simplified, + transferCase, xmlIndent, yesNo ) from tests.mocked import causeOSError @@ -487,6 +487,15 @@ def testBaseCommon_cssCol(): assert cssCol(QColor(10, 20, 30, 40)) == "rgba(10, 20, 30, 40)" +@pytest.mark.base +def testBaseCommon_describeFont(): + """Test the describeFont function.""" + fontDB = QFontDatabase() + font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont) + assert font.family() in describeFont(font) + assert describeFont(None) == "Error" # type: ignore + + @pytest.mark.base def testBaseCommon_jsonEncode(): """Test the jsonEncode function.""" diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 8af01057b..93a09a75f 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -103,15 +103,19 @@ def testBaseConfig_InitLoadSave(monkeypatch, fncPath, tstPaths): if confFile.is_file(): confFile.unlink() - # Running init against a new oath should write a new config file + # Running init + load against a new path should write a new config file tstConf.initConfig(confPath=fncPath, dataPath=fncPath) assert tstConf._confPath == fncPath assert tstConf._dataPath == fncPath + tstConf.loadConfig() assert confFile.exists() # Check that we have a default file copyfile(confFile, testFile) - ignore = ("timestamp", "lastnotes", "localisation", "lastpath", "backuppath") + ignore = ( + "timestamp", "lastnotes", "localisation", + "lastpath", "backuppath", "font", "textfont" + ) assert cmpFiles(testFile, compFile, ignoreStart=ignore) tstConf.errorText() # This clears the error cache @@ -136,6 +140,7 @@ def testBaseConfig_InitLoadSave(monkeypatch, fncPath, tstPaths): newConf = Config() newConf.initConfig(confPath=fncPath, dataPath=fncPath) + newConf.loadConfig() assert newConf.guiTheme == "foo" assert newConf.guiSyntax == "bar" diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index 360c339f8..70e950de3 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -23,7 +23,7 @@ import pytest from PyQt5.QtCore import QEvent, Qt -from PyQt5.QtGui import QFontDatabase, QKeyEvent +from PyQt5.QtGui import QFont, QFontDatabase, QKeyEvent from PyQt5.QtWidgets import QAction, QFileDialog, QFontDialog from novelwriter import CONFIG, SHARED @@ -173,32 +173,28 @@ def pointSize(self): prefs.guiLocale.setCurrentIndex(prefs.guiLocale.findData("en_US")) prefs.guiTheme.setCurrentIndex(prefs.guiTheme.findData("default_dark")) with monkeypatch.context() as mp: - mp.setattr(QFontDialog, "getFont", lambda *a: (MockFont(), True)) + mp.setattr(QFontDialog, "getFont", lambda *a: (QFont(), True)) prefs.guiFontButton.click() - prefs.guiFontSize.stepDown() # Should change it to 41 prefs.hideVScroll.setChecked(True) prefs.hideHScroll.setChecked(True) assert CONFIG.guiLocale != "en_US" assert CONFIG.guiTheme != "default_dark" - assert CONFIG.guiFont != "TestFont" - assert CONFIG.guiFontSize < 42 + assert CONFIG.guiFont.family() != "" assert CONFIG.hideVScroll is False assert CONFIG.hideHScroll is False # Document Style prefs.guiSyntax.setCurrentIndex(prefs.guiSyntax.findData("default_dark")) with monkeypatch.context() as mp: - mp.setattr(QFontDialog, "getFont", lambda *a: (MockFont(), True)) + mp.setattr(QFontDialog, "getFont", lambda *a: (QFont(), True)) prefs.textFontButton.click() - prefs.textSize.stepDown() # Should change it to 41 prefs.emphLabels.setChecked(False) prefs.showFullPath.setChecked(False) prefs.incNotesWCount.setChecked(False) assert CONFIG.guiSyntax != "default_dark" - assert CONFIG.textFont != "testFont" - assert CONFIG.textSize < 42 + assert CONFIG.textFont.family() != "" assert CONFIG.emphLabels is True assert CONFIG.showFullPath is True assert CONFIG.incNotesWCount is True @@ -341,15 +337,13 @@ def pointSize(self): # Appearance assert CONFIG.guiLocale == "en_US" assert CONFIG.guiTheme == "default_dark" - assert CONFIG.guiFont == "TestFont" - assert CONFIG.guiFontSize == 41 + assert CONFIG.guiFont == QFont() assert CONFIG.hideVScroll is True assert CONFIG.hideHScroll is True # Document Style assert CONFIG.guiSyntax == "default_dark" - assert CONFIG.textFont == "TestFont" - assert CONFIG.textSize == 41 + assert CONFIG.textFont == QFont() assert CONFIG.emphLabels is False assert CONFIG.showFullPath is False assert CONFIG.incNotesWCount is False diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 30e199d0e..6c8ed7a66 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -23,7 +23,7 @@ import pytest from PyQt5.QtCore import QEvent, Qt, QThreadPool -from PyQt5.QtGui import QClipboard, QMouseEvent, QTextBlock, QTextCursor, QTextOption +from PyQt5.QtGui import QClipboard, QFont, QMouseEvent, QTextBlock, QTextCursor, QTextOption from PyQt5.QtWidgets import QAction, QApplication, QMenu from novelwriter import CONFIG, SHARED @@ -84,7 +84,7 @@ def testGuiEditor_Init(qtbot, nwGUI, projPath, ipsumText, mockRnd): assert docEditor.docHeader._docOutline == {0: "### New Scene"} # Check that editor handles settings - CONFIG.textFont = "" + CONFIG.textFont = QFont() CONFIG.doJustify = True CONFIG.showTabsNSpaces = True CONFIG.showLineEndings = True @@ -96,7 +96,7 @@ def testGuiEditor_Init(qtbot, nwGUI, projPath, ipsumText, mockRnd): docEditor.initEditor() qDoc = docEditor.document() - assert CONFIG.textFont == qDoc.defaultFont().family() + assert CONFIG.textFont == qDoc.defaultFont() assert qDoc.defaultTextOption().alignment() == QtAlignJustify assert qDoc.defaultTextOption().flags() & QTextOption.ShowTabsAndSpaces assert qDoc.defaultTextOption().flags() & QTextOption.ShowLineAndParagraphSeparators diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index e5431b657..18458883c 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -48,27 +48,6 @@ def testGuiTheme_Main(qtbot, nwGUI, tstPaths): assert mSize > 0 assert mainTheme.getTextWidth("m", mainTheme.guiFont) == mSize - # Init Fonts - # ========== - - # The defaults should be set - defaultFont = CONFIG.guiFont - defaultSize = CONFIG.guiFontSize - - # CHange them to nonsense values - CONFIG.guiFont = "notafont" - CONFIG.guiFontSize = 99 - - # Let the theme class set them back to default - mainTheme._setGuiFont() - assert CONFIG.guiFont == defaultFont - assert CONFIG.guiFontSize == defaultSize - - # A second call should just restore the defaults again - mainTheme._setGuiFont() - assert CONFIG.guiFont == defaultFont - assert CONFIG.guiFontSize == defaultSize - # Scan for Themes # =============== diff --git a/tests/test_tools/test_tools_manussettings.py b/tests/test_tools/test_tools_manussettings.py index a2d0bc7d4..1d1ef5918 100644 --- a/tests/test_tools/test_tools_manussettings.py +++ b/tests/test_tools/test_tools_manussettings.py @@ -547,7 +547,7 @@ def testBuildSettings_Format(monkeypatch, qtbot, nwGUI): """Test the Format Tab of the GuiBuildSettings dialog.""" build = BuildSettings() - textFont = str(CONFIG.textFont) + textFont = str(CONFIG.textFont.family()) build.setValue("format.buildLang", "en_US") build.setValue("format.textFont", "") # Will fall back to config value