diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index 55e73ce9..68a049f4 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -27,7 +27,6 @@ import sys import getopt import logging -from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication, QErrorMessage from novelwriter.error import exceptionHandler, logException @@ -249,7 +248,6 @@ def main(sysArgs=None): nwApp = QApplication([CONFIG.appName, (f"-style={qtStyle}")]) nwApp.setApplicationName(CONFIG.appName) nwApp.setApplicationVersion(__version__) - nwApp.setWindowIcon(QIcon(CONFIG.appIcon)) nwApp.setOrganizationDomain(__domain__) # Connect the exception handler before making the main GUI diff --git a/novelwriter/config.py b/novelwriter/config.py index 17b99530..ef342f4d 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -51,6 +51,9 @@ class Config: def __init__(self): + # Initialisation + # ============== + # Set Application Variables self.appName = "novelWriter" self.appHandle = "novelwriter" @@ -62,20 +65,37 @@ class Config: self._confPath = confRoot.absolute() / self.appHandle # The user config location self._dataPath = dataRoot.absolute() / self.appHandle # The user data location - self.cmdOpen = None # Path from command line for project to be opened on launch - self.lastPath = None # The last user-selected folder (browse dialogs) - self.appPath = None # The full path to the novelwriter package folder - self.appRoot = None # The full path to the novelwriter root folder - self.appIcon = None # The full path to the novelwriter icon file - self.assetPath = None # The full path to the novelwriter/assets folder - self.pdfDocs = None # The location of the PDF manual, if it exists + if hasattr(sys, "_MEIPASS"): + self._appPath = Path(sys._MEIPASS).absolute() + else: + self._appPath = Path(__file__).parent.absolute() + + self._appRoot = self._appPath.parent + if self._appRoot.is_file(): + # novelWriter is packaged as a single file + self._appRoot = self._appRoot.parent + self._appPath = self._appRoot + + self.cmdOpen = None # Path from command line for project to be opened on launch + self.lastPath = None # The last user-selected folder (browse dialogs) + self.pdfDocs = None # The location of the PDF manual, if it exists # 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 - # General + # Localisation Info + self._qLocal = QLocale.system() + self._qtTrans = {} + self._qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath) + self._nwLangPath = str(self._appPath / "assets" / "i18n") + + # User Settings + # ============= + + # General GUI Settings + self.guiLang = self._qLocal.name() self.guiTheme = "" # GUI theme self.guiSyntax = "" # Syntax theme self.guiFont = "" # Defaults to system default font @@ -86,14 +106,7 @@ class Config: self.setDefaultGuiTheme() self.setDefaultSyntaxTheme() - # Localisation - self.qLocal = QLocale.system() - self.guiLang = self.qLocal.name() - self.qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath) - self.nwLangPath = None - self.qtTrans = {} - - # Sizes + # Size Settings self.winGeometry = [1200, 650] self.prefGeometry = [700, 615] self.projColWidth = [200, 60, 140] @@ -103,16 +116,16 @@ class Config: self.outlnPanePos = [500, 150] self.isFullScreen = False - # Features + # 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 - # Project + # Project Settings self.autoSaveProj = 60 # Interval for auto-saving project in seconds self.autoSaveDoc = 30 # Interval for auto-saving document in seconds - # Text Editor + # Text Editor Settings self.textFont = None # Editor font self.textSize = 12 # Editor font size self.textWidth = 700 # Editor text width @@ -151,7 +164,7 @@ class Config: self.stopWhenIdle = True # Stop the status bar clock when the user is idle self.userIdleTime = 300 # Time of inactivity to consider user idle - # User-Selected Symbols + # User-Selected Symbol Settings self.fmtApostrophe = nwUnicode.U_RSQUO self.fmtSingleQuotes = [nwUnicode.U_LSQUO, nwUnicode.U_RSQUO] self.fmtDoubleQuotes = [nwUnicode.U_LDQUO, nwUnicode.U_RDQUO] @@ -159,7 +172,7 @@ class Config: self.fmtPadAfter = "" self.fmtPadThin = False - # Spell Checking + # Spell Checking Settings self.spellLanguage = None # Search Bar Switches @@ -170,7 +183,7 @@ class Config: self.searchNextFile = False self.searchMatchCap = False - # Backup + # Backup Settings self.backupPath = "" self.backupOnClose = False self.askBeforeBackup = True @@ -180,6 +193,9 @@ class Config: self.viewComments = True # Comments are shown in the viewer self.viewSynopsis = True # Synopsis is shown in the viewer + # System and App Information + # ========================== + # Check Qt5 Versions verQt = splitVersionNumber(QT_VERSION_STR) self.verQtString = QT_VERSION_STR @@ -254,6 +270,13 @@ class Config: return self._dataPath / target return self._dataPath + def getAssetPath(self, target=None): + """Return a path in the assets folder. + """ + if isinstance(target, str): + return self._appPath / "assets" / target + return self._appPath / "assets" + ## # Config Actions ## @@ -271,29 +294,12 @@ class Config: logger.info("Setting data path from alternative path: %s", dataPath) self._dataPath = Path(dataPath) - logger.debug("Config path: %s", self._confPath) - logger.debug("Data path: %s", self._dataPath) + logger.debug("Config Path: %s", self._confPath) + logger.debug("Data Path: %s", self._dataPath) + logger.debug("App Root: %s", self._appRoot) + logger.debug("App Path: %s", self._appPath) self.lastPath = os.path.expanduser("~") - self.appPath = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__))) - self.appRoot = os.path.abspath(os.path.join(self.appPath, os.path.pardir)) - - if os.path.isfile(self.appRoot): - # novelWriter is packaged as a single file, so the app and - # root paths are the same, and equal to the folder that - # contains the single executable. - self.appRoot = os.path.dirname(self.appRoot) - self.appPath = self.appRoot - - # Assets - self.assetPath = os.path.join(self.appPath, "assets") - self.appIcon = os.path.join(self.assetPath, "icons", "novelwriter.svg") - - # Internationalisation - self.nwLangPath = os.path.join(self.assetPath, "i18n") - - logger.debug("Assets: %s", self.assetPath) - logger.debug("App path: %s", self.appPath) logger.debug("Last path: %s", self.lastPath) # If the config and data folders don't not exist, create them @@ -324,9 +330,9 @@ class Config: self.spellLanguage = "en" # Look for a PDF version of the manual - pdfDocs = os.path.join(self.assetPath, "manual.pdf") - if os.path.isfile(pdfDocs): - logger.debug("Found manual: %s", pdfDocs) + pdfDocs = self._appPath / "assets" / "manual.pdf" + if pdfDocs.is_file(): + logger.debug("Found PDF manual: %s", pdfDocs) self.pdfDocs = pdfDocs logger.debug("Config initialisation complete") @@ -336,24 +342,24 @@ class Config: def initLocalisation(self, nwApp): """Initialise the localisation of the GUI. """ - self.qLocal = QLocale(self.guiLang) - QLocale.setDefault(self.qLocal) - self.qtTrans = {} + self._qLocal = QLocale(self.guiLang) + QLocale.setDefault(self._qLocal) + self._qtTrans = {} langList = [ - (self.qtLangPath, "qtbase"), # Qt 5.x - (self.nwLangPath, "qtbase"), # Alternative Qt 5.x - (self.nwLangPath, "nw"), # novelWriter + (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._qLocal.uiLanguages(): qTrans = QTranslator() lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_")) - if lngFile not in self.qtTrans: + if lngFile not in self._qtTrans: if qTrans.load(lngFile, lngPath): logger.debug("Loaded: %s", os.path.join(lngPath, lngFile)) nwApp.installTranslator(qTrans) - self.qtTrans[lngFile] = qTrans + self._qtTrans[lngFile] = qTrans return @@ -372,8 +378,8 @@ class Config: else: return [] - for qmFile in os.listdir(self.nwLangPath): - if not os.path.isfile(os.path.join(self.nwLangPath, qmFile)): + for qmFile in os.listdir(self._nwLangPath): + if not os.path.isfile(os.path.join(self._nwLangPath, qmFile)): continue if not qmFile.startswith(fPre) or not qmFile.endswith(fExt): continue diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py index 4f9d4954..97bab0de 100644 --- a/novelwriter/core/coretools.py +++ b/novelwriter/core/coretools.py @@ -24,7 +24,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ -import os import shutil import logging import novelwriter @@ -431,8 +430,8 @@ class ProjectBuilder: logger.error("No project path set for the example project") return False - pkgSample = os.path.join(self.mainConf.assetPath, "sample.zip") - if os.path.isfile(pkgSample): + pkgSample = self.mainConf.getAssetPath("sample.zip") + if pkgSample.is_file(): try: shutil.unpack_archive(pkgSample, projPath) except Exception as exc: diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index a0e3ac8d..49df331a 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -698,13 +698,13 @@ class NWProject(QObject): def _loadProjectLocalisation(self): """Load the language data for the current project language. """ - if self._data.language is None or self.mainConf.nwLangPath is None: + if self._data.language is None or self.mainConf._nwLangPath is None: self._langData = {} return False - langFile = Path(self.mainConf.nwLangPath) / f"project_{self._data.language}.json" + langFile = Path(self.mainConf._nwLangPath) / f"project_{self._data.language}.json" if not langFile.is_file(): - langFile = Path(self.mainConf.nwLangPath) / "project_en_GB.json" + langFile = Path(self.mainConf._nwLangPath) / "project_en_GB.json" try: with open(langFile, mode="r", encoding="utf-8") as inFile: diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py index 5b3a509e..8d4523b1 100644 --- a/novelwriter/dialogs/about.py +++ b/novelwriter/dialogs/about.py @@ -23,7 +23,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ -import os import logging import novelwriter @@ -233,7 +232,7 @@ class GuiAbout(QDialog): def _fillNotesPage(self): """Load the content for the Release Notes page. """ - docPath = os.path.join(self.mainConf.assetPath, "text", "release_notes.htm") + docPath = self.mainConf.getAssetPath("text") / "release_notes.htm" docText = readTextFile(docPath) if docText: self.pageNotes.setHtml(docText) @@ -244,7 +243,7 @@ class GuiAbout(QDialog): def _fillLicensePage(self): """Load the content for the Licence page. """ - docPath = os.path.join(self.mainConf.assetPath, "text", "gplv3_en.htm") + docPath = self.mainConf.getAssetPath("text") / "gplv3_en.htm" docText = readTextFile(docPath) if docText: self.pageLicense.setHtml(docText) diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index a3b7eca0..b4acd6f2 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -26,6 +26,7 @@ along with this program. If not, see . import logging import novelwriter +from pathlib import Path from urllib.parse import urljoin from urllib.request import pathname2url @@ -104,10 +105,11 @@ class GuiMainMenu(QMenuBar): def _openUserManualFile(self): """Open the documentation in PDF format. """ - if self.mainConf.pdfDocs is None: - return False - QDesktopServices.openUrl(QUrl(urljoin("file:", pathname2url(self.mainConf.pdfDocs)))) - return True + if isinstance(self.mainConf.pdfDocs, Path): + QDesktopServices.openUrl( + QUrl(urljoin("file:", pathname2url(str(self.mainConf.pdfDocs)))) + ) + return ## # Menu Builders @@ -881,7 +883,7 @@ class GuiMainMenu(QMenuBar): self.helpMenu.addAction(self.aHelpDocs) # Help > User Manual (PDF) - if self.mainConf.pdfDocs is not None: + if isinstance(self.mainConf.pdfDocs, Path): self.aPdfDocs = QAction(self.tr("User Manual (PDF)"), self) self.aPdfDocs.setShortcut("Shift+F1") self.aPdfDocs.triggered.connect(self._openUserManualFile) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index fc8a7f49..061c8b5c 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -24,12 +24,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ -import os import logging import novelwriter from math import ceil -from pathlib import Path from PyQt5.QtCore import Qt from PyQt5.QtWidgets import qApp @@ -120,9 +118,8 @@ class GuiTheme: self._availThemes = {} self._availSyntax = {} - self._listConf(self._availSyntax, os.path.join(self.mainConf.assetPath, "syntax")) - self._listConf(self._availThemes, os.path.join(self.mainConf.assetPath, "themes")) - + self._listConf(self._availSyntax, self.mainConf.getAssetPath("syntax")) + self._listConf(self._availThemes, self.mainConf.getAssetPath("themes")) self._listConf(self._availSyntax, self.mainConf.getDataPath("syntax")) self._listConf(self._availThemes, self.mainConf.getDataPath("themes")) @@ -380,7 +377,6 @@ class GuiTheme: def _listConf(self, targetDict, checkDir): """Scan for theme config files and populate the dictionary. """ - checkDir = Path(checkDir) if not checkDir.is_dir(): return False @@ -476,7 +472,7 @@ class GuiIcons: self._confName = "icons.conf" # Icon Theme Path - self._iconPath = os.path.join(self.mainConf.assetPath, "icons") + self._iconPath = self.mainConf.getAssetPath("icons") # Icon Theme Meta self.themeName = "" @@ -499,12 +495,12 @@ class GuiIcons: update functions for the classes where they're used. """ self._themeMap = {} - themePath = os.path.join(self.mainConf.assetPath, "icons", iconTheme) - if not os.path.isdir(themePath): + themePath = self._iconPath / iconTheme + if not themePath.is_dir(): logger.warning("No icons loaded for '%s'", iconTheme) return False - themeConf = os.path.join(themePath, self._confName) + themeConf = themePath / self._confName logger.info("Loading icon theme '%s'", iconTheme) # Config File @@ -535,8 +531,8 @@ class GuiIcons: if iconName not in self.ICON_KEYS: logger.error("Unknown icon name '%s' in config file", iconName) else: - iconPath = os.path.join(themePath, iconFile) - if os.path.isfile(iconPath): + iconPath = themePath / iconFile + if iconPath.is_file(): self._themeMap[iconName] = iconPath logger.debug("Icon slot '%s' using file '%s'", iconName, iconFile) else: @@ -572,18 +568,16 @@ class GuiIcons: if decoKey in self._themeMap: imgPath = self._themeMap[decoKey] elif decoKey in self.IMAGE_MAP: - imgPath = os.path.join( - self.mainConf.assetPath, "images", self.IMAGE_MAP[decoKey] - ) + imgPath = self.mainConf.getAssetPath("images") / self.IMAGE_MAP[decoKey] else: logger.error("Decoration with name '%s' does not exist", decoKey) return QPixmap() - if not os.path.isfile(imgPath): + if not imgPath.is_file(): logger.error("Asset not found: %s", imgPath) return QPixmap() - theDeco = QPixmap(imgPath) + theDeco = QPixmap(str(imgPath)) if pxW is not None and pxH is not None: return theDeco.scaled(pxW, pxH, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) elif pxW is None and pxH is not None: @@ -667,15 +661,14 @@ class GuiIcons: # If we just want the app icons, return right away if iconKey == "novelwriter": - return QIcon(os.path.join(self._iconPath, "novelwriter.svg")) + return QIcon(str(self._iconPath / "novelwriter.svg")) elif iconKey == "proj_nwx": - return QIcon(os.path.join(self._iconPath, "x-novelwriter-project.svg")) + return QIcon(str(self._iconPath / "x-novelwriter-project.svg")) # Otherwise, we load from the theme folder if iconKey in self._themeMap: - relPath = os.path.relpath(self._themeMap[iconKey], self._iconPath) - logger.debug("Loading: %s", relPath) - return QIcon(self._themeMap[iconKey]) + logger.debug("Loading: %s", self._themeMap[iconKey].name) + return QIcon(str(self._themeMap[iconKey])) # If we didn't find one, give up and return an empty icon logger.warning("Did not load an icon for '%s'", iconKey) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 98471d8d..ae918d9a 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -29,6 +29,7 @@ import novelwriter from enum import Enum from time import time +from pathlib import Path from datetime import datetime from PyQt5.QtCore import Qt, QTimer, QThreadPool, pyqtSlot @@ -95,7 +96,11 @@ class GuiMain(QMainWindow): # Prepare Main Window self.resize(*self.mainConf.getWinSize()) self._updateWindowTitle() - self.setWindowIcon(QIcon(self.mainConf.appIcon)) + + nwIcon = self.mainConf.getAssetPath("icons") / "novelwriter.svg" + self.nwIcon = QIcon(str(nwIcon)) if nwIcon.is_file() else QIcon() + self.setWindowIcon(self.nwIcon) + qApp.setWindowIcon(self.nwIcon) # Build the GUI # ============= @@ -1362,7 +1367,7 @@ class GuiMain(QMainWindow): # Help self.addAction(self.mainMenu.aHelpDocs) - if self.mainConf.pdfDocs is not None: + if isinstance(self.mainConf.pdfDocs, Path): self.addAction(self.mainMenu.aPdfDocs) return True diff --git a/novelwriter/tools/lipsum.py b/novelwriter/tools/lipsum.py index 976732dd..002f7769 100644 --- a/novelwriter/tools/lipsum.py +++ b/novelwriter/tools/lipsum.py @@ -23,7 +23,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ -import os import random import logging import novelwriter @@ -120,7 +119,7 @@ class GuiLipsum(QDialog): def _doInsert(self): """Load the text and insert it in the open document. """ - lipsumFile = os.path.join(self.mainConf.assetPath, "text", "lipsum.txt") + lipsumFile = self.mainConf.getAssetPath("text") / "lipsum.txt" lipsumText = readTextFile(lipsumFile).splitlines() if self.randSwitch.isChecked(): diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 42aa9b14..c0f5fe79 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -159,14 +159,14 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir): tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir) assert tstConf._confPath == tmpDir assert tstConf._dataPath == tmpDir - appRoot = tstConf.appRoot + appRoot = tstConf._appRoot mp.setattr("os.path.isfile", lambda *a: True) tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir) assert tstConf._confPath == tmpDir assert tstConf._dataPath == tmpDir - assert tstConf.appRoot == os.path.dirname(appRoot) - assert tstConf.appPath == os.path.dirname(appRoot) + assert tstConf._appRoot == os.path.dirname(appRoot) + assert tstConf._appPath == os.path.dirname(appRoot) assert tstConf.loadConfig() is True assert tstConf.saveConfig() is True @@ -199,7 +199,7 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir): i18nDir = os.path.join(fncDir, "i18n") os.mkdir(i18nDir) os.mkdir(os.path.join(i18nDir, "stuff")) - tstConf.nwLangPath = i18nDir + tstConf._nwLangPath = i18nDir copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(i18nDir, "nw_en_GB.qm")) writeFile(os.path.join(i18nDir, "nw_en_GB.ts"), "") diff --git a/tests/test_core/test_core_coretools.py b/tests/test_core/test_core_coretools.py index 4fc6c9ec..5398e4f7 100644 --- a/tests/test_core/test_core_coretools.py +++ b/tests/test_core/test_core_coretools.py @@ -372,7 +372,7 @@ def testCoreTools_NewCustomB(monkeypatch, fncDir, outDir, refDir, mockGUI, mockR @pytest.mark.core -def testCoreTools_NewSample(fncDir, tmpConf, mockGUI, tmpDir): +def testCoreTools_NewSample(monkeypatch, fncPath, tmpConf, tmpPath, mockGUI): """Check that we can create a new project can be created from the provided sample project via a zip file. """ @@ -380,7 +380,7 @@ def testCoreTools_NewSample(fncDir, tmpConf, mockGUI, tmpDir): "projName": "Test Sample", "projTitle": "Test Novel", "projAuthors": "Jane Doe\nJohn Doh\n", - "projPath": fncDir, + "projPath": fncPath, "popSample": True, "popMinimal": False, "popCustom": False, @@ -392,9 +392,11 @@ def testCoreTools_NewSample(fncDir, tmpConf, mockGUI, tmpDir): assert projBuild.buildProject({"popSample": True}) is False # Force the lookup path for assets to our temp folder - srcSample = os.path.abspath(os.path.join(tmpConf.appRoot, "sample")) - dstSample = os.path.join(tmpDir, "sample.zip") - tmpConf.assetPath = tmpDir + srcSample = tmpConf._appRoot / "sample" + dstSample = tmpPath / "sample.zip" + monkeypatch.setattr( + "novelwriter.config.Config.getAssetPath", lambda *a: tmpPath / "sample.zip" + ) # Cannot extract when the zip does not exist assert projBuild.buildProject(projData) is False diff --git a/tests/test_dialogs/test_dlg_about.py b/tests/test_dialogs/test_dlg_about.py index 63de9039..b4357051 100644 --- a/tests/test_dialogs/test_dlg_about.py +++ b/tests/test_dialogs/test_dlg_about.py @@ -21,6 +21,8 @@ along with this program. If not, see . import pytest +from pathlib import Path + from tools import getGuiItem from PyQt5.QtWidgets import QAction, QMessageBox @@ -29,7 +31,7 @@ from novelwriter.dialogs.about import GuiAbout @pytest.mark.gui -def testDlgAbout_NWDialog(qtbot, nwGUI): +def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI): """Test the novelWriter about dialogs. """ # NW About @@ -45,13 +47,12 @@ def testDlgAbout_NWDialog(qtbot, nwGUI): assert msgAbout.pageNotes.document().characterCount() > 100 assert msgAbout.pageLicense.document().characterCount() > 100 - msgAbout.mainConf.assetPath = "whatever" - - msgAbout._fillNotesPage() - assert msgAbout.pageNotes.toPlainText() == "Error loading release notes text ..." - - msgAbout._fillLicensePage() - assert msgAbout.pageLicense.toPlainText() == "Error loading licence text ..." + with monkeypatch.context() as mp: + mp.setattr("novelwriter.config.Config.getAssetPath", lambda *a: Path("whatever")) + msgAbout._fillNotesPage() + assert msgAbout.pageNotes.toPlainText() == "Error loading release notes text ..." + msgAbout._fillLicensePage() + assert msgAbout.pageLicense.toPlainText() == "Error loading licence text ..." msgAbout.showReleaseNotes() assert msgAbout.tabBox.currentWidget() == msgAbout.pageNotes diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index cd16ff86..df0a1b15 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -19,10 +19,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ -import os import shutil import pytest +from pathlib import Path from configparser import ConfigParser from mock import causeOSError @@ -38,7 +38,7 @@ from novelwriter.gui.theme import GuiIcons, GuiTheme @pytest.mark.gui -def testGuiTheme_Main(qtbot, nwGUI, fncDir): +def testGuiTheme_Main(qtbot, nwGUI, fncPath): """Test the theme class init. """ mainTheme: GuiTheme = nwGUI.mainTheme @@ -75,15 +75,15 @@ def testGuiTheme_Main(qtbot, nwGUI, fncDir): # Scan for Themes # =============== - assert mainTheme._listConf({}, "not_a_path") is False + assert mainTheme._listConf({}, Path("not_a_path")) is False - themeOne = os.path.join(fncDir, "themes", "themeone.conf") - themeTwo = os.path.join(fncDir, "themes", "themetwo.conf") + themeOne = fncPath / "themes" / "themeone.conf" + themeTwo = fncPath / "themes" / "themetwo.conf" writeFile(themeOne, "# Stuff") writeFile(themeTwo, "# Stuff") result = {} - assert mainTheme._listConf(result, os.path.join(fncDir, "themes")) is True + assert mainTheme._listConf(result, fncPath / "themes") is True assert result["themeone"] == themeOne assert result["themetwo"] == themeTwo @@ -123,7 +123,7 @@ def testGuiTheme_Main(qtbot, nwGUI, fncDir): @pytest.mark.gui -def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncDir): +def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncPath): """Test the theme part of the class. """ mainTheme: GuiTheme = nwGUI.mainTheme @@ -132,15 +132,8 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncDir): # List Themes # =========== - shutil.copy( - os.path.join(mainConf.assetPath, "themes", "default_dark.conf"), - os.path.join(fncDir, "themes") - ) - shutil.copy( - os.path.join(mainConf.assetPath, "themes", "default.conf"), - os.path.join(fncDir, "themes") - ) - writeFile(os.path.join(fncDir, "themes", "default.qss"), "/* Stuff */") + shutil.copy(mainConf.getAssetPath("themes") / "default_dark.conf", fncPath / "themes") + shutil.copy(mainConf.getAssetPath("themes") / "default.conf", fncPath / "themes") # Block the reading of the files with monkeypatch.context() as mp: @@ -197,7 +190,7 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncDir): @pytest.mark.gui -def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncDir): +def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncPath): """Test the syntax part of the class. """ mainTheme: GuiTheme = nwGUI.mainTheme @@ -206,14 +199,8 @@ def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncDir): # List Themes # =========== - shutil.copy( - os.path.join(mainConf.assetPath, "syntax", "default_dark.conf"), - os.path.join(fncDir, "syntax") - ) - shutil.copy( - os.path.join(mainConf.assetPath, "syntax", "default_light.conf"), - os.path.join(fncDir, "syntax") - ) + shutil.copy(mainConf.getAssetPath("syntax") / "default_dark.conf", fncPath / "syntax") + shutil.copy(mainConf.getAssetPath("syntax") / "default_light.conf", fncPath / "syntax") # Block the reading of the files with monkeypatch.context() as mp: @@ -270,11 +257,10 @@ def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncDir): @pytest.mark.gui -def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncDir): +def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncPath): """Test the icon cache class. """ iconCache: GuiIcons = nwGUI.mainTheme.iconCache - mainConf: Config = nwGUI.mainConf # Load Theme # ========== @@ -288,10 +274,11 @@ def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncDir): assert iconCache.loadTheme("typicons_dark") is False # Load a broken theme file - iconsDir = os.path.join(fncDir, "icons") - os.mkdir(iconsDir) - os.mkdir(os.path.join(iconsDir, "testicons")) - writeFile(os.path.join(iconsDir, "testicons", "icons.conf"), ( + iconsDir = fncPath / "icons" + testIcons = iconsDir / "testicons" + iconsDir.mkdir() + testIcons.mkdir() + writeFile(testIcons / "icons.conf", ( "[Main]\n" "name = Test Icons\n" "\n" @@ -300,15 +287,15 @@ def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncDir): "stuff = stuff.svg\n" )) - assetPath = mainConf.assetPath - mainConf.assetPath = fncDir + iconPath = iconCache._iconPath + iconCache._iconPath = fncPath / "icons" caplog.clear() assert iconCache.loadTheme("testicons") is True assert "Unknown icon name 'stuff' in config file" in caplog.text assert "Icon file 'add.svg' not in theme folder" in caplog.text - mainConf.assetPath = assetPath + iconCache._iconPath = iconPath # Load working theme file assert iconCache.loadTheme("typicons_dark") is True @@ -327,7 +314,7 @@ def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncDir): # Fail finding the file with monkeypatch.context() as mp: - mp.setattr("os.path.isfile", lambda *a: False) + mp.setattr("pathlib.Path.is_file", lambda *a: False) qPix = iconCache.loadDecoration("wiz-back") assert qPix.isNull() is True