Change assetPath to a Path object

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