From e2f3a44d96491811f1009352a5065df81dea9227 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 9 Nov 2022 23:10:30 +0100 Subject: [PATCH 1/4] Fix pollution of recent cache from tests --- novelwriter/config.py | 10 +++++----- tests/test_base/test_base_config.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/novelwriter/config.py b/novelwriter/config.py index 454e54d1..599022be 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -91,7 +91,7 @@ class Config: # User Settings # ============= - self._recentProj = RecentProjects(self._dataPath) + self._recentProj = RecentProjects(self) # General GUI Settings self.guiLang = self._qLocal.name() @@ -839,8 +839,8 @@ class Config: class RecentProjects: - def __init__(self, dataPath): - self._dataPath = dataPath + def __init__(self, mainConf): + self.mainConf = mainConf self._data = {} return @@ -849,7 +849,7 @@ class RecentProjects: """ self._data = {} - cacheFile = self._dataPath / nwFiles.RECENT_FILE + cacheFile = self.mainConf.dataPath(nwFiles.RECENT_FILE) if not cacheFile.is_file(): return True @@ -872,7 +872,7 @@ class RecentProjects: def saveCache(self): """Save the cache dictionary of recent projects. """ - cacheFile = self._dataPath / nwFiles.RECENT_FILE + cacheFile = self.mainConf.dataPath(nwFiles.RECENT_FILE) cacheTemp = cacheFile.with_suffix(".tmp") try: with open(cacheTemp, mode="w+", encoding="utf-8") as outFile: diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index bac296ce..9ca3908f 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -411,11 +411,11 @@ def testBaseConfig_Internal(monkeypatch, tmpConf): @pytest.mark.base -def testBaseConfig_RecentCache(monkeypatch, fncPath): +def testBaseConfig_RecentCache(monkeypatch, fncConf, fncPath): """Test recent cache file. """ cacheFile = fncPath / nwFiles.RECENT_FILE - recent = RecentProjects(fncPath) + recent = RecentProjects(fncConf) # Load when there is no file should pass, but load nothing assert not cacheFile.exists() From 15fec913586ccdd288e433b0e33ccca21840c898 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 10 Nov 2022 09:29:00 +0100 Subject: [PATCH 2/4] Clean up setters and getters in Config --- novelwriter/common.py | 16 + novelwriter/config.py | 273 +++++++++--------- novelwriter/dialogs/preferences.py | 18 +- novelwriter/dialogs/projload.py | 4 +- novelwriter/gui/outline.py | 2 +- novelwriter/guimain.py | 9 +- tests/reference/baseConfig_novelwriter.conf | 1 - .../reference/guiPreferences_novelwriter.conf | 1 - tests/test_base/test_base_common.py | 21 +- tests/test_base/test_base_config.py | 79 +++-- tests/test_dialogs/test_dlg_preferences.py | 2 +- 11 files changed, 220 insertions(+), 206 deletions(-) diff --git a/novelwriter/common.py b/novelwriter/common.py index 8b4c2da8..c712015a 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -124,6 +124,17 @@ def checkUuid(value, default): return default +def checkPath(value, default): + """Check if a value is a valid path. Non-empty strings are accepted. + """ + if isinstance(value, Path): + return value + elif isinstance(value, str): + if value.strip(): + return Path(value) + return default + + # =============================================================================================== # # Validator Functions # =============================================================================================== # @@ -552,6 +563,11 @@ class NWConfigParser(ConfigParser): logger.error("Could not read '%s':'%s' from config", section, option) return default + def rdPath(self, section, option, default): + """Read a path value. + """ + return checkPath(self.get(section, option, fallback=default), default) + def rdStrList(self, section, option, default): """Read string list. """ diff --git a/novelwriter/config.py b/novelwriter/config.py index 599022be..1a69fea5 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -37,7 +37,7 @@ from PyQt5.QtCore import ( ) from novelwriter.error import logException, formatException -from novelwriter.common import splitVersionNumber, formatTimeStamp, NWConfigParser +from novelwriter.common import checkPath, splitVersionNumber, formatTimeStamp, NWConfigParser from novelwriter.constants import nwFiles, nwUnicode logger = logging.getLogger(__name__) @@ -73,10 +73,10 @@ class Config: self._appPath = self._appRoot # Runtime Settings and Variables - self._hasError = False # True if the config class encountered an error - self._errData = [] # List of error messages - self.confChanged = False # True whenever the config has chenged, false after save - self.cmdOpen = None # Path from command line for project to be opened on launch + self._hasError = False # True if the config class encountered an error + self._errData = [] # List of error messages + self._confChanged = False # True whenever the config has chenged, false after save + self.cmdOpen = None # Path from command line for project to be opened on launch # Localisation Info self._qLocal = QLocale.system() @@ -106,14 +106,13 @@ class Config: self.setDefaultSyntaxTheme() # Size Settings - self.winGeometry = [1200, 650] - self.prefGeometry = [700, 615] - self.projColWidth = [200, 60, 140] - self.mainPanePos = [300, 800] - self.docPanePos = [400, 400] - self.viewPanePos = [500, 150] - self.outlnPanePos = [500, 150] - self.isFullScreen = False + self._winGeometry = [1200, 650] + self._prefGeometry = [700, 615] + self._projColWidth = [200, 60, 140] + self._mainPanePos = [300, 800] + self._viewPanePos = [500, 150] + self._outlnPanePos = [500, 150] + self.isFullScreen = False # Feature Settings self.hideVScroll = False # Hide vertical scroll bars on main widgets @@ -258,6 +257,109 @@ class Config: def recentProjects(self): return self._recentProj + @property + def mainWinSize(self): + return [int(x*self.guiScale) for x in self._winGeometry] + + @property + def preferencesWinSize(self): + return [int(x*self.guiScale) for x in self._prefGeometry] + + @property + def projLoadColWidths(self): + return [int(x*self.guiScale) for x in self._projColWidth] + + @property + def mainPanePos(self): + return [int(x*self.guiScale) for x in self._mainPanePos] + + @property + def viewPanePos(self): + return [int(x*self.guiScale) for x in self._viewPanePos] + + @property + def outlinePanePos(self): + return [int(x*self.guiScale) for x in self._outlnPanePos] + + ## + # Setters + ## + + def setMainWinSize(self, newWidth, newHeight): + """Set the size of the main window, but only if the change is + larger than 5 pixels. The OS window manager will sometimes + adjust it a bit, and we don't want the main window to shrink or + grow each time the app is opened. + """ + newWidth = int(newWidth/self.guiScale) + newHeight = int(newHeight/self.guiScale) + if abs(self._winGeometry[0] - newWidth) > 5: + self._winGeometry[0] = newWidth + self._confChanged = True + if abs(self._winGeometry[1] - newHeight) > 5: + self._winGeometry[1] = newHeight + self._confChanged = True + return + + def setPreferencesWinSize(self, newWidth, newHeight): + """Sat the size of the Preferences dialog window. + """ + self._prefGeometry[0] = int(newWidth/self.guiScale) + self._prefGeometry[1] = int(newHeight/self.guiScale) + self._confChanged = True + return + + def setProjLoadColWidths(self, colWidths): + """Set the column widths of the Load Project dialog. + """ + self._projColWidth = [int(x/self.guiScale) for x in colWidths] + self._confChanged = True + return + + def setMainPanePos(self, panePos): + """Set the position of the main GUI splitter. + """ + self._mainPanePos = [int(x/self.guiScale) for x in panePos] + self._confChanged = True + return + + def setViewPanePos(self, panePos): + """Set the position of the viewer meta data splitter. + """ + self._viewPanePos = [int(x/self.guiScale) for x in panePos] + self._confChanged = True + return + + def setOutlinePanePos(self, panePos): + """Set the position of the outline details splitter. + """ + self._outlnPanePos = [int(x/self.guiScale) for x in panePos] + self._confChanged = True + return + + def setLastPath(self, lastPath): + """Set the last used path. Only the folder is saved, so if the + path is not a folder, the parent of the path is used instead. + """ + if isinstance(lastPath, (str, Path)): + lastPath = Path(lastPath) + if not lastPath.is_dir(): + lastPath = lastPath.parent + if lastPath.is_dir(): + self._lastPath = lastPath + logger.debug("Last path updated: %s" % self._lastPath) + return + + def setBackupPath(self, backupPath): + """Set the current backup path. + """ + self._backupPath = checkPath(backupPath, None) + return + + def setConfigChanged(self, value): + self._confChanged = bool(value) + return + ## # Methods ## @@ -291,8 +393,9 @@ class Config: def lastPath(self): """Return the last path used by the user, but ensure it exists. """ - if self._lastPath.is_dir(): - return self._lastPath + if isinstance(self._lastPath, Path): + if self._lastPath.is_dir(): + return self._lastPath return Path.home().absolute() def backupPath(self): @@ -440,13 +543,12 @@ class Config: # Sizes cnfSec = "Sizes" - self.winGeometry = theConf.rdIntList(cnfSec, "geometry", self.winGeometry) - self.prefGeometry = theConf.rdIntList(cnfSec, "preferences", self.prefGeometry) - self.projColWidth = theConf.rdIntList(cnfSec, "projcols", self.projColWidth) - self.mainPanePos = theConf.rdIntList(cnfSec, "mainpane", self.mainPanePos) - self.docPanePos = theConf.rdIntList(cnfSec, "docpane", self.docPanePos) - self.viewPanePos = theConf.rdIntList(cnfSec, "viewpane", self.viewPanePos) - self.outlnPanePos = theConf.rdIntList(cnfSec, "outlinepane", self.outlnPanePos) + self._winGeometry = theConf.rdIntList(cnfSec, "geometry", self._winGeometry) + self._prefGeometry = theConf.rdIntList(cnfSec, "preferences", self._prefGeometry) + self._projColWidth = theConf.rdIntList(cnfSec, "projcols", self._projColWidth) + self._mainPanePos = theConf.rdIntList(cnfSec, "mainpane", self._mainPanePos) + self._viewPanePos = theConf.rdIntList(cnfSec, "viewpane", self._viewPanePos) + self._outlnPanePos = theConf.rdIntList(cnfSec, "outlinepane", self._outlnPanePos) self.isFullScreen = theConf.rdBool(cnfSec, "fullscreen", self.isFullScreen) # Project @@ -496,10 +598,9 @@ class Config: # Backup cnfSec = "Backup" - backupPath = theConf.rdStr(cnfSec, "backuppath", None) + self._backupPath = theConf.rdPath(cnfSec, "backuppath", self._backupPath) self.backupOnClose = theConf.rdBool(cnfSec, "backuponclose", self.backupOnClose) self.askBeforeBackup = theConf.rdBool(cnfSec, "askbeforebackup", self.askBeforeBackup) - self.setBackupPath(backupPath) # State cnfSec = "State" @@ -515,7 +616,7 @@ class Config: # Path cnfSec = "Path" - self._lastPath = Path(theConf.rdStr(cnfSec, "lastpath", self._lastPath)) + self._lastPath = theConf.rdPath(cnfSec, "lastpath", self._lastPath) # Check Certain Values for None self.spellLanguage = self._checkNone(self.spellLanguage) @@ -551,13 +652,12 @@ class Config: } theConf["Sizes"] = { - "geometry": self._packList(self.winGeometry), - "preferences": self._packList(self.prefGeometry), - "projcols": self._packList(self.projColWidth), - "mainpane": self._packList(self.mainPanePos), - "docpane": self._packList(self.docPanePos), - "viewpane": self._packList(self.viewPanePos), - "outlinepane": self._packList(self.outlnPanePos), + "geometry": self._packList(self._winGeometry), + "preferences": self._packList(self._prefGeometry), + "projcols": self._packList(self._projColWidth), + "mainpane": self._packList(self._mainPanePos), + "viewpane": self._packList(self._viewPanePos), + "outlinepane": self._packList(self._outlnPanePos), "fullscreen": str(self.isFullScreen), } @@ -633,7 +733,7 @@ class Config: try: with open(cnfPath, mode="w", encoding="utf-8") as outFile: theConf.write(outFile) - self.confChanged = False + self._confChanged = False except Exception as exc: logger.error("Could not save config file") logException() @@ -648,105 +748,25 @@ class Config: # Setters ## - def setLastPath(self, lastPath): - """Set the last used path. Only the folder is saved, so if the - path is not a folder, the parent of the path is used instead. - """ - if isinstance(lastPath, (str, Path)): - lastPath = Path(lastPath) - if not lastPath.is_dir(): - lastPath = lastPath.parent - if lastPath.is_dir(): - self._lastPath = lastPath - logger.debug("Last path updated: %s" % self._lastPath) - return - - def setBackupPath(self, backupPath): - """Set the current backup path. - """ - self._backupPath = None - if isinstance(backupPath, (str, Path)): - self._backupPath = Path(backupPath) - return - - def setWinSize(self, newWidth, newHeight): - """Set the size of the main window, but only if the change is - larger than 5 pixels. The OS window manager will sometimes - adjust it a bit, and we don't want the main window to shrink or - grow each time the app is opened. - """ - newWidth = int(newWidth/self.guiScale) - newHeight = int(newHeight/self.guiScale) - if abs(self.winGeometry[0] - newWidth) > 5: - self.winGeometry[0] = newWidth - self.confChanged = True - if abs(self.winGeometry[1] - newHeight) > 5: - self.winGeometry[1] = newHeight - self.confChanged = True - return - - def setPreferencesSize(self, newWidth, newHeight): - """Sat the size of the Preferences dialog window. - """ - self.prefGeometry[0] = int(newWidth/self.guiScale) - self.prefGeometry[1] = int(newHeight/self.guiScale) - self.confChanged = True - return - - def setProjColWidths(self, colWidths): - """Set the column widths of the Load Project dialog. - """ - self.projColWidth = [int(x/self.guiScale) for x in colWidths] - self.confChanged = True - return - - def setMainPanePos(self, panePos): - """Set the position of the main GUI splitter. - """ - self.mainPanePos = [int(x/self.guiScale) for x in panePos] - self.confChanged = True - return - - def setDocPanePos(self, panePos): - """Set the position of the main editor/viewer splitter. - """ - self.docPanePos = [int(x/self.guiScale) for x in panePos] - self.confChanged = True - return - - def setViewPanePos(self, panePos): - """Set the position of the viewer meta data splitter. - """ - self.viewPanePos = [int(x/self.guiScale) for x in panePos] - self.confChanged = True - return - - def setOutlinePanePos(self, panePos): - """Set the position of the outline details splitter. - """ - self.outlnPanePos = [int(x/self.guiScale) for x in panePos] - self.confChanged = True - return - def setShowRefPanel(self, checkState): """Set the visibility state of the reference panel. """ self.showRefPanel = checkState - self.confChanged = True + self._confChanged = True return def setViewComments(self, viewState): """Set the visibility state of comments in the viewer. """ self.viewComments = viewState - self.confChanged = True + self._confChanged = True return def setViewSynopsis(self, viewState): """Set the visibility state of synopsis comments in the viewer. """ self.viewSynopsis = viewState - self.confChanged = True + self._confChanged = True return ## @@ -767,27 +787,6 @@ class Config: # Getters ## - def getWinSize(self): - return [int(x*self.guiScale) for x in self.winGeometry] - - def getPreferencesSize(self): - return [int(x*self.guiScale) for x in self.prefGeometry] - - def getProjColWidths(self): - return [int(x*self.guiScale) for x in self.projColWidth] - - def getMainPanePos(self): - return [int(x*self.guiScale) for x in self.mainPanePos] - - def getDocPanePos(self): - return [int(x*self.guiScale) for x in self.docPanePos] - - def getViewPanePos(self): - return [int(x*self.guiScale) for x in self.viewPanePos] - - def getOutlinePanePos(self): - return [int(x*self.guiScale) for x in self.outlnPanePos] - def getTextWidth(self, focusMode=False): if focusMode: return self.pxInt(max(self.focusWidth, 200)) diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 81e62649..5fe3626a 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -74,7 +74,7 @@ class GuiPreferences(PagedDialog): self.buttonBox.rejected.connect(self._doClose) self.addControls(self.buttonBox) - self.resize(*self.mainConf.getPreferencesSize()) + self.resize(*self.mainConf.preferencesWinSize) # Settings self._updateTheme = False @@ -143,7 +143,7 @@ class GuiPreferences(PagedDialog): def _saveWindowSize(self): """Save the dialog window size. """ - self.mainConf.setPreferencesSize(self.width(), self.height()) + self.mainConf.setPreferencesWinSize(self.width(), self.height()) return # END Class GuiPreferences @@ -311,7 +311,7 @@ class GuiPreferencesGeneral(QWidget): self.mainConf.hideVScroll = self.hideVScroll.isChecked() self.mainConf.hideHScroll = self.hideHScroll.isChecked() - self.mainConf.confChanged = True + self.mainConf.setConfigChanged(True) return @@ -458,7 +458,7 @@ class GuiPreferencesProjects(QWidget): self.mainConf.stopWhenIdle = self.stopWhenIdle.isChecked() self.mainConf.userIdleTime = round(self.userIdleTime.value() * 60) - self.mainConf.confChanged = True + self.mainConf.setConfigChanged(True) return @@ -629,7 +629,7 @@ class GuiPreferencesDocuments(QWidget): self.mainConf.textMargin = self.textMargin.value() self.mainConf.tabWidth = self.tabWidth.value() - self.mainConf.confChanged = True + self.mainConf.setConfigChanged(True) return @@ -820,7 +820,7 @@ class GuiPreferencesEditor(QWidget): self.mainConf.autoScroll = self.autoScroll.isChecked() self.mainConf.autoScrollPos = self.autoScrollPos.value() - self.mainConf.confChanged = True + self.mainConf.setConfigChanged(True) return @@ -911,7 +911,7 @@ class GuiPreferencesSyntax(QWidget): # Text Errors self.mainConf.showMultiSpaces = self.showMultiSpaces.isChecked() - self.mainConf.confChanged = True + self.mainConf.setConfigChanged(True) return @@ -1065,7 +1065,7 @@ class GuiPreferencesAutomation(QWidget): self.mainConf.fmtPadAfter = self.fmtPadAfter.text().strip() self.mainConf.fmtPadThin = self.fmtPadThin.isChecked() - self.mainConf.confChanged = True + self.mainConf.setConfigChanged(True) return @@ -1186,7 +1186,7 @@ class GuiPreferencesQuotes(QWidget): self.mainConf.fmtDoubleQuotes[0] = self.quoteSym["DO"].text() self.mainConf.fmtDoubleQuotes[1] = self.quoteSym["DC"].text() - self.mainConf.confChanged = True + self.mainConf.setConfigChanged(True) return diff --git a/novelwriter/dialogs/projload.py b/novelwriter/dialogs/projload.py index e646a0b0..7034b465 100644 --- a/novelwriter/dialogs/projload.py +++ b/novelwriter/dialogs/projload.py @@ -258,7 +258,7 @@ class GuiProjectLoad(QDialog): colWidths[self.C_NAME] = self.listBox.columnWidth(self.C_NAME) colWidths[self.C_COUNT] = self.listBox.columnWidth(self.C_COUNT) colWidths[self.C_TIME] = self.listBox.columnWidth(self.C_TIME) - self.mainConf.setProjColWidths(colWidths) + self.mainConf.setProjLoadColWidths(colWidths) return def _populateList(self): @@ -284,7 +284,7 @@ class GuiProjectLoad(QDialog): if self.listBox.topLevelItemCount() > 0: self.listBox.topLevelItem(0).setSelected(True) - projColWidth = self.mainConf.getProjColWidths() + projColWidth = self.mainConf.projLoadColWidths if len(projColWidth) == 3: self.listBox.setColumnWidth(self.C_NAME, projColWidth[self.C_NAME]) self.listBox.setColumnWidth(self.C_COUNT, projColWidth[self.C_COUNT]) diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index 5d66d139..4d5826a1 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -71,7 +71,7 @@ class GuiOutlineView(QWidget): self.splitOutline = QSplitter(Qt.Vertical) self.splitOutline.addWidget(self.outlineTree) self.splitOutline.addWidget(self.outlineData) - self.splitOutline.setSizes(self.mainConf.getOutlinePanePos()) + self.splitOutline.setSizes(self.mainConf.outlinePanePos) # Assemble self.outerBox = QVBoxLayout() diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 4acb2952..8c0e1cfb 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -93,7 +93,7 @@ class GuiMain(QMainWindow): self.idleTime = 0.0 # Prepare Main Window - self.resize(*self.mainConf.getWinSize()) + self.resize(*self.mainConf.mainWinSize) self._updateWindowTitle() nwIcon = self.mainConf.assetPath("icons") / "novelwriter.svg" @@ -140,7 +140,7 @@ class GuiMain(QMainWindow): self.splitView.addWidget(self.docViewer) self.splitView.addWidget(self.viewMeta) self.splitView.setHandleWidth(hWd) - self.splitView.setSizes(self.mainConf.getViewPanePos()) + self.splitView.setSizes(self.mainConf.viewPanePos) # Splitter : Document Editor / Document Viewer self.splitDocs = QSplitter(Qt.Horizontal) @@ -154,7 +154,7 @@ class GuiMain(QMainWindow): self.splitMain.addWidget(self.treePane) self.splitMain.addWidget(self.splitDocs) self.splitMain.setHandleWidth(hWd) - self.splitMain.setSizes(self.mainConf.getMainPanePos()) + self.splitMain.setSizes(self.mainConf.mainPanePos) # Main Stack : Editor / Outline self.mainStack = QStackedWidget() @@ -1169,14 +1169,13 @@ class GuiMain(QMainWindow): if not self.isFocusMode: self.mainConf.setMainPanePos(self.splitMain.sizes()) - self.mainConf.setDocPanePos(self.splitDocs.sizes()) self.mainConf.setOutlinePanePos(self.outlineView.splitSizes()) if self.viewMeta.isVisible(): self.mainConf.setViewPanePos(self.splitView.sizes()) self.mainConf.setShowRefPanel(self.viewMeta.isVisible()) if not self.mainConf.isFullScreen: - self.mainConf.setWinSize(self.width(), self.height()) + self.mainConf.setMainWinSize(self.width(), self.height()) if self.hasProject: self.closeProject(True) diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf index 87c65cba..8c38cc09 100644 --- a/tests/reference/baseConfig_novelwriter.conf +++ b/tests/reference/baseConfig_novelwriter.conf @@ -14,7 +14,6 @@ geometry = 1200, 650 preferences = 700, 615 projcols = 200, 60, 140 mainpane = 300, 800 -docpane = 400, 400 viewpane = 500, 150 outlinepane = 500, 150 fullscreen = False diff --git a/tests/reference/guiPreferences_novelwriter.conf b/tests/reference/guiPreferences_novelwriter.conf index d42234e8..a97fa527 100644 --- a/tests/reference/guiPreferences_novelwriter.conf +++ b/tests/reference/guiPreferences_novelwriter.conf @@ -14,7 +14,6 @@ geometry = 1200, 650 preferences = 699, 614 projcols = 200, 60, 140 mainpane = 300, 800 -docpane = 400, 400 viewpane = 500, 150 outlinepane = 500, 150 fullscreen = False diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py index adabbfe7..e11895dc 100644 --- a/tests/test_base/test_base_common.py +++ b/tests/test_base/test_base_common.py @@ -23,15 +23,17 @@ import time import pytest import hashlib +from pathlib import Path + from mock import causeOSError from tools import writeFile from novelwriter.guimain import GuiMain from novelwriter.common import ( checkStringNone, checkString, checkInt, checkFloat, checkBool, checkHandle, - checkUuid, isHandle, isTitleTag, isItemClass, isItemType, isItemLayout, - hexToInt, minmax, checkIntTuple, formatInt, formatTimeStamp, formatTime, - simplified, yesNo, splitVersionNumber, transferCase, fuzzyTime, + checkUuid, checkPath, isHandle, isTitleTag, isItemClass, isItemType, + isItemLayout, hexToInt, minmax, checkIntTuple, formatInt, formatTimeStamp, + formatTime, simplified, yesNo, splitVersionNumber, transferCase, fuzzyTime, numberToRoman, jsonEncode, readTextFile, makeFileNameSafe, sha256sum, getGuiItem, NWConfigParser ) @@ -175,6 +177,19 @@ def testBaseCommon_CheckUuid(): # END Test testBaseCommon_CheckUuid +@pytest.mark.base +def testBaseCommon_CheckPath(): + """Test the checkPath function. + """ + assert checkPath(Path("test"), None) == Path("test") + assert checkPath("test", None) == Path("test") + assert checkPath(None, None) is None + assert checkPath("", None) is None + assert checkPath(" ", None) is None + +# END Test testBaseCommon_CheckPath + + @pytest.mark.base def testBaseCommon_IsHandle(): """Test the isHandle function. diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 9ca3908f..8fb150cc 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -253,96 +253,83 @@ def testBaseConfig_SettersGetters(tmpConf): # Window Size tmpConf.guiScale = 1.0 - tmpConf.setWinSize(1205, 655) - assert tmpConf.confChanged is False + tmpConf.setMainWinSize(1205, 655) + assert tmpConf._confChanged is False tmpConf.guiScale = 2.0 - tmpConf.setWinSize(70, 70) - assert tmpConf.getWinSize() == [70, 70] - assert tmpConf.winGeometry == [35, 35] + tmpConf.setMainWinSize(70, 70) + assert tmpConf.mainWinSize == [70, 70] + assert tmpConf._winGeometry == [35, 35] tmpConf.guiScale = 1.0 - tmpConf.setWinSize(70, 70) - assert tmpConf.getWinSize() == [70, 70] - assert tmpConf.winGeometry == [70, 70] + tmpConf.setMainWinSize(70, 70) + assert tmpConf.mainWinSize == [70, 70] + assert tmpConf._winGeometry == [70, 70] - tmpConf.setWinSize(1200, 650) + tmpConf.setMainWinSize(1200, 650) # Preferences Size tmpConf.guiScale = 2.0 - tmpConf.setPreferencesSize(70, 70) - assert tmpConf.getPreferencesSize() == [70, 70] - assert tmpConf.prefGeometry == [35, 35] + tmpConf.setPreferencesWinSize(70, 70) + assert tmpConf.preferencesWinSize == [70, 70] + assert tmpConf._prefGeometry == [35, 35] tmpConf.guiScale = 1.0 - tmpConf.setPreferencesSize(70, 70) - assert tmpConf.getPreferencesSize() == [70, 70] - assert tmpConf.prefGeometry == [70, 70] + tmpConf.setPreferencesWinSize(70, 70) + assert tmpConf.preferencesWinSize == [70, 70] + assert tmpConf._prefGeometry == [70, 70] - tmpConf.setPreferencesSize(700, 615) + tmpConf.setPreferencesWinSize(700, 615) # Project Settings Tree Columns tmpConf.guiScale = 2.0 - tmpConf.setProjColWidths([10, 20, 30]) - assert tmpConf.getProjColWidths() == [10, 20, 30] - assert tmpConf.projColWidth == [5, 10, 15] + tmpConf.setProjLoadColWidths([10, 20, 30]) + assert tmpConf.projLoadColWidths == [10, 20, 30] + assert tmpConf._projColWidth == [5, 10, 15] tmpConf.guiScale = 1.0 - tmpConf.setProjColWidths([10, 20, 30]) - assert tmpConf.getProjColWidths() == [10, 20, 30] - assert tmpConf.projColWidth == [10, 20, 30] + tmpConf.setProjLoadColWidths([10, 20, 30]) + assert tmpConf.projLoadColWidths == [10, 20, 30] + assert tmpConf._projColWidth == [10, 20, 30] - tmpConf.setProjColWidths([200, 60, 140]) + tmpConf.setProjLoadColWidths([200, 60, 140]) # Main Pane Splitter tmpConf.guiScale = 2.0 tmpConf.setMainPanePos([200, 700]) - assert tmpConf.getMainPanePos() == [200, 700] - assert tmpConf.mainPanePos == [100, 350] + assert tmpConf.mainPanePos == [200, 700] + assert tmpConf._mainPanePos == [100, 350] tmpConf.guiScale = 1.0 tmpConf.setMainPanePos([200, 700]) - assert tmpConf.getMainPanePos() == [200, 700] assert tmpConf.mainPanePos == [200, 700] + assert tmpConf._mainPanePos == [200, 700] tmpConf.setMainPanePos([300, 800]) - # Doc Pane Splitter - tmpConf.guiScale = 2.0 - tmpConf.setDocPanePos([300, 300]) - assert tmpConf.getDocPanePos() == [300, 300] - assert tmpConf.docPanePos == [150, 150] - - tmpConf.guiScale = 1.0 - tmpConf.setDocPanePos([300, 300]) - assert tmpConf.getDocPanePos() == [300, 300] - assert tmpConf.docPanePos == [300, 300] - - tmpConf.setDocPanePos([400, 400]) - # View Pane Splitter tmpConf.guiScale = 2.0 tmpConf.setViewPanePos([400, 250]) - assert tmpConf.getViewPanePos() == [400, 250] - assert tmpConf.viewPanePos == [200, 125] + assert tmpConf.viewPanePos == [400, 250] + assert tmpConf._viewPanePos == [200, 125] tmpConf.guiScale = 1.0 tmpConf.setViewPanePos([400, 250]) - assert tmpConf.getViewPanePos() == [400, 250] assert tmpConf.viewPanePos == [400, 250] + assert tmpConf._viewPanePos == [400, 250] tmpConf.setViewPanePos([500, 150]) # Outline Pane Splitter tmpConf.guiScale = 2.0 tmpConf.setOutlinePanePos([400, 250]) - assert tmpConf.getOutlinePanePos() == [400, 250] - assert tmpConf.outlnPanePos == [200, 125] + assert tmpConf.outlinePanePos == [400, 250] + assert tmpConf._outlnPanePos == [200, 125] tmpConf.guiScale = 1.0 tmpConf.setOutlinePanePos([400, 250]) - assert tmpConf.getOutlinePanePos() == [400, 250] - assert tmpConf.outlnPanePos == [400, 250] + assert tmpConf.outlinePanePos == [400, 250] + assert tmpConf._outlnPanePos == [400, 250] tmpConf.setOutlinePanePos([500, 150]) diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index 193987f4..ec83b428 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -215,7 +215,7 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, fncPath, tstPaths): qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton) nwPrefs._doClose() - assert theConf.confChanged + assert theConf._confChanged is True assert nwGUI.mainConf.saveConfig() projFile = fncPath / "novelwriter.conf" From b46ad86c315061425df9b9104ac9cc22f1be330f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 10 Nov 2022 13:20:26 +0100 Subject: [PATCH 3/4] Clean up config file format, modify how projects are launched, and add broken theme setting handling --- novelwriter/__init__.py | 7 +- novelwriter/config.py | 369 ++++++++---------- novelwriter/dialogs/preferences.py | 34 +- novelwriter/gui/docviewer.py | 4 +- novelwriter/gui/theme.py | 14 +- novelwriter/guimain.py | 24 +- tests/conftest.py | 4 +- tests/mock.py | 4 +- tests/reference/baseConfig_novelwriter.conf | 30 +- .../reference/guiPreferences_novelwriter.conf | 30 +- tests/test_base/test_base_config.py | 34 +- tests/test_base/test_base_init.py | 1 - tests/test_dialogs/test_dlg_preferences.py | 2 - tests/test_gui/test_gui_guimain.py | 43 +- tests/test_gui/test_gui_theme.py | 6 + 15 files changed, 277 insertions(+), 329 deletions(-) diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index 68a049f4..deff10a4 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -161,9 +161,6 @@ def main(sysArgs=None): elif inOpt == "--testmode": testMode = True - # Set Config Options - CONFIG.cmdOpen = cmdOpen - # Set Logging cHandle = logging.StreamHandler() cHandle.setFormatter(logging.Formatter(fmt=logFormat, style="{")) @@ -256,9 +253,7 @@ def main(sysArgs=None): # Launch main GUI CONFIG.initLocalisation(nwApp) nwGUI = GuiMain() - if not nwGUI.hasProject: - nwGUI.showProjectLoadDialog() - nwGUI.releaseNotes() + nwGUI.postLaunchTasks(cmdOpen) sys.exit(nwApp.exec_()) diff --git a/novelwriter/config.py b/novelwriter/config.py index 1a69fea5..53fe20fe 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -63,7 +63,7 @@ class Config: self._confPath = confRoot.absolute() / self.appHandle # The user config location self._dataPath = dataRoot.absolute() / self.appHandle # The user data location - self._lastPath = Path.home().absolute() # The user's last used path + self._homePath = Path.home().absolute() # The user's home directory self._appPath = Path(__file__).parent.absolute() self._appRoot = self._appPath.parent @@ -73,13 +73,12 @@ class Config: self._appPath = self._appRoot # Runtime Settings and Variables - self._hasError = False # True if the config class encountered an error - self._errData = [] # List of error messages - self._confChanged = False # True whenever the config has chenged, false after save - self.cmdOpen = None # Path from command line for project to be opened on launch + self._hasError = False # True if the config class encountered an error + self._errData = [] # List of error messages - # Localisation Info - self._qLocal = QLocale.system() + # Localisation + # Note that these paths must be strings + self._qLocale = QLocale.system() self._qtTrans = {} self._qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath) self._nwLangPath = str(self._appPath / "assets" / "i18n") @@ -94,34 +93,32 @@ class Config: self._recentProj = RecentProjects(self) # General GUI Settings - self.guiLang = self._qLocal.name() - self.guiTheme = "" # GUI theme - self.guiSyntax = "" # Syntax theme - self.guiFont = "" # Defaults to system default font - self.guiFontSize = 11 # Is overridden if system default is loaded - self.guiScale = 1.0 # Set automatically by Theme class - self.lastNotes = "0x0" # The latest release notes that have been shown - - self.setDefaultGuiTheme() - self.setDefaultSyntaxTheme() + 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._lastPath = self._homePath # The user's last used path # Size Settings - self._winGeometry = [1200, 650] - self._prefGeometry = [700, 615] - self._projColWidth = [200, 60, 140] - self._mainPanePos = [300, 800] - self._viewPanePos = [500, 150] - self._outlnPanePos = [500, 150] - self.isFullScreen = False - - # Feature Settings - self.hideVScroll = False # Hide vertical scroll bars on main widgets - self.hideHScroll = False # Hide horizontal scroll bars on main widgets - self.emphLabels = True # Add emphasis to H1 and H2 item labels + self._mainWinSize = [1200, 650] # Last size of the main GUI window + self._prefsWinSize = [700, 615] # Last size of the Preferences dialog + self._projLoadCols = [280, 60, 160] # Last columns withs of the Project Load dialog + self._mainPanePos = [300, 800] # Last position of the main window splitter + self._viewPanePos = [500, 150] # Last position of the document viewer splitter + self._outlnPanePos = [500, 150] # Last position of the outline panel splitter # Project Settings - self.autoSaveProj = 60 # Interval for auto-saving project in seconds - self.autoSaveDoc = 30 # Interval for auto-saving document in seconds + self.autoSaveProj = 60 # Interval for auto-saving project, in seconds + self.autoSaveDoc = 30 # Interval for auto-saving document, in seconds + self.emphLabels = True # Add emphasis to H1 and H2 item labels + self._backupPath = None # Backup path to use, can be none + self.backupOnClose = False # Flag for running automatic backups + self.askBeforeBackup = True # Flag for asking before running automatic backup # Text Editor Settings self.textFont = None # Editor font @@ -173,6 +170,12 @@ class Config: # Spell Checking Settings self.spellLanguage = "en" + # State + self.isFullScreen = False # Last fullscreen state + self.showRefPanel = True # The reference panel for the viewer is visible + self.viewComments = True # Comments are shown in the viewer + self.viewSynopsis = True # Synopsis is shown in the viewer + # Search Bar Switches self.searchCase = False self.searchWord = False @@ -181,16 +184,6 @@ class Config: self.searchNextFile = False self.searchMatchCap = False - # Backup Settings - self._backupPath = None - self.backupOnClose = False - self.askBeforeBackup = True - - # State - self.showRefPanel = True # The reference panel for the viewer is visible - self.viewComments = True # Comments are shown in the viewer - self.viewSynopsis = True # Synopsis is shown in the viewer - # System and App Information # ========================== @@ -259,15 +252,15 @@ class Config: @property def mainWinSize(self): - return [int(x*self.guiScale) for x in self._winGeometry] + return [int(x*self.guiScale) for x in self._mainWinSize] @property def preferencesWinSize(self): - return [int(x*self.guiScale) for x in self._prefGeometry] + return [int(x*self.guiScale) for x in self._prefsWinSize] @property def projLoadColWidths(self): - return [int(x*self.guiScale) for x in self._projColWidth] + return [int(x*self.guiScale) for x in self._projLoadCols] @property def mainPanePos(self): @@ -281,6 +274,25 @@ class Config: def outlinePanePos(self): return [int(x*self.guiScale) for x in self._outlnPanePos] + ## + # Getters + ## + + def getTextWidth(self, focusMode=False): + """Get the text with for the correct editor mode.""" + if focusMode: + return self.pxInt(max(self.focusWidth, 200)) + else: + return self.pxInt(max(self.textWidth, 200)) + + def getTextMargin(self): + """Get the scaled text margin.""" + return self.pxInt(max(self.textMargin, 0)) + + def getTabWidth(self): + """Get the scaled tab width.""" + return self.pxInt(max(self.tabWidth, 0)) + ## # Setters ## @@ -293,48 +305,36 @@ class Config: """ newWidth = int(newWidth/self.guiScale) newHeight = int(newHeight/self.guiScale) - if abs(self._winGeometry[0] - newWidth) > 5: - self._winGeometry[0] = newWidth - self._confChanged = True - if abs(self._winGeometry[1] - newHeight) > 5: - self._winGeometry[1] = newHeight - self._confChanged = True + if abs(self._mainWinSize[0] - newWidth) > 5: + self._mainWinSize[0] = newWidth + if abs(self._mainWinSize[1] - newHeight) > 5: + self._mainWinSize[1] = newHeight return def setPreferencesWinSize(self, newWidth, newHeight): - """Sat the size of the Preferences dialog window. - """ - self._prefGeometry[0] = int(newWidth/self.guiScale) - self._prefGeometry[1] = int(newHeight/self.guiScale) - self._confChanged = True + """Set the size of the Preferences dialog window.""" + self._prefsWinSize[0] = int(newWidth/self.guiScale) + self._prefsWinSize[1] = int(newHeight/self.guiScale) return def setProjLoadColWidths(self, colWidths): - """Set the column widths of the Load Project dialog. - """ - self._projColWidth = [int(x/self.guiScale) for x in colWidths] - self._confChanged = True + """Set the column widths of the Load Project dialog.""" + self._projLoadCols = [int(x/self.guiScale) for x in colWidths] return def setMainPanePos(self, panePos): - """Set the position of the main GUI splitter. - """ + """Set the position of the main GUI splitter.""" self._mainPanePos = [int(x/self.guiScale) for x in panePos] - self._confChanged = True return def setViewPanePos(self, panePos): - """Set the position of the viewer meta data splitter. - """ + """Set the position of the viewer meta data splitter.""" self._viewPanePos = [int(x/self.guiScale) for x in panePos] - self._confChanged = True return def setOutlinePanePos(self, panePos): - """Set the position of the outline details splitter. - """ + """Set the position of the outline details splitter.""" self._outlnPanePos = [int(x/self.guiScale) for x in panePos] - self._confChanged = True return def setLastPath(self, lastPath): @@ -342,7 +342,7 @@ class Config: path is not a folder, the parent of the path is used instead. """ if isinstance(lastPath, (str, Path)): - lastPath = Path(lastPath) + lastPath = checkPath(lastPath, self._homePath) if not lastPath.is_dir(): lastPath = lastPath.parent if lastPath.is_dir(): @@ -351,15 +351,10 @@ class Config: return def setBackupPath(self, backupPath): - """Set the current backup path. - """ + """Set the current backup path.""" self._backupPath = checkPath(backupPath, None) return - def setConfigChanged(self, value): - self._confChanged = bool(value) - return - ## # Methods ## @@ -377,15 +372,13 @@ class Config: return int(theSize/self.guiScale) def dataPath(self, target=None): - """Return a path in the data folder. - """ + """Return a path in the data folder.""" if isinstance(target, str): return self._dataPath / target return self._dataPath def assetPath(self, target=None): - """Return a path in the assets folder. - """ + """Return a path in the assets folder.""" if isinstance(target, str): return self._appPath / "assets" / target return self._appPath / "assets" @@ -396,11 +389,10 @@ class Config: if isinstance(self._lastPath, Path): if self._lastPath.is_dir(): return self._lastPath - return Path.home().absolute() + return self._homePath def backupPath(self): - """Return the backup path. - """ + """Return the backup path.""" if isinstance(self._backupPath, Path): if self._backupPath.is_dir(): return self._backupPath @@ -415,6 +407,33 @@ class Config: self._errData = [] return errMessage + def listLanguages(self, lngSet): + """List localisation files in the i18n folder. The default GUI + language is British English (en_GB). + """ + if lngSet == self.LANG_NW: + fPre = "nw_" + fExt = ".qm" + langList = {"en_GB": QLocale("en_GB").nativeLanguageName().title()} + elif lngSet == self.LANG_PROJ: + fPre = "project_" + fExt = ".json" + langList = {"en_GB": QLocale("en_GB").nativeLanguageName().title()} + else: + return [] + + for qmFile in Path(self._nwLangPath).iterdir(): + qmName = qmFile.name + if not (qmFile.is_file() and qmName.startswith(fPre) and qmName.endswith(fExt)): + continue + + qmLang = qmName[len(fPre):-len(fExt)] + qmName = QLocale(qmLang).nativeLanguageName().title() + if qmLang and qmName and qmLang != "en_GB": + langList[qmLang] = qmName + + return sorted(langList.items(), key=lambda x: x[0]) + ## # Config Actions ## @@ -464,54 +483,26 @@ class Config: def initLocalisation(self, nwApp): """Initialise the localisation of the GUI. """ - self._qLocal = QLocale(self.guiLang) - QLocale.setDefault(self._qLocal) + self._qLocale = QLocale(self.guiLocale) + QLocale.setDefault(self._qLocale) self._qtTrans = {} langList = [ (self._qtLangPath, "qtbase"), # Qt 5.x - (self._nwLangPath, "qtbase"), # Alternative Qt 5.x (self._nwLangPath, "nw"), # novelWriter ] for lngPath, lngBase in langList: - for lngCode in self._qLocal.uiLanguages(): + for lngCode in self._qLocale.uiLanguages(): qTrans = QTranslator() lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_")) if lngFile not in self._qtTrans: - if qTrans.load(lngFile, str(lngPath)): - logger.debug("Loaded: %s/%s", lngPath, lngFile) + if qTrans.load(lngFile, lngPath): + logger.debug("Loaded: %s.qm", lngFile) nwApp.installTranslator(qTrans) self._qtTrans[lngFile] = qTrans return - def listLanguages(self, lngSet): - """List localisation files in the i18n folder. The default GUI - language is British English (en_GB). - """ - if lngSet == self.LANG_NW: - fPre = "nw_" - fExt = ".qm" - langList = {"en_GB": QLocale("en_GB").nativeLanguageName().title()} - elif lngSet == self.LANG_PROJ: - fPre = "project_" - fExt = ".json" - langList = {"en_GB": QLocale("en_GB").nativeLanguageName().title()} - else: - return [] - - for qmFile in Path(self._nwLangPath).iterdir(): - qmName = qmFile.name - if not (qmFile.is_file() and qmName.startswith(fPre) and qmName.endswith(fExt)): - continue - - qmLang = qmName[len(fPre):-len(fExt)] - qmName = QLocale(qmLang).nativeLanguageName().title() - if qmLang and qmName and qmLang != "en_GB": - langList[qmLang] = qmName - - return sorted(langList.items(), key=lambda x: x[0]) - def loadConfig(self): """Load preferences from file and replace default settings. """ @@ -534,28 +525,31 @@ class Config: cnfSec = "Main" self.guiTheme = theConf.rdStr(cnfSec, "theme", self.guiTheme) self.guiSyntax = theConf.rdStr(cnfSec, "syntax", self.guiSyntax) - self.guiFont = theConf.rdStr(cnfSec, "guifont", self.guiFont) - self.guiFontSize = theConf.rdInt(cnfSec, "guifontsize", self.guiFontSize) - self.lastNotes = theConf.rdStr(cnfSec, "lastnotes", self.lastNotes) - self.guiLang = theConf.rdStr(cnfSec, "guilang", self.guiLang) + self.guiFont = theConf.rdStr(cnfSec, "font", self.guiFont) + self.guiFontSize = theConf.rdInt(cnfSec, "fontsize", self.guiFontSize) + self.guiLocale = theConf.rdStr(cnfSec, "localisation", self.guiLocale) self.hideVScroll = theConf.rdBool(cnfSec, "hidevscroll", self.hideVScroll) self.hideHScroll = theConf.rdBool(cnfSec, "hidehscroll", self.hideHScroll) + self.lastNotes = theConf.rdStr(cnfSec, "lastnotes", self.lastNotes) + self._lastPath = theConf.rdPath(cnfSec, "lastpath", self._lastPath) # Sizes cnfSec = "Sizes" - self._winGeometry = theConf.rdIntList(cnfSec, "geometry", self._winGeometry) - self._prefGeometry = theConf.rdIntList(cnfSec, "preferences", self._prefGeometry) - self._projColWidth = theConf.rdIntList(cnfSec, "projcols", self._projColWidth) + self._mainWinSize = theConf.rdIntList(cnfSec, "mainwindow", self._mainWinSize) + self._prefsWinSize = theConf.rdIntList(cnfSec, "preferences", self._prefsWinSize) + self._projLoadCols = theConf.rdIntList(cnfSec, "projloadcols", self._projLoadCols) self._mainPanePos = theConf.rdIntList(cnfSec, "mainpane", self._mainPanePos) self._viewPanePos = theConf.rdIntList(cnfSec, "viewpane", self._viewPanePos) self._outlnPanePos = theConf.rdIntList(cnfSec, "outlinepane", self._outlnPanePos) - self.isFullScreen = theConf.rdBool(cnfSec, "fullscreen", self.isFullScreen) # Project cnfSec = "Project" - self.autoSaveProj = theConf.rdInt(cnfSec, "autosaveproject", self.autoSaveProj) - self.autoSaveDoc = theConf.rdInt(cnfSec, "autosavedoc", self.autoSaveDoc) - self.emphLabels = theConf.rdBool(cnfSec, "emphlabels", self.emphLabels) + self.autoSaveProj = theConf.rdInt(cnfSec, "autosaveproject", self.autoSaveProj) + self.autoSaveDoc = theConf.rdInt(cnfSec, "autosavedoc", self.autoSaveDoc) + self.emphLabels = theConf.rdBool(cnfSec, "emphlabels", self.emphLabels) + self._backupPath = theConf.rdPath(cnfSec, "backuppath", self._backupPath) + self.backupOnClose = theConf.rdBool(cnfSec, "backuponclose", self.backupOnClose) + self.askBeforeBackup = theConf.rdBool(cnfSec, "askbeforebackup", self.askBeforeBackup) # Editor cnfSec = "Editor" @@ -596,14 +590,9 @@ class Config: self.stopWhenIdle = theConf.rdBool(cnfSec, "stopwhenidle", self.stopWhenIdle) self.userIdleTime = theConf.rdInt(cnfSec, "useridletime", self.userIdleTime) - # Backup - cnfSec = "Backup" - self._backupPath = theConf.rdPath(cnfSec, "backuppath", self._backupPath) - self.backupOnClose = theConf.rdBool(cnfSec, "backuponclose", self.backupOnClose) - self.askBeforeBackup = theConf.rdBool(cnfSec, "askbeforebackup", self.askBeforeBackup) - # State cnfSec = "State" + self.isFullScreen = theConf.rdBool(cnfSec, "fullscreen", self.isFullScreen) self.showRefPanel = theConf.rdBool(cnfSec, "showrefpanel", self.showRefPanel) self.viewComments = theConf.rdBool(cnfSec, "viewcomments", self.viewComments) self.viewSynopsis = theConf.rdBool(cnfSec, "viewsynopsis", self.viewSynopsis) @@ -614,9 +603,14 @@ class Config: self.searchNextFile = theConf.rdBool(cnfSec, "searchnextfile", self.searchNextFile) self.searchMatchCap = theConf.rdBool(cnfSec, "searchmatchcap", self.searchMatchCap) - # Path - cnfSec = "Path" - self._lastPath = theConf.rdPath(cnfSec, "lastpath", self._lastPath) + # Deprecated Settings or Locations as of 2.0 + # These will be loaded for a few minor releases until the users have converted them + self.guiFont = theConf.rdStr("Main", "guifont", self.guiFont) + self.guiFontSize = theConf.rdInt("Main", "guifontsize", self.guiFontSize) + self.guiLocale = theConf.rdStr("Main", "guilang", self.guiLocale) + self._backupPath = theConf.rdPath("Backup", "backuppath", self._backupPath) + self.backupOnClose = theConf.rdBool("Backup", "backuponclose", self.backupOnClose) + self.askBeforeBackup = theConf.rdBool("Backup", "askbeforebackup", self.askBeforeBackup) # Check Certain Values for None self.spellLanguage = self._checkNone(self.spellLanguage) @@ -639,32 +633,38 @@ class Config: theConf = NWConfigParser() + theConf["Meta"] = { + "timestamp": formatTimeStamp(time()), + } + theConf["Main"] = { - "timestamp": formatTimeStamp(time()), - "theme": str(self.guiTheme), - "syntax": str(self.guiSyntax), - "guifont": str(self.guiFont), - "guifontsize": str(self.guiFontSize), - "lastnotes": str(self.lastNotes), - "guilang": str(self.guiLang), - "hidevscroll": str(self.hideVScroll), - "hidehscroll": str(self.hideHScroll), + "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), + "lastnotes": str(self.lastNotes), + "lastpath": str(self._lastPath), } theConf["Sizes"] = { - "geometry": self._packList(self._winGeometry), - "preferences": self._packList(self._prefGeometry), - "projcols": self._packList(self._projColWidth), - "mainpane": self._packList(self._mainPanePos), - "viewpane": self._packList(self._viewPanePos), - "outlinepane": self._packList(self._outlnPanePos), - "fullscreen": str(self.isFullScreen), + "mainwindow": self._packList(self._mainWinSize), + "preferences": self._packList(self._prefsWinSize), + "projloadcols": self._packList(self._projLoadCols), + "mainpane": self._packList(self._mainPanePos), + "viewpane": self._packList(self._viewPanePos), + "outlinepane": self._packList(self._outlnPanePos), } theConf["Project"] = { "autosaveproject": str(self.autoSaveProj), "autosavedoc": str(self.autoSaveDoc), "emphlabels": str(self.emphLabels), + "backuppath": str(self._backupPath or ""), + "backuponclose": str(self.backupOnClose), + "askbeforebackup": str(self.askBeforeBackup), } theConf["Editor"] = { @@ -706,13 +706,8 @@ class Config: "useridletime": str(self.userIdleTime), } - theConf["Backup"] = { - "backuppath": str(self._backupPath or ""), - "backuponclose": str(self.backupOnClose), - "askbeforebackup": str(self.askBeforeBackup), - } - theConf["State"] = { + "fullscreen": str(self.isFullScreen), "showrefpanel": str(self.showRefPanel), "viewcomments": str(self.viewComments), "viewsynopsis": str(self.viewSynopsis), @@ -724,16 +719,11 @@ class Config: "searchmatchcap": str(self.searchMatchCap), } - theConf["Path"] = { - "lastpath": str(self._lastPath), - } - # Write config file cnfPath = self._confPath / nwFiles.CONF_FILE try: with open(cnfPath, mode="w", encoding="utf-8") as outFile: theConf.write(outFile) - self._confChanged = False except Exception as exc: logger.error("Could not save config file") logException() @@ -744,61 +734,6 @@ class Config: return True - ## - # Setters - ## - - def setShowRefPanel(self, checkState): - """Set the visibility state of the reference panel. - """ - self.showRefPanel = checkState - self._confChanged = True - return - - def setViewComments(self, viewState): - """Set the visibility state of comments in the viewer. - """ - self.viewComments = viewState - self._confChanged = True - return - - def setViewSynopsis(self, viewState): - """Set the visibility state of synopsis comments in the viewer. - """ - self.viewSynopsis = viewState - self._confChanged = True - return - - ## - # Default Setters - ## - - def setDefaultGuiTheme(self): - """Reset the GUI theme to default value. - """ - self.guiTheme = "default" - - def setDefaultSyntaxTheme(self): - """Reset the syntax theme to default value. - """ - self.guiSyntax = "default_light" - - ## - # Getters - ## - - def getTextWidth(self, focusMode=False): - if focusMode: - return self.pxInt(max(self.focusWidth, 200)) - else: - return self.pxInt(max(self.textWidth, 200)) - - def getTextMargin(self): - return self.pxInt(max(self.textMargin, 0)) - - def getTabWidth(self): - return self.pxInt(max(self.tabWidth, 0)) - ## # Internal Functions ## diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 5fe3626a..568ee265 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -125,6 +125,7 @@ class GuiPreferences(PagedDialog): self.tabQuote.saveValues() self._saveWindowSize() + self.mainConf.saveConfig() self.accept() return @@ -170,18 +171,18 @@ class GuiPreferencesGeneral(QWidget): minWidth = self.mainConf.pxInt(200) # Select Locale - self.guiLang = QComboBox() - self.guiLang.setMinimumWidth(minWidth) + self.guiLocale = QComboBox() + self.guiLocale.setMinimumWidth(minWidth) theLangs = self.mainConf.listLanguages(self.mainConf.LANG_NW) for lang, langName in theLangs: - self.guiLang.addItem(langName, lang) - langIdx = self.guiLang.findData(self.mainConf.guiLang) + self.guiLocale.addItem(langName, lang) + langIdx = self.guiLocale.findData(self.mainConf.guiLocale) if langIdx != -1: - self.guiLang.setCurrentIndex(langIdx) + self.guiLocale.setCurrentIndex(langIdx) self.mainForm.addRow( self.tr("Main GUI language"), - self.guiLang, + self.guiLocale, self.tr("Requires restart to take effect.") ) @@ -286,7 +287,7 @@ class GuiPreferencesGeneral(QWidget): def saveValues(self): """Save the values set for this tab. """ - guiLang = self.guiLang.currentData() + guiLocale = self.guiLocale.currentData() guiTheme = self.guiTheme.currentData() guiSyntax = self.guiSyntax.currentData() guiFont = self.guiFont.text() @@ -296,12 +297,12 @@ class GuiPreferencesGeneral(QWidget): # Update Flags self.prefsGui._updateTheme |= self.mainConf.guiTheme != guiTheme self.prefsGui._updateSyntax |= self.mainConf.guiSyntax != guiSyntax - self.prefsGui._needsRestart |= self.mainConf.guiLang != guiLang + self.prefsGui._needsRestart |= self.mainConf.guiLocale != guiLocale self.prefsGui._needsRestart |= self.mainConf.guiFont != guiFont self.prefsGui._needsRestart |= self.mainConf.guiFontSize != guiFontSize self.prefsGui._refreshTree |= self.mainConf.emphLabels != emphLabels - self.mainConf.guiLang = guiLang + self.mainConf.guiLocale = guiLocale self.mainConf.guiTheme = guiTheme self.mainConf.guiSyntax = guiSyntax self.mainConf.guiFont = guiFont @@ -311,8 +312,6 @@ class GuiPreferencesGeneral(QWidget): self.mainConf.hideVScroll = self.hideVScroll.isChecked() self.mainConf.hideHScroll = self.hideHScroll.isChecked() - self.mainConf.setConfigChanged(True) - return ## @@ -458,8 +457,6 @@ class GuiPreferencesProjects(QWidget): self.mainConf.stopWhenIdle = self.stopWhenIdle.isChecked() self.mainConf.userIdleTime = round(self.userIdleTime.value() * 60) - self.mainConf.setConfigChanged(True) - return ## @@ -629,8 +626,6 @@ class GuiPreferencesDocuments(QWidget): self.mainConf.textMargin = self.textMargin.value() self.mainConf.tabWidth = self.tabWidth.value() - self.mainConf.setConfigChanged(True) - return ## @@ -820,8 +815,6 @@ class GuiPreferencesEditor(QWidget): self.mainConf.autoScroll = self.autoScroll.isChecked() self.mainConf.autoScrollPos = self.autoScrollPos.value() - self.mainConf.setConfigChanged(True) - return # END Class GuiPreferencesEditor @@ -911,8 +904,6 @@ class GuiPreferencesSyntax(QWidget): # Text Errors self.mainConf.showMultiSpaces = self.showMultiSpaces.isChecked() - self.mainConf.setConfigChanged(True) - return ## @@ -1065,8 +1056,6 @@ class GuiPreferencesAutomation(QWidget): self.mainConf.fmtPadAfter = self.fmtPadAfter.text().strip() self.mainConf.fmtPadThin = self.fmtPadThin.isChecked() - self.mainConf.setConfigChanged(True) - return ## @@ -1185,9 +1174,6 @@ class GuiPreferencesQuotes(QWidget): self.mainConf.fmtSingleQuotes[1] = self.quoteSym["SC"].text() self.mainConf.fmtDoubleQuotes[0] = self.quoteSym["DO"].text() self.mainConf.fmtDoubleQuotes[1] = self.quoteSym["DC"].text() - - self.mainConf.setConfigChanged(True) - return ## diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 808749df..e498a100 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -1148,7 +1148,7 @@ class GuiDocViewFooter(QWidget): def _doToggleComments(self, theState): """Toggle the view comment button and reload the document. """ - self.mainConf.setViewComments(theState) + self.mainConf.viewComments = theState self.docViewer.reloadText() return @@ -1156,7 +1156,7 @@ class GuiDocViewFooter(QWidget): def _doToggleSynopsis(self, theState): """Toggle the view synopsis button and reload the document. """ - self.mainConf.setViewSynopsis(theState) + self.mainConf.viewSynopsis = theState self.docViewer.reloadText() return diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 2d69806c..cf10f78a 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -185,9 +185,14 @@ class GuiTheme: """Load the currently specified GUI theme. """ guiTheme = self.mainConf.guiTheme + if guiTheme not in self._availThemes: + logger.error("Could not find GUI theme '%s'", guiTheme) + guiTheme = "default" + self.mainConf.guiTheme = guiTheme + themeFile = self._availThemes.get(guiTheme, None) if themeFile is None: - logger.error("Could not find GUI theme '%s'", guiTheme) + logger.error("Could not load GUI theme") return False # Config File @@ -266,9 +271,14 @@ class GuiTheme: """Load the currently specified syntax highlighter theme. """ guiSyntax = self.mainConf.guiSyntax + if guiSyntax not in self._availSyntax: + logger.error("Could not find syntax theme '%s'", guiSyntax) + guiSyntax = "default_light" + self.mainConf.guiSyntax = guiSyntax + syntaxFile = self._availSyntax.get(guiSyntax, None) if syntaxFile is None: - logger.error("Could not find syntax theme '%s'", guiSyntax) + logger.error("Could not load syntax theme") return False logger.info("Loading syntax theme '%s'", guiSyntax) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 8c0e1cfb..6e96fdaf 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -79,7 +79,7 @@ class GuiMain(QMainWindow): logger.info("Qt5: %s (%d)", self.mainConf.verQtString, self.mainConf.verQtValue) logger.info("PyQt5: %s (%d)", self.mainConf.verPyQtString, self.mainConf.verPyQtValue) logger.info("Python: %s (0x%x)", self.mainConf.verPyString, self.mainConf.verPyHexVal) - logger.info("GUI Language: %s", self.mainConf.guiLang) + logger.info("GUI Language: %s", self.mainConf.guiLocale) # Core Classes # ============ @@ -290,11 +290,6 @@ class GuiMain(QMainWindow): "and make sure you take regular backups." ), nwAlert.WARN) - # If a project path was provided at command line, open it - if self.mainConf.cmdOpen is not None: - logger.debug("Opening project from additional command line option") - self.openProject(self.mainConf.cmdOpen) - logger.info("novelWriter is ready ...") self.setStatus(self.tr("novelWriter is ready ...")) @@ -327,13 +322,22 @@ class GuiMain(QMainWindow): self.asDocTimer.setInterval(int(self.mainConf.autoSaveDoc*1000)) return True - def releaseNotes(self): - """Determine whether release notes need to be shown, and show - them by calling the About dialog. + def postLaunchTasks(self, cmdOpen): + """This function is called after the main window is created to + determine what to open or show after initialisation. """ + if cmdOpen: + logger.info("Command line path: %s", cmdOpen) + self.openProject(cmdOpen) + + if not self.hasProject: + self.showProjectLoadDialog() + + # Determine whether release notes need to be shown or not if hexToInt(self.mainConf.lastNotes) < hexToInt(novelwriter.__hexversion__): self.mainConf.lastNotes = novelwriter.__hexversion__ self.showAboutNWDialog(showNotes=True) + return ## @@ -1173,7 +1177,7 @@ class GuiMain(QMainWindow): if self.viewMeta.isVisible(): self.mainConf.setViewPanePos(self.splitView.sizes()) - self.mainConf.setShowRefPanel(self.viewMeta.isVisible()) + self.mainConf.showRefPanel = self.viewMeta.isVisible() if not self.mainConf.isFullScreen: self.mainConf.setMainWinSize(self.width(), self.height()) diff --git a/tests/conftest.py b/tests/conftest.py index 28034a18..39cc96cd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -113,7 +113,7 @@ def tmpConf(tmpPath): theConf = Config() theConf.initConfig(tmpPath, tmpPath) theConf.setLastPath(tmpPath) - theConf.guiLang = "en_GB" + theConf.guiLocale = "en_GB" return theConf @@ -127,7 +127,7 @@ def fncConf(fncPath): theConf = Config() theConf.initConfig(fncPath, fncPath) theConf.setLastPath(fncPath) - theConf.guiLang = "en_GB" + theConf.guiLocale = "en_GB" return theConf diff --git a/tests/mock.py b/tests/mock.py index 74623dd1..e70034bd 100644 --- a/tests/mock.py +++ b/tests/mock.py @@ -35,6 +35,7 @@ class MockGuiMain(QObject): self.hasProject = True self.theProject = None self.mainStatus = MockStatusBar() + self.projPath = "" # Test Variables self.askResponse = True @@ -43,7 +44,7 @@ class MockGuiMain(QObject): return - def releaseNotes(self): + def postLaunchTasks(self, cmdOpen): return def makeAlert(self, message, level=0, exception=None): @@ -61,6 +62,7 @@ class MockGuiMain(QObject): return def openProject(self, projPath): + self.projPath = projPath return def rebuildIndex(self): diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf index 8c38cc09..ed621ba2 100644 --- a/tests/reference/baseConfig_novelwriter.conf +++ b/tests/reference/baseConfig_novelwriter.conf @@ -1,27 +1,32 @@ +[Meta] +timestamp = 2022-11-10 11:10:10 + [Main] -timestamp = 2022-10-26 11:19:49 theme = default syntax = default_light -guifont = -guifontsize = 11 -lastnotes = 0x0 -guilang = en_GB +font = +fontsize = 11 +localisation = en_GB hidevscroll = False hidehscroll = False +lastnotes = 0x0 +lastpath = /home/vkbo [Sizes] -geometry = 1200, 650 +mainwindow = 1200, 650 preferences = 700, 615 -projcols = 200, 60, 140 +projloadcols = 280, 60, 160 mainpane = 300, 800 viewpane = 500, 150 outlinepane = 500, 150 -fullscreen = False [Project] autosaveproject = 60 autosavedoc = 30 emphlabels = True +backuppath = +backuponclose = False +askbeforebackup = True [Editor] textfont = None @@ -61,12 +66,8 @@ highlightemph = True stopwhenidle = True useridletime = 300 -[Backup] -backuppath = -backuponclose = False -askbeforebackup = True - [State] +fullscreen = False showrefpanel = True viewcomments = True viewsynopsis = True @@ -77,6 +78,3 @@ searchloop = False searchnextfile = False searchmatchcap = False -[Path] -lastpath = - diff --git a/tests/reference/guiPreferences_novelwriter.conf b/tests/reference/guiPreferences_novelwriter.conf index a97fa527..47c5579d 100644 --- a/tests/reference/guiPreferences_novelwriter.conf +++ b/tests/reference/guiPreferences_novelwriter.conf @@ -1,27 +1,32 @@ +[Meta] +timestamp = 2022-11-10 11:10:13 + [Main] -timestamp = 2022-10-26 11:19:51 theme = default syntax = default_light -guifont = Cantarell -guifontsize = 12 -lastnotes = 0x0 -guilang = en_GB +font = Cantarell +fontsize = 12 +localisation = en_GB hidevscroll = True hidehscroll = True +lastnotes = 0x0 +lastpath = /home/vkbo/Code/novelWriter/Source/tests/temp/function [Sizes] -geometry = 1200, 650 +mainwindow = 1200, 650 preferences = 699, 614 -projcols = 200, 60, 140 +projloadcols = 280, 60, 160 mainpane = 300, 800 viewpane = 500, 150 outlinepane = 500, 150 -fullscreen = False [Project] autosaveproject = 40 autosavedoc = 20 emphlabels = True +backuppath = some/dir +backuponclose = True +askbeforebackup = True [Editor] textfont = None @@ -61,12 +66,8 @@ highlightemph = False stopwhenidle = True useridletime = 300 -[Backup] -backuppath = some/dir -backuponclose = True -askbeforebackup = True - [State] +fullscreen = False showrefpanel = True viewcomments = True viewsynopsis = True @@ -77,6 +78,3 @@ searchloop = False searchnextfile = False searchmatchcap = False -[Path] -lastpath = - diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 8fb150cc..a11095c3 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -166,7 +166,7 @@ def testBaseConfig_Localisation(fncPath, tstPaths): i18nDir = fncPath / "i18n" i18nDir.mkdir() - tstConf._nwLangPath = i18nDir + tstConf._nwLangPath = str(i18nDir) copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_en_GB.qm") writeFile(i18nDir / "nw_en_GB.ts", "") @@ -254,17 +254,17 @@ def testBaseConfig_SettersGetters(tmpConf): # Window Size tmpConf.guiScale = 1.0 tmpConf.setMainWinSize(1205, 655) - assert tmpConf._confChanged is False + assert tmpConf.mainWinSize == [1200, 650] tmpConf.guiScale = 2.0 tmpConf.setMainWinSize(70, 70) assert tmpConf.mainWinSize == [70, 70] - assert tmpConf._winGeometry == [35, 35] + assert tmpConf._mainWinSize == [35, 35] tmpConf.guiScale = 1.0 tmpConf.setMainWinSize(70, 70) assert tmpConf.mainWinSize == [70, 70] - assert tmpConf._winGeometry == [70, 70] + assert tmpConf._mainWinSize == [70, 70] tmpConf.setMainWinSize(1200, 650) @@ -272,12 +272,12 @@ def testBaseConfig_SettersGetters(tmpConf): tmpConf.guiScale = 2.0 tmpConf.setPreferencesWinSize(70, 70) assert tmpConf.preferencesWinSize == [70, 70] - assert tmpConf._prefGeometry == [35, 35] + assert tmpConf._prefsWinSize == [35, 35] tmpConf.guiScale = 1.0 tmpConf.setPreferencesWinSize(70, 70) assert tmpConf.preferencesWinSize == [70, 70] - assert tmpConf._prefGeometry == [70, 70] + assert tmpConf._prefsWinSize == [70, 70] tmpConf.setPreferencesWinSize(700, 615) @@ -285,12 +285,12 @@ def testBaseConfig_SettersGetters(tmpConf): tmpConf.guiScale = 2.0 tmpConf.setProjLoadColWidths([10, 20, 30]) assert tmpConf.projLoadColWidths == [10, 20, 30] - assert tmpConf._projColWidth == [5, 10, 15] + assert tmpConf._projLoadCols == [5, 10, 15] tmpConf.guiScale = 1.0 tmpConf.setProjLoadColWidths([10, 20, 30]) assert tmpConf.projLoadColWidths == [10, 20, 30] - assert tmpConf._projColWidth == [10, 20, 30] + assert tmpConf._projLoadCols == [10, 20, 30] tmpConf.setProjLoadColWidths([200, 60, 140]) @@ -348,24 +348,6 @@ def testBaseConfig_SettersGetters(tmpConf): assert tmpConf.getTextMargin() == 80 assert tmpConf.getTabWidth() == 80 - # Flag Setters - # ============ - - tmpConf.setShowRefPanel(False) - assert tmpConf.showRefPanel is False - tmpConf.setShowRefPanel(True) - assert tmpConf.showRefPanel is True - - tmpConf.setViewComments(False) - assert tmpConf.viewComments is False - tmpConf.setViewComments(True) - assert tmpConf.viewComments is True - - tmpConf.setViewSynopsis(False) - assert tmpConf.viewSynopsis is False - tmpConf.setViewSynopsis(True) - assert tmpConf.viewSynopsis is True - # END Test testBaseConfig_SettersGetters diff --git a/tests/test_base/test_base_init.py b/tests/test_base/test_base_init.py index 2a41ea38..2cc4d255 100644 --- a/tests/test_base/test_base_init.py +++ b/tests/test_base/test_base_init.py @@ -138,7 +138,6 @@ def testBaseInit_Options(monkeypatch, tmpPath): nwGUI = novelwriter.main( ["--testmode", f"--config={tmpPath}", f"--data={tmpPath}", "sample/"] ) - assert novelwriter.CONFIG.cmdOpen == "sample/" assert nwGUI.closeMain() == "closeMain" # END Test testBaseInit_Options diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index ec83b428..b36cc02e 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -215,8 +215,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, fncPath, tstPaths): qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton) nwPrefs._doClose() - assert theConf._confChanged is True - assert nwGUI.mainConf.saveConfig() projFile = fncPath / "novelwriter.conf" testFile = tstPaths.outDir / "guiPreferences_novelwriter.conf" diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 8c5670a8..2a6daf33 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -21,15 +21,18 @@ along with this program. If not, see . import pytest -from tools import C, cmpFiles, buildTestProject, XML_IGNORE, writeFile from shutil import copyfile +from tools import ( + C, cmpFiles, buildTestProject, XML_IGNORE, getGuiItem, writeFile +) + from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QMessageBox, QInputDialog +from PyQt5.QtWidgets import QDialog, QMessageBox, QInputDialog from novelwriter.enum import nwItemType, nwView, nwWidget from novelwriter.tools import GuiProjectWizard -from novelwriter.dialogs import GuiEditLabel +from novelwriter.dialogs import GuiEditLabel, GuiAbout, GuiProjectLoad from novelwriter.constants import nwFiles from novelwriter.gui.outline import GuiOutlineView from novelwriter.gui.projtree import GuiProjectTree @@ -62,7 +65,39 @@ def testGuiMain_ProjectBlocker(nwGUI): assert nwGUI.showProjectWordListDialog() is False assert nwGUI.showWritingStatsDialog() is False -# END Test testGuiMain_NoProject +# END Test testGuiMain_ProjectBlocker + + +@pytest.mark.gui +def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, prjLipsum): + """Test the handling of launch tasks. + """ + monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *a: None) + monkeypatch.setattr(GuiProjectLoad, "result", lambda *a: QDialog.Accepted) + nwGUI.mainConf.lastNotes = "0x0" + + # Open Lipsum project + nwGUI.postLaunchTasks(prjLipsum) + nwGUI.closeProject() + + # Check that release notes opened + qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000) + msgAbout = getGuiItem("GuiAbout") + assert isinstance(msgAbout, GuiAbout) + assert msgAbout.tabBox.currentWidget() == msgAbout.pageNotes + msgAbout.accept() + + # Check that project open dialog launches + nwGUI.postLaunchTasks(None) + qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000) + nwLoad = getGuiItem("GuiProjectLoad") + assert isinstance(nwLoad, GuiProjectLoad) + nwLoad.show() + nwLoad.reject() + + # qtbot.stop() + +# END Test testGuiMain_Launch @pytest.mark.gui diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 42f5c7c5..5ec03c30 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -150,7 +150,10 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncPath): # Check handling of broken theme settings mainConf.guiTheme = "not_a_theme" + availThemes = mainTheme._availThemes + mainTheme._availThemes = {} assert mainTheme.loadTheme() is False + mainTheme._availThemes = availThemes # Check handling of unreadable file mainConf.guiTheme = "default" @@ -216,8 +219,11 @@ def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncPath): assert mainTheme.listSyntax() == mainTheme._syntaxList # Check handling of broken theme settings + availSyntax = mainTheme._availSyntax + mainTheme._availSyntax = {} mainConf.guiSyntax = "not_a_syntax" assert mainTheme.loadSyntax() is False + mainTheme._availSyntax = availSyntax # Check handling of unreadable file mainConf.guiSyntax = "default_light" From a1ddc6ce1d945588a2c912f987db6e39483986da Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 10 Nov 2022 13:24:13 +0100 Subject: [PATCH 4/4] Fix test file comparison filters --- tests/test_base/test_base_config.py | 2 +- tests/test_dialogs/test_dlg_preferences.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index a11095c3..0159952b 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -112,7 +112,7 @@ def testBaseConfig_InitLoadSave(monkeypatch, fncPath, tstPaths): # Check that we have a default file copyfile(confFile, testFile) - ignore = ("timestamp", "lastnotes", "guilang", "lastpath") + ignore = ("timestamp", "lastnotes", "localisation", "lastpath") assert cmpFiles(testFile, compFile, ignoreStart=ignore) tstConf.errorText() # This clears the error cache diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index b36cc02e..a5990d95 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -221,7 +221,7 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, fncPath, tstPaths): compFile = tstPaths.refDir / "guiPreferences_novelwriter.conf" copyfile(projFile, testFile) ignTuple = ( - "timestamp", "guifont", "lastnotes", "guilang", "geometry", + "timestamp", "font", "lastnotes", "localisation", "geometry", "preferences", "projcols", "mainpane", "docpane", "viewpane", "outlinepane", "textfont", "textsize", "lastpath", "backuppath" )