Make further config class updates (#1229)

This commit is contained in:
Veronica Berglyd Olsen
2022-11-10 13:37:09 +01:00
committed by GitHub
19 changed files with 431 additions and 469 deletions
+1 -6
View File
@@ -161,9 +161,6 @@ def main(sysArgs=None):
elif inOpt == "--testmode": elif inOpt == "--testmode":
testMode = True testMode = True
# Set Config Options
CONFIG.cmdOpen = cmdOpen
# Set Logging # Set Logging
cHandle = logging.StreamHandler() cHandle = logging.StreamHandler()
cHandle.setFormatter(logging.Formatter(fmt=logFormat, style="{")) cHandle.setFormatter(logging.Formatter(fmt=logFormat, style="{"))
@@ -256,9 +253,7 @@ def main(sysArgs=None):
# Launch main GUI # Launch main GUI
CONFIG.initLocalisation(nwApp) CONFIG.initLocalisation(nwApp)
nwGUI = GuiMain() nwGUI = GuiMain()
if not nwGUI.hasProject: nwGUI.postLaunchTasks(cmdOpen)
nwGUI.showProjectLoadDialog()
nwGUI.releaseNotes()
sys.exit(nwApp.exec_()) sys.exit(nwApp.exec_())
+16
View File
@@ -124,6 +124,17 @@ def checkUuid(value, default):
return 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 # Validator Functions
# =============================================================================================== # # =============================================================================================== #
@@ -552,6 +563,11 @@ class NWConfigParser(ConfigParser):
logger.error("Could not read '%s':'%s' from config", section, option) logger.error("Could not read '%s':'%s' from config", section, option)
return default 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): def rdStrList(self, section, option, default):
"""Read string list. """Read string list.
""" """
+233 -299
View File
@@ -37,7 +37,7 @@ from PyQt5.QtCore import (
) )
from novelwriter.error import logException, formatException 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 from novelwriter.constants import nwFiles, nwUnicode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -63,7 +63,7 @@ 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._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._appPath = Path(__file__).parent.absolute()
self._appRoot = self._appPath.parent self._appRoot = self._appPath.parent
@@ -73,13 +73,12 @@ class Config:
self._appPath = self._appRoot self._appPath = self._appRoot
# 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.cmdOpen = None # Path from command line for project to be opened on launch
# Localisation Info # Localisation
self._qLocal = QLocale.system() # Note that these paths must be strings
self._qLocale = QLocale.system()
self._qtTrans = {} self._qtTrans = {}
self._qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath) self._qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
self._nwLangPath = str(self._appPath / "assets" / "i18n") self._nwLangPath = str(self._appPath / "assets" / "i18n")
@@ -91,38 +90,35 @@ class Config:
# User Settings # User Settings
# ============= # =============
self._recentProj = RecentProjects(self._dataPath) self._recentProj = RecentProjects(self)
# General GUI Settings # General GUI Settings
self.guiLang = self._qLocal.name() self.guiLocale = self._qLocale.name()
self.guiTheme = "" # GUI theme self.guiTheme = "default" # GUI theme
self.guiSyntax = "" # Syntax theme self.guiSyntax = "default_light" # Syntax theme
self.guiFont = "" # Defaults to system default font self.guiFont = "" # Defaults to system default font in theme class
self.guiFontSize = 11 # Is overridden if system default is loaded self.guiFontSize = 11 # Is overridden if system default is loaded
self.guiScale = 1.0 # Set automatically by Theme class self.guiScale = 1.0 # Set automatically by Theme class
self.lastNotes = "0x0" # The latest release notes that have been shown self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
self.setDefaultGuiTheme() self.lastNotes = "0x0" # The latest release notes that have been shown
self.setDefaultSyntaxTheme() self._lastPath = self._homePath # The user's last used path
# Size Settings # Size Settings
self.winGeometry = [1200, 650] self._mainWinSize = [1200, 650] # Last size of the main GUI window
self.prefGeometry = [700, 615] self._prefsWinSize = [700, 615] # Last size of the Preferences dialog
self.projColWidth = [200, 60, 140] self._projLoadCols = [280, 60, 160] # Last columns withs of the Project Load dialog
self.mainPanePos = [300, 800] self._mainPanePos = [300, 800] # Last position of the main window splitter
self.docPanePos = [400, 400] self._viewPanePos = [500, 150] # Last position of the document viewer splitter
self.viewPanePos = [500, 150] self._outlnPanePos = [500, 150] # Last position of the outline panel splitter
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
# Project Settings # 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
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 # Text Editor Settings
self.textFont = None # Editor font self.textFont = None # Editor font
@@ -174,6 +170,12 @@ class Config:
# Spell Checking Settings # Spell Checking Settings
self.spellLanguage = "en" 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 # Search Bar Switches
self.searchCase = False self.searchCase = False
self.searchWord = False self.searchWord = False
@@ -182,16 +184,6 @@ class Config:
self.searchNextFile = False self.searchNextFile = False
self.searchMatchCap = 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 # System and App Information
# ========================== # ==========================
@@ -258,6 +250,111 @@ class Config:
def recentProjects(self): def recentProjects(self):
return self._recentProj return self._recentProj
@property
def mainWinSize(self):
return [int(x*self.guiScale) for x in self._mainWinSize]
@property
def preferencesWinSize(self):
return [int(x*self.guiScale) for x in self._prefsWinSize]
@property
def projLoadColWidths(self):
return [int(x*self.guiScale) for x in self._projLoadCols]
@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]
##
# 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
##
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._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):
"""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._projLoadCols = [int(x/self.guiScale) for x in colWidths]
return
def setMainPanePos(self, panePos):
"""Set the position of the main GUI splitter."""
self._mainPanePos = [int(x/self.guiScale) for x in panePos]
return
def setViewPanePos(self, panePos):
"""Set the position of the viewer meta data splitter."""
self._viewPanePos = [int(x/self.guiScale) for x in panePos]
return
def setOutlinePanePos(self, panePos):
"""Set the position of the outline details splitter."""
self._outlnPanePos = [int(x/self.guiScale) for x in panePos]
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 = checkPath(lastPath, self._homePath)
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
## ##
# Methods # Methods
## ##
@@ -275,15 +372,13 @@ class Config:
return int(theSize/self.guiScale) return int(theSize/self.guiScale)
def dataPath(self, target=None): def dataPath(self, target=None):
"""Return a path in the data folder. """Return a path in the data folder."""
"""
if isinstance(target, str): if isinstance(target, str):
return self._dataPath / target return self._dataPath / target
return self._dataPath return self._dataPath
def assetPath(self, target=None): def assetPath(self, target=None):
"""Return a path in the assets folder. """Return a path in the assets folder."""
"""
if isinstance(target, str): if isinstance(target, str):
return self._appPath / "assets" / target return self._appPath / "assets" / target
return self._appPath / "assets" return self._appPath / "assets"
@@ -291,13 +386,13 @@ class Config:
def lastPath(self): def lastPath(self):
"""Return the last path used by the user, but ensure it exists. """Return the last path used by the user, but ensure it exists.
""" """
if self._lastPath.is_dir(): if isinstance(self._lastPath, Path):
return self._lastPath if self._lastPath.is_dir():
return Path.home().absolute() return self._lastPath
return self._homePath
def backupPath(self): def backupPath(self):
"""Return the backup path. """Return the backup path."""
"""
if isinstance(self._backupPath, Path): if isinstance(self._backupPath, Path):
if self._backupPath.is_dir(): if self._backupPath.is_dir():
return self._backupPath return self._backupPath
@@ -312,6 +407,33 @@ class Config:
self._errData = [] self._errData = []
return errMessage 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 # Config Actions
## ##
@@ -361,54 +483,26 @@ 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._qLocale = QLocale(self.guiLocale)
QLocale.setDefault(self._qLocal) QLocale.setDefault(self._qLocale)
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, "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._qLocale.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, str(lngPath)): if qTrans.load(lngFile, lngPath):
logger.debug("Loaded: %s/%s", lngPath, lngFile) logger.debug("Loaded: %s.qm", lngFile)
nwApp.installTranslator(qTrans) nwApp.installTranslator(qTrans)
self._qtTrans[lngFile] = qTrans self._qtTrans[lngFile] = qTrans
return 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): def loadConfig(self):
"""Load preferences from file and replace default settings. """Load preferences from file and replace default settings.
""" """
@@ -431,29 +525,31 @@ class Config:
cnfSec = "Main" cnfSec = "Main"
self.guiTheme = theConf.rdStr(cnfSec, "theme", self.guiTheme) self.guiTheme = theConf.rdStr(cnfSec, "theme", self.guiTheme)
self.guiSyntax = theConf.rdStr(cnfSec, "syntax", self.guiSyntax) self.guiSyntax = theConf.rdStr(cnfSec, "syntax", self.guiSyntax)
self.guiFont = theConf.rdStr(cnfSec, "guifont", self.guiFont) self.guiFont = theConf.rdStr(cnfSec, "font", self.guiFont)
self.guiFontSize = theConf.rdInt(cnfSec, "guifontsize", self.guiFontSize) self.guiFontSize = theConf.rdInt(cnfSec, "fontsize", self.guiFontSize)
self.lastNotes = theConf.rdStr(cnfSec, "lastnotes", self.lastNotes) self.guiLocale = theConf.rdStr(cnfSec, "localisation", self.guiLocale)
self.guiLang = theConf.rdStr(cnfSec, "guilang", self.guiLang)
self.hideVScroll = theConf.rdBool(cnfSec, "hidevscroll", self.hideVScroll) self.hideVScroll = theConf.rdBool(cnfSec, "hidevscroll", self.hideVScroll)
self.hideHScroll = theConf.rdBool(cnfSec, "hidehscroll", self.hideHScroll) 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 # Sizes
cnfSec = "Sizes" cnfSec = "Sizes"
self.winGeometry = theConf.rdIntList(cnfSec, "geometry", self.winGeometry) self._mainWinSize = theConf.rdIntList(cnfSec, "mainwindow", self._mainWinSize)
self.prefGeometry = theConf.rdIntList(cnfSec, "preferences", self.prefGeometry) self._prefsWinSize = theConf.rdIntList(cnfSec, "preferences", self._prefsWinSize)
self.projColWidth = theConf.rdIntList(cnfSec, "projcols", self.projColWidth) self._projLoadCols = theConf.rdIntList(cnfSec, "projloadcols", self._projLoadCols)
self.mainPanePos = theConf.rdIntList(cnfSec, "mainpane", self.mainPanePos) 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.viewPanePos = theConf.rdIntList(cnfSec, "viewpane", self.viewPanePos) self._outlnPanePos = theConf.rdIntList(cnfSec, "outlinepane", self._outlnPanePos)
self.outlnPanePos = theConf.rdIntList(cnfSec, "outlinepane", self.outlnPanePos)
self.isFullScreen = theConf.rdBool(cnfSec, "fullscreen", self.isFullScreen)
# Project # Project
cnfSec = "Project" cnfSec = "Project"
self.autoSaveProj = theConf.rdInt(cnfSec, "autosaveproject", self.autoSaveProj) self.autoSaveProj = theConf.rdInt(cnfSec, "autosaveproject", self.autoSaveProj)
self.autoSaveDoc = theConf.rdInt(cnfSec, "autosavedoc", self.autoSaveDoc) self.autoSaveDoc = theConf.rdInt(cnfSec, "autosavedoc", self.autoSaveDoc)
self.emphLabels = theConf.rdBool(cnfSec, "emphlabels", self.emphLabels) 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 # Editor
cnfSec = "Editor" cnfSec = "Editor"
@@ -494,15 +590,9 @@ class Config:
self.stopWhenIdle = theConf.rdBool(cnfSec, "stopwhenidle", self.stopWhenIdle) self.stopWhenIdle = theConf.rdBool(cnfSec, "stopwhenidle", self.stopWhenIdle)
self.userIdleTime = theConf.rdInt(cnfSec, "useridletime", self.userIdleTime) self.userIdleTime = theConf.rdInt(cnfSec, "useridletime", self.userIdleTime)
# Backup
cnfSec = "Backup"
backupPath = theConf.rdStr(cnfSec, "backuppath", None)
self.backupOnClose = theConf.rdBool(cnfSec, "backuponclose", self.backupOnClose)
self.askBeforeBackup = theConf.rdBool(cnfSec, "askbeforebackup", self.askBeforeBackup)
self.setBackupPath(backupPath)
# State # State
cnfSec = "State" cnfSec = "State"
self.isFullScreen = theConf.rdBool(cnfSec, "fullscreen", self.isFullScreen)
self.showRefPanel = theConf.rdBool(cnfSec, "showrefpanel", self.showRefPanel) self.showRefPanel = theConf.rdBool(cnfSec, "showrefpanel", self.showRefPanel)
self.viewComments = theConf.rdBool(cnfSec, "viewcomments", self.viewComments) self.viewComments = theConf.rdBool(cnfSec, "viewcomments", self.viewComments)
self.viewSynopsis = theConf.rdBool(cnfSec, "viewsynopsis", self.viewSynopsis) self.viewSynopsis = theConf.rdBool(cnfSec, "viewsynopsis", self.viewSynopsis)
@@ -513,9 +603,14 @@ class Config:
self.searchNextFile = theConf.rdBool(cnfSec, "searchnextfile", self.searchNextFile) self.searchNextFile = theConf.rdBool(cnfSec, "searchnextfile", self.searchNextFile)
self.searchMatchCap = theConf.rdBool(cnfSec, "searchmatchcap", self.searchMatchCap) self.searchMatchCap = theConf.rdBool(cnfSec, "searchmatchcap", self.searchMatchCap)
# Path # Deprecated Settings or Locations as of 2.0
cnfSec = "Path" # These will be loaded for a few minor releases until the users have converted them
self._lastPath = Path(theConf.rdStr(cnfSec, "lastpath", self._lastPath)) 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 # Check Certain Values for None
self.spellLanguage = self._checkNone(self.spellLanguage) self.spellLanguage = self._checkNone(self.spellLanguage)
@@ -538,33 +633,38 @@ class Config:
theConf = NWConfigParser() theConf = NWConfigParser()
theConf["Meta"] = {
"timestamp": formatTimeStamp(time()),
}
theConf["Main"] = { theConf["Main"] = {
"timestamp": formatTimeStamp(time()), "theme": str(self.guiTheme),
"theme": str(self.guiTheme), "syntax": str(self.guiSyntax),
"syntax": str(self.guiSyntax), "font": str(self.guiFont),
"guifont": str(self.guiFont), "fontsize": str(self.guiFontSize),
"guifontsize": str(self.guiFontSize), "localisation": str(self.guiLocale),
"lastnotes": str(self.lastNotes), "hidevscroll": str(self.hideVScroll),
"guilang": str(self.guiLang), "hidehscroll": str(self.hideHScroll),
"hidevscroll": str(self.hideVScroll), "lastnotes": str(self.lastNotes),
"hidehscroll": str(self.hideHScroll), "lastpath": str(self._lastPath),
} }
theConf["Sizes"] = { theConf["Sizes"] = {
"geometry": self._packList(self.winGeometry), "mainwindow": self._packList(self._mainWinSize),
"preferences": self._packList(self.prefGeometry), "preferences": self._packList(self._prefsWinSize),
"projcols": self._packList(self.projColWidth), "projloadcols": self._packList(self._projLoadCols),
"mainpane": self._packList(self.mainPanePos), "mainpane": self._packList(self._mainPanePos),
"docpane": self._packList(self.docPanePos), "viewpane": self._packList(self._viewPanePos),
"viewpane": self._packList(self.viewPanePos), "outlinepane": self._packList(self._outlnPanePos),
"outlinepane": self._packList(self.outlnPanePos),
"fullscreen": str(self.isFullScreen),
} }
theConf["Project"] = { theConf["Project"] = {
"autosaveproject": str(self.autoSaveProj), "autosaveproject": str(self.autoSaveProj),
"autosavedoc": str(self.autoSaveDoc), "autosavedoc": str(self.autoSaveDoc),
"emphlabels": str(self.emphLabels), "emphlabels": str(self.emphLabels),
"backuppath": str(self._backupPath or ""),
"backuponclose": str(self.backupOnClose),
"askbeforebackup": str(self.askBeforeBackup),
} }
theConf["Editor"] = { theConf["Editor"] = {
@@ -606,13 +706,8 @@ class Config:
"useridletime": str(self.userIdleTime), "useridletime": str(self.userIdleTime),
} }
theConf["Backup"] = {
"backuppath": str(self._backupPath or ""),
"backuponclose": str(self.backupOnClose),
"askbeforebackup": str(self.askBeforeBackup),
}
theConf["State"] = { theConf["State"] = {
"fullscreen": str(self.isFullScreen),
"showrefpanel": str(self.showRefPanel), "showrefpanel": str(self.showRefPanel),
"viewcomments": str(self.viewComments), "viewcomments": str(self.viewComments),
"viewsynopsis": str(self.viewSynopsis), "viewsynopsis": str(self.viewSynopsis),
@@ -624,16 +719,11 @@ class Config:
"searchmatchcap": str(self.searchMatchCap), "searchmatchcap": str(self.searchMatchCap),
} }
theConf["Path"] = {
"lastpath": str(self._lastPath),
}
# Write config file # Write config file
cnfPath = self._confPath / nwFiles.CONF_FILE cnfPath = self._confPath / nwFiles.CONF_FILE
try: try:
with open(cnfPath, mode="w", encoding="utf-8") as outFile: with open(cnfPath, mode="w", encoding="utf-8") as outFile:
theConf.write(outFile) theConf.write(outFile)
self.confChanged = False
except Exception as exc: except Exception as exc:
logger.error("Could not save config file") logger.error("Could not save config file")
logException() logException()
@@ -644,162 +734,6 @@ class Config:
return True return True
##
# 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
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 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))
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 # Internal Functions
## ##
@@ -839,8 +773,8 @@ class Config:
class RecentProjects: class RecentProjects:
def __init__(self, dataPath): def __init__(self, mainConf):
self._dataPath = dataPath self.mainConf = mainConf
self._data = {} self._data = {}
return return
@@ -849,7 +783,7 @@ class RecentProjects:
""" """
self._data = {} self._data = {}
cacheFile = self._dataPath / nwFiles.RECENT_FILE cacheFile = self.mainConf.dataPath(nwFiles.RECENT_FILE)
if not cacheFile.is_file(): if not cacheFile.is_file():
return True return True
@@ -872,7 +806,7 @@ class RecentProjects:
def saveCache(self): def saveCache(self):
"""Save the cache dictionary of recent projects. """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") cacheTemp = cacheFile.with_suffix(".tmp")
try: try:
with open(cacheTemp, mode="w+", encoding="utf-8") as outFile: with open(cacheTemp, mode="w+", encoding="utf-8") as outFile:
+12 -26
View File
@@ -74,7 +74,7 @@ class GuiPreferences(PagedDialog):
self.buttonBox.rejected.connect(self._doClose) self.buttonBox.rejected.connect(self._doClose)
self.addControls(self.buttonBox) self.addControls(self.buttonBox)
self.resize(*self.mainConf.getPreferencesSize()) self.resize(*self.mainConf.preferencesWinSize)
# Settings # Settings
self._updateTheme = False self._updateTheme = False
@@ -125,6 +125,7 @@ class GuiPreferences(PagedDialog):
self.tabQuote.saveValues() self.tabQuote.saveValues()
self._saveWindowSize() self._saveWindowSize()
self.mainConf.saveConfig()
self.accept() self.accept()
return return
@@ -143,7 +144,7 @@ class GuiPreferences(PagedDialog):
def _saveWindowSize(self): def _saveWindowSize(self):
"""Save the dialog window size. """Save the dialog window size.
""" """
self.mainConf.setPreferencesSize(self.width(), self.height()) self.mainConf.setPreferencesWinSize(self.width(), self.height())
return return
# END Class GuiPreferences # END Class GuiPreferences
@@ -170,18 +171,18 @@ class GuiPreferencesGeneral(QWidget):
minWidth = self.mainConf.pxInt(200) minWidth = self.mainConf.pxInt(200)
# Select Locale # Select Locale
self.guiLang = QComboBox() self.guiLocale = QComboBox()
self.guiLang.setMinimumWidth(minWidth) self.guiLocale.setMinimumWidth(minWidth)
theLangs = self.mainConf.listLanguages(self.mainConf.LANG_NW) theLangs = self.mainConf.listLanguages(self.mainConf.LANG_NW)
for lang, langName in theLangs: for lang, langName in theLangs:
self.guiLang.addItem(langName, lang) self.guiLocale.addItem(langName, lang)
langIdx = self.guiLang.findData(self.mainConf.guiLang) langIdx = self.guiLocale.findData(self.mainConf.guiLocale)
if langIdx != -1: if langIdx != -1:
self.guiLang.setCurrentIndex(langIdx) self.guiLocale.setCurrentIndex(langIdx)
self.mainForm.addRow( self.mainForm.addRow(
self.tr("Main GUI language"), self.tr("Main GUI language"),
self.guiLang, self.guiLocale,
self.tr("Requires restart to take effect.") self.tr("Requires restart to take effect.")
) )
@@ -286,7 +287,7 @@ class GuiPreferencesGeneral(QWidget):
def saveValues(self): def saveValues(self):
"""Save the values set for this tab. """Save the values set for this tab.
""" """
guiLang = self.guiLang.currentData() guiLocale = self.guiLocale.currentData()
guiTheme = self.guiTheme.currentData() guiTheme = self.guiTheme.currentData()
guiSyntax = self.guiSyntax.currentData() guiSyntax = self.guiSyntax.currentData()
guiFont = self.guiFont.text() guiFont = self.guiFont.text()
@@ -296,12 +297,12 @@ class GuiPreferencesGeneral(QWidget):
# Update Flags # Update Flags
self.prefsGui._updateTheme |= self.mainConf.guiTheme != guiTheme self.prefsGui._updateTheme |= self.mainConf.guiTheme != guiTheme
self.prefsGui._updateSyntax |= self.mainConf.guiSyntax != guiSyntax 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.guiFont != guiFont
self.prefsGui._needsRestart |= self.mainConf.guiFontSize != guiFontSize self.prefsGui._needsRestart |= self.mainConf.guiFontSize != guiFontSize
self.prefsGui._refreshTree |= self.mainConf.emphLabels != emphLabels self.prefsGui._refreshTree |= self.mainConf.emphLabels != emphLabels
self.mainConf.guiLang = guiLang self.mainConf.guiLocale = guiLocale
self.mainConf.guiTheme = guiTheme self.mainConf.guiTheme = guiTheme
self.mainConf.guiSyntax = guiSyntax self.mainConf.guiSyntax = guiSyntax
self.mainConf.guiFont = guiFont self.mainConf.guiFont = guiFont
@@ -311,8 +312,6 @@ class GuiPreferencesGeneral(QWidget):
self.mainConf.hideVScroll = self.hideVScroll.isChecked() self.mainConf.hideVScroll = self.hideVScroll.isChecked()
self.mainConf.hideHScroll = self.hideHScroll.isChecked() self.mainConf.hideHScroll = self.hideHScroll.isChecked()
self.mainConf.confChanged = True
return return
## ##
@@ -458,8 +457,6 @@ class GuiPreferencesProjects(QWidget):
self.mainConf.stopWhenIdle = self.stopWhenIdle.isChecked() self.mainConf.stopWhenIdle = self.stopWhenIdle.isChecked()
self.mainConf.userIdleTime = round(self.userIdleTime.value() * 60) self.mainConf.userIdleTime = round(self.userIdleTime.value() * 60)
self.mainConf.confChanged = True
return return
## ##
@@ -629,8 +626,6 @@ class GuiPreferencesDocuments(QWidget):
self.mainConf.textMargin = self.textMargin.value() self.mainConf.textMargin = self.textMargin.value()
self.mainConf.tabWidth = self.tabWidth.value() self.mainConf.tabWidth = self.tabWidth.value()
self.mainConf.confChanged = True
return return
## ##
@@ -820,8 +815,6 @@ class GuiPreferencesEditor(QWidget):
self.mainConf.autoScroll = self.autoScroll.isChecked() self.mainConf.autoScroll = self.autoScroll.isChecked()
self.mainConf.autoScrollPos = self.autoScrollPos.value() self.mainConf.autoScrollPos = self.autoScrollPos.value()
self.mainConf.confChanged = True
return return
# END Class GuiPreferencesEditor # END Class GuiPreferencesEditor
@@ -911,8 +904,6 @@ class GuiPreferencesSyntax(QWidget):
# Text Errors # Text Errors
self.mainConf.showMultiSpaces = self.showMultiSpaces.isChecked() self.mainConf.showMultiSpaces = self.showMultiSpaces.isChecked()
self.mainConf.confChanged = True
return return
## ##
@@ -1065,8 +1056,6 @@ class GuiPreferencesAutomation(QWidget):
self.mainConf.fmtPadAfter = self.fmtPadAfter.text().strip() self.mainConf.fmtPadAfter = self.fmtPadAfter.text().strip()
self.mainConf.fmtPadThin = self.fmtPadThin.isChecked() self.mainConf.fmtPadThin = self.fmtPadThin.isChecked()
self.mainConf.confChanged = True
return return
## ##
@@ -1185,9 +1174,6 @@ class GuiPreferencesQuotes(QWidget):
self.mainConf.fmtSingleQuotes[1] = self.quoteSym["SC"].text() self.mainConf.fmtSingleQuotes[1] = self.quoteSym["SC"].text()
self.mainConf.fmtDoubleQuotes[0] = self.quoteSym["DO"].text() self.mainConf.fmtDoubleQuotes[0] = self.quoteSym["DO"].text()
self.mainConf.fmtDoubleQuotes[1] = self.quoteSym["DC"].text() self.mainConf.fmtDoubleQuotes[1] = self.quoteSym["DC"].text()
self.mainConf.confChanged = True
return return
## ##
+2 -2
View File
@@ -258,7 +258,7 @@ class GuiProjectLoad(QDialog):
colWidths[self.C_NAME] = self.listBox.columnWidth(self.C_NAME) colWidths[self.C_NAME] = self.listBox.columnWidth(self.C_NAME)
colWidths[self.C_COUNT] = self.listBox.columnWidth(self.C_COUNT) colWidths[self.C_COUNT] = self.listBox.columnWidth(self.C_COUNT)
colWidths[self.C_TIME] = self.listBox.columnWidth(self.C_TIME) colWidths[self.C_TIME] = self.listBox.columnWidth(self.C_TIME)
self.mainConf.setProjColWidths(colWidths) self.mainConf.setProjLoadColWidths(colWidths)
return return
def _populateList(self): def _populateList(self):
@@ -284,7 +284,7 @@ class GuiProjectLoad(QDialog):
if self.listBox.topLevelItemCount() > 0: if self.listBox.topLevelItemCount() > 0:
self.listBox.topLevelItem(0).setSelected(True) self.listBox.topLevelItem(0).setSelected(True)
projColWidth = self.mainConf.getProjColWidths() projColWidth = self.mainConf.projLoadColWidths
if len(projColWidth) == 3: if len(projColWidth) == 3:
self.listBox.setColumnWidth(self.C_NAME, projColWidth[self.C_NAME]) self.listBox.setColumnWidth(self.C_NAME, projColWidth[self.C_NAME])
self.listBox.setColumnWidth(self.C_COUNT, projColWidth[self.C_COUNT]) self.listBox.setColumnWidth(self.C_COUNT, projColWidth[self.C_COUNT])
+2 -2
View File
@@ -1148,7 +1148,7 @@ class GuiDocViewFooter(QWidget):
def _doToggleComments(self, theState): def _doToggleComments(self, theState):
"""Toggle the view comment button and reload the document. """Toggle the view comment button and reload the document.
""" """
self.mainConf.setViewComments(theState) self.mainConf.viewComments = theState
self.docViewer.reloadText() self.docViewer.reloadText()
return return
@@ -1156,7 +1156,7 @@ class GuiDocViewFooter(QWidget):
def _doToggleSynopsis(self, theState): def _doToggleSynopsis(self, theState):
"""Toggle the view synopsis button and reload the document. """Toggle the view synopsis button and reload the document.
""" """
self.mainConf.setViewSynopsis(theState) self.mainConf.viewSynopsis = theState
self.docViewer.reloadText() self.docViewer.reloadText()
return return
+1 -1
View File
@@ -71,7 +71,7 @@ class GuiOutlineView(QWidget):
self.splitOutline = QSplitter(Qt.Vertical) self.splitOutline = QSplitter(Qt.Vertical)
self.splitOutline.addWidget(self.outlineTree) self.splitOutline.addWidget(self.outlineTree)
self.splitOutline.addWidget(self.outlineData) self.splitOutline.addWidget(self.outlineData)
self.splitOutline.setSizes(self.mainConf.getOutlinePanePos()) self.splitOutline.setSizes(self.mainConf.outlinePanePos)
# Assemble # Assemble
self.outerBox = QVBoxLayout() self.outerBox = QVBoxLayout()
+12 -2
View File
@@ -185,9 +185,14 @@ class GuiTheme:
"""Load the currently specified GUI theme. """Load the currently specified GUI theme.
""" """
guiTheme = self.mainConf.guiTheme 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) themeFile = self._availThemes.get(guiTheme, None)
if themeFile is None: if themeFile is None:
logger.error("Could not find GUI theme '%s'", guiTheme) logger.error("Could not load GUI theme")
return False return False
# Config File # Config File
@@ -266,9 +271,14 @@ class GuiTheme:
"""Load the currently specified syntax highlighter theme. """Load the currently specified syntax highlighter theme.
""" """
guiSyntax = self.mainConf.guiSyntax 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) syntaxFile = self._availSyntax.get(guiSyntax, None)
if syntaxFile is None: if syntaxFile is None:
logger.error("Could not find syntax theme '%s'", guiSyntax) logger.error("Could not load syntax theme")
return False return False
logger.info("Loading syntax theme '%s'", guiSyntax) logger.info("Loading syntax theme '%s'", guiSyntax)
+18 -15
View File
@@ -79,7 +79,7 @@ class GuiMain(QMainWindow):
logger.info("Qt5: %s (%d)", self.mainConf.verQtString, self.mainConf.verQtValue) logger.info("Qt5: %s (%d)", self.mainConf.verQtString, self.mainConf.verQtValue)
logger.info("PyQt5: %s (%d)", self.mainConf.verPyQtString, self.mainConf.verPyQtValue) 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("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 # Core Classes
# ============ # ============
@@ -93,7 +93,7 @@ class GuiMain(QMainWindow):
self.idleTime = 0.0 self.idleTime = 0.0
# Prepare Main Window # Prepare Main Window
self.resize(*self.mainConf.getWinSize()) self.resize(*self.mainConf.mainWinSize)
self._updateWindowTitle() self._updateWindowTitle()
nwIcon = self.mainConf.assetPath("icons") / "novelwriter.svg" nwIcon = self.mainConf.assetPath("icons") / "novelwriter.svg"
@@ -140,7 +140,7 @@ class GuiMain(QMainWindow):
self.splitView.addWidget(self.docViewer) self.splitView.addWidget(self.docViewer)
self.splitView.addWidget(self.viewMeta) self.splitView.addWidget(self.viewMeta)
self.splitView.setHandleWidth(hWd) self.splitView.setHandleWidth(hWd)
self.splitView.setSizes(self.mainConf.getViewPanePos()) self.splitView.setSizes(self.mainConf.viewPanePos)
# Splitter : Document Editor / Document Viewer # Splitter : Document Editor / Document Viewer
self.splitDocs = QSplitter(Qt.Horizontal) self.splitDocs = QSplitter(Qt.Horizontal)
@@ -154,7 +154,7 @@ class GuiMain(QMainWindow):
self.splitMain.addWidget(self.treePane) self.splitMain.addWidget(self.treePane)
self.splitMain.addWidget(self.splitDocs) self.splitMain.addWidget(self.splitDocs)
self.splitMain.setHandleWidth(hWd) self.splitMain.setHandleWidth(hWd)
self.splitMain.setSizes(self.mainConf.getMainPanePos()) self.splitMain.setSizes(self.mainConf.mainPanePos)
# Main Stack : Editor / Outline # Main Stack : Editor / Outline
self.mainStack = QStackedWidget() self.mainStack = QStackedWidget()
@@ -290,11 +290,6 @@ class GuiMain(QMainWindow):
"and make sure you take regular backups." "and make sure you take regular backups."
), nwAlert.WARN) ), 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 ...") logger.info("novelWriter is ready ...")
self.setStatus(self.tr("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)) self.asDocTimer.setInterval(int(self.mainConf.autoSaveDoc*1000))
return True return True
def releaseNotes(self): def postLaunchTasks(self, cmdOpen):
"""Determine whether release notes need to be shown, and show """This function is called after the main window is created to
them by calling the About dialog. 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__): if hexToInt(self.mainConf.lastNotes) < hexToInt(novelwriter.__hexversion__):
self.mainConf.lastNotes = novelwriter.__hexversion__ self.mainConf.lastNotes = novelwriter.__hexversion__
self.showAboutNWDialog(showNotes=True) self.showAboutNWDialog(showNotes=True)
return return
## ##
@@ -1169,14 +1173,13 @@ class GuiMain(QMainWindow):
if not self.isFocusMode: if not self.isFocusMode:
self.mainConf.setMainPanePos(self.splitMain.sizes()) self.mainConf.setMainPanePos(self.splitMain.sizes())
self.mainConf.setDocPanePos(self.splitDocs.sizes())
self.mainConf.setOutlinePanePos(self.outlineView.splitSizes()) self.mainConf.setOutlinePanePos(self.outlineView.splitSizes())
if self.viewMeta.isVisible(): if self.viewMeta.isVisible():
self.mainConf.setViewPanePos(self.splitView.sizes()) self.mainConf.setViewPanePos(self.splitView.sizes())
self.mainConf.setShowRefPanel(self.viewMeta.isVisible()) self.mainConf.showRefPanel = self.viewMeta.isVisible()
if not self.mainConf.isFullScreen: if not self.mainConf.isFullScreen:
self.mainConf.setWinSize(self.width(), self.height()) self.mainConf.setMainWinSize(self.width(), self.height())
if self.hasProject: if self.hasProject:
self.closeProject(True) self.closeProject(True)
+2 -2
View File
@@ -113,7 +113,7 @@ def tmpConf(tmpPath):
theConf = Config() theConf = Config()
theConf.initConfig(tmpPath, tmpPath) theConf.initConfig(tmpPath, tmpPath)
theConf.setLastPath(tmpPath) theConf.setLastPath(tmpPath)
theConf.guiLang = "en_GB" theConf.guiLocale = "en_GB"
return theConf return theConf
@@ -127,7 +127,7 @@ def fncConf(fncPath):
theConf = Config() theConf = Config()
theConf.initConfig(fncPath, fncPath) theConf.initConfig(fncPath, fncPath)
theConf.setLastPath(fncPath) theConf.setLastPath(fncPath)
theConf.guiLang = "en_GB" theConf.guiLocale = "en_GB"
return theConf return theConf
+3 -1
View File
@@ -35,6 +35,7 @@ class MockGuiMain(QObject):
self.hasProject = True self.hasProject = True
self.theProject = None self.theProject = None
self.mainStatus = MockStatusBar() self.mainStatus = MockStatusBar()
self.projPath = ""
# Test Variables # Test Variables
self.askResponse = True self.askResponse = True
@@ -43,7 +44,7 @@ class MockGuiMain(QObject):
return return
def releaseNotes(self): def postLaunchTasks(self, cmdOpen):
return return
def makeAlert(self, message, level=0, exception=None): def makeAlert(self, message, level=0, exception=None):
@@ -61,6 +62,7 @@ class MockGuiMain(QObject):
return return
def openProject(self, projPath): def openProject(self, projPath):
self.projPath = projPath
return return
def rebuildIndex(self): def rebuildIndex(self):
+14 -17
View File
@@ -1,28 +1,32 @@
[Meta]
timestamp = 2022-11-10 11:10:10
[Main] [Main]
timestamp = 2022-10-26 11:19:49
theme = default theme = default
syntax = default_light syntax = default_light
guifont = font =
guifontsize = 11 fontsize = 11
lastnotes = 0x0 localisation = en_GB
guilang = en_GB
hidevscroll = False hidevscroll = False
hidehscroll = False hidehscroll = False
lastnotes = 0x0
lastpath = /home/vkbo
[Sizes] [Sizes]
geometry = 1200, 650 mainwindow = 1200, 650
preferences = 700, 615 preferences = 700, 615
projcols = 200, 60, 140 projloadcols = 280, 60, 160
mainpane = 300, 800 mainpane = 300, 800
docpane = 400, 400
viewpane = 500, 150 viewpane = 500, 150
outlinepane = 500, 150 outlinepane = 500, 150
fullscreen = False
[Project] [Project]
autosaveproject = 60 autosaveproject = 60
autosavedoc = 30 autosavedoc = 30
emphlabels = True emphlabels = True
backuppath =
backuponclose = False
askbeforebackup = True
[Editor] [Editor]
textfont = None textfont = None
@@ -62,12 +66,8 @@ highlightemph = True
stopwhenidle = True stopwhenidle = True
useridletime = 300 useridletime = 300
[Backup]
backuppath =
backuponclose = False
askbeforebackup = True
[State] [State]
fullscreen = False
showrefpanel = True showrefpanel = True
viewcomments = True viewcomments = True
viewsynopsis = True viewsynopsis = True
@@ -78,6 +78,3 @@ searchloop = False
searchnextfile = False searchnextfile = False
searchmatchcap = False searchmatchcap = False
[Path]
lastpath =
+14 -17
View File
@@ -1,28 +1,32 @@
[Meta]
timestamp = 2022-11-10 11:10:13
[Main] [Main]
timestamp = 2022-10-26 11:19:51
theme = default theme = default
syntax = default_light syntax = default_light
guifont = Cantarell font = Cantarell
guifontsize = 12 fontsize = 12
lastnotes = 0x0 localisation = en_GB
guilang = en_GB
hidevscroll = True hidevscroll = True
hidehscroll = True hidehscroll = True
lastnotes = 0x0
lastpath = /home/vkbo/Code/novelWriter/Source/tests/temp/function
[Sizes] [Sizes]
geometry = 1200, 650 mainwindow = 1200, 650
preferences = 699, 614 preferences = 699, 614
projcols = 200, 60, 140 projloadcols = 280, 60, 160
mainpane = 300, 800 mainpane = 300, 800
docpane = 400, 400
viewpane = 500, 150 viewpane = 500, 150
outlinepane = 500, 150 outlinepane = 500, 150
fullscreen = False
[Project] [Project]
autosaveproject = 40 autosaveproject = 40
autosavedoc = 20 autosavedoc = 20
emphlabels = True emphlabels = True
backuppath = some/dir
backuponclose = True
askbeforebackup = True
[Editor] [Editor]
textfont = None textfont = None
@@ -62,12 +66,8 @@ highlightemph = False
stopwhenidle = True stopwhenidle = True
useridletime = 300 useridletime = 300
[Backup]
backuppath = some/dir
backuponclose = True
askbeforebackup = True
[State] [State]
fullscreen = False
showrefpanel = True showrefpanel = True
viewcomments = True viewcomments = True
viewsynopsis = True viewsynopsis = True
@@ -78,6 +78,3 @@ searchloop = False
searchnextfile = False searchnextfile = False
searchmatchcap = False searchmatchcap = False
[Path]
lastpath =
+18 -3
View File
@@ -23,15 +23,17 @@ import time
import pytest import pytest
import hashlib import hashlib
from pathlib import Path
from mock import causeOSError from mock import causeOSError
from tools import writeFile from tools import writeFile
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
from novelwriter.common import ( from novelwriter.common import (
checkStringNone, checkString, checkInt, checkFloat, checkBool, checkHandle, checkStringNone, checkString, checkInt, checkFloat, checkBool, checkHandle,
checkUuid, isHandle, isTitleTag, isItemClass, isItemType, isItemLayout, checkUuid, checkPath, isHandle, isTitleTag, isItemClass, isItemType,
hexToInt, minmax, checkIntTuple, formatInt, formatTimeStamp, formatTime, isItemLayout, hexToInt, minmax, checkIntTuple, formatInt, formatTimeStamp,
simplified, yesNo, splitVersionNumber, transferCase, fuzzyTime, formatTime, simplified, yesNo, splitVersionNumber, transferCase, fuzzyTime,
numberToRoman, jsonEncode, readTextFile, makeFileNameSafe, sha256sum, numberToRoman, jsonEncode, readTextFile, makeFileNameSafe, sha256sum,
getGuiItem, NWConfigParser getGuiItem, NWConfigParser
) )
@@ -175,6 +177,19 @@ def testBaseCommon_CheckUuid():
# END Test 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 @pytest.mark.base
def testBaseCommon_IsHandle(): def testBaseCommon_IsHandle():
"""Test the isHandle function. """Test the isHandle function.
+37 -68
View File
@@ -112,7 +112,7 @@ def testBaseConfig_InitLoadSave(monkeypatch, fncPath, tstPaths):
# Check that we have a default file # Check that we have a default file
copyfile(confFile, testFile) copyfile(confFile, testFile)
ignore = ("timestamp", "lastnotes", "guilang", "lastpath") ignore = ("timestamp", "lastnotes", "localisation", "lastpath")
assert cmpFiles(testFile, compFile, ignoreStart=ignore) assert cmpFiles(testFile, compFile, ignoreStart=ignore)
tstConf.errorText() # This clears the error cache tstConf.errorText() # This clears the error cache
@@ -166,7 +166,7 @@ def testBaseConfig_Localisation(fncPath, tstPaths):
i18nDir = fncPath / "i18n" i18nDir = fncPath / "i18n"
i18nDir.mkdir() i18nDir.mkdir()
tstConf._nwLangPath = i18nDir tstConf._nwLangPath = str(i18nDir)
copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_en_GB.qm") copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_en_GB.qm")
writeFile(i18nDir / "nw_en_GB.ts", "") writeFile(i18nDir / "nw_en_GB.ts", "")
@@ -253,96 +253,83 @@ def testBaseConfig_SettersGetters(tmpConf):
# Window Size # Window Size
tmpConf.guiScale = 1.0 tmpConf.guiScale = 1.0
tmpConf.setWinSize(1205, 655) tmpConf.setMainWinSize(1205, 655)
assert tmpConf.confChanged is False assert tmpConf.mainWinSize == [1200, 650]
tmpConf.guiScale = 2.0 tmpConf.guiScale = 2.0
tmpConf.setWinSize(70, 70) tmpConf.setMainWinSize(70, 70)
assert tmpConf.getWinSize() == [70, 70] assert tmpConf.mainWinSize == [70, 70]
assert tmpConf.winGeometry == [35, 35] assert tmpConf._mainWinSize == [35, 35]
tmpConf.guiScale = 1.0 tmpConf.guiScale = 1.0
tmpConf.setWinSize(70, 70) tmpConf.setMainWinSize(70, 70)
assert tmpConf.getWinSize() == [70, 70] assert tmpConf.mainWinSize == [70, 70]
assert tmpConf.winGeometry == [70, 70] assert tmpConf._mainWinSize == [70, 70]
tmpConf.setWinSize(1200, 650) tmpConf.setMainWinSize(1200, 650)
# Preferences Size # Preferences Size
tmpConf.guiScale = 2.0 tmpConf.guiScale = 2.0
tmpConf.setPreferencesSize(70, 70) tmpConf.setPreferencesWinSize(70, 70)
assert tmpConf.getPreferencesSize() == [70, 70] assert tmpConf.preferencesWinSize == [70, 70]
assert tmpConf.prefGeometry == [35, 35] assert tmpConf._prefsWinSize == [35, 35]
tmpConf.guiScale = 1.0 tmpConf.guiScale = 1.0
tmpConf.setPreferencesSize(70, 70) tmpConf.setPreferencesWinSize(70, 70)
assert tmpConf.getPreferencesSize() == [70, 70] assert tmpConf.preferencesWinSize == [70, 70]
assert tmpConf.prefGeometry == [70, 70] assert tmpConf._prefsWinSize == [70, 70]
tmpConf.setPreferencesSize(700, 615) tmpConf.setPreferencesWinSize(700, 615)
# Project Settings Tree Columns # Project Settings Tree Columns
tmpConf.guiScale = 2.0 tmpConf.guiScale = 2.0
tmpConf.setProjColWidths([10, 20, 30]) tmpConf.setProjLoadColWidths([10, 20, 30])
assert tmpConf.getProjColWidths() == [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.guiScale = 1.0
tmpConf.setProjColWidths([10, 20, 30]) tmpConf.setProjLoadColWidths([10, 20, 30])
assert tmpConf.getProjColWidths() == [10, 20, 30] assert tmpConf.projLoadColWidths == [10, 20, 30]
assert tmpConf.projColWidth == [10, 20, 30] assert tmpConf._projLoadCols == [10, 20, 30]
tmpConf.setProjColWidths([200, 60, 140]) tmpConf.setProjLoadColWidths([200, 60, 140])
# Main Pane Splitter # Main Pane Splitter
tmpConf.guiScale = 2.0 tmpConf.guiScale = 2.0
tmpConf.setMainPanePos([200, 700]) tmpConf.setMainPanePos([200, 700])
assert tmpConf.getMainPanePos() == [200, 700] assert tmpConf.mainPanePos == [200, 700]
assert tmpConf.mainPanePos == [100, 350] assert tmpConf._mainPanePos == [100, 350]
tmpConf.guiScale = 1.0 tmpConf.guiScale = 1.0
tmpConf.setMainPanePos([200, 700]) tmpConf.setMainPanePos([200, 700])
assert tmpConf.getMainPanePos() == [200, 700]
assert tmpConf.mainPanePos == [200, 700] assert tmpConf.mainPanePos == [200, 700]
assert tmpConf._mainPanePos == [200, 700]
tmpConf.setMainPanePos([300, 800]) 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 # View Pane Splitter
tmpConf.guiScale = 2.0 tmpConf.guiScale = 2.0
tmpConf.setViewPanePos([400, 250]) tmpConf.setViewPanePos([400, 250])
assert tmpConf.getViewPanePos() == [400, 250] assert tmpConf.viewPanePos == [400, 250]
assert tmpConf.viewPanePos == [200, 125] assert tmpConf._viewPanePos == [200, 125]
tmpConf.guiScale = 1.0 tmpConf.guiScale = 1.0
tmpConf.setViewPanePos([400, 250]) tmpConf.setViewPanePos([400, 250])
assert tmpConf.getViewPanePos() == [400, 250]
assert tmpConf.viewPanePos == [400, 250] assert tmpConf.viewPanePos == [400, 250]
assert tmpConf._viewPanePos == [400, 250]
tmpConf.setViewPanePos([500, 150]) tmpConf.setViewPanePos([500, 150])
# Outline Pane Splitter # Outline Pane Splitter
tmpConf.guiScale = 2.0 tmpConf.guiScale = 2.0
tmpConf.setOutlinePanePos([400, 250]) tmpConf.setOutlinePanePos([400, 250])
assert tmpConf.getOutlinePanePos() == [400, 250] assert tmpConf.outlinePanePos == [400, 250]
assert tmpConf.outlnPanePos == [200, 125] assert tmpConf._outlnPanePos == [200, 125]
tmpConf.guiScale = 1.0 tmpConf.guiScale = 1.0
tmpConf.setOutlinePanePos([400, 250]) tmpConf.setOutlinePanePos([400, 250])
assert tmpConf.getOutlinePanePos() == [400, 250] assert tmpConf.outlinePanePos == [400, 250]
assert tmpConf.outlnPanePos == [400, 250] assert tmpConf._outlnPanePos == [400, 250]
tmpConf.setOutlinePanePos([500, 150]) tmpConf.setOutlinePanePos([500, 150])
@@ -361,24 +348,6 @@ def testBaseConfig_SettersGetters(tmpConf):
assert tmpConf.getTextMargin() == 80 assert tmpConf.getTextMargin() == 80
assert tmpConf.getTabWidth() == 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 # END Test testBaseConfig_SettersGetters
@@ -411,11 +380,11 @@ def testBaseConfig_Internal(monkeypatch, tmpConf):
@pytest.mark.base @pytest.mark.base
def testBaseConfig_RecentCache(monkeypatch, fncPath): def testBaseConfig_RecentCache(monkeypatch, fncConf, fncPath):
"""Test recent cache file. """Test recent cache file.
""" """
cacheFile = fncPath / nwFiles.RECENT_FILE cacheFile = fncPath / nwFiles.RECENT_FILE
recent = RecentProjects(fncPath) recent = RecentProjects(fncConf)
# Load when there is no file should pass, but load nothing # Load when there is no file should pass, but load nothing
assert not cacheFile.exists() assert not cacheFile.exists()
-1
View File
@@ -138,7 +138,6 @@ def testBaseInit_Options(monkeypatch, tmpPath):
nwGUI = novelwriter.main( nwGUI = novelwriter.main(
["--testmode", f"--config={tmpPath}", f"--data={tmpPath}", "sample/"] ["--testmode", f"--config={tmpPath}", f"--data={tmpPath}", "sample/"]
) )
assert novelwriter.CONFIG.cmdOpen == "sample/"
assert nwGUI.closeMain() == "closeMain" assert nwGUI.closeMain() == "closeMain"
# END Test testBaseInit_Options # END Test testBaseInit_Options
+1 -3
View File
@@ -215,15 +215,13 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton) qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton)
nwPrefs._doClose() nwPrefs._doClose()
assert theConf.confChanged
assert nwGUI.mainConf.saveConfig() assert nwGUI.mainConf.saveConfig()
projFile = fncPath / "novelwriter.conf" projFile = fncPath / "novelwriter.conf"
testFile = tstPaths.outDir / "guiPreferences_novelwriter.conf" testFile = tstPaths.outDir / "guiPreferences_novelwriter.conf"
compFile = tstPaths.refDir / "guiPreferences_novelwriter.conf" compFile = tstPaths.refDir / "guiPreferences_novelwriter.conf"
copyfile(projFile, testFile) copyfile(projFile, testFile)
ignTuple = ( ignTuple = (
"timestamp", "guifont", "lastnotes", "guilang", "geometry", "timestamp", "font", "lastnotes", "localisation", "geometry",
"preferences", "projcols", "mainpane", "docpane", "viewpane", "preferences", "projcols", "mainpane", "docpane", "viewpane",
"outlinepane", "textfont", "textsize", "lastpath", "backuppath" "outlinepane", "textfont", "textsize", "lastpath", "backuppath"
) )
+39 -4
View File
@@ -21,15 +21,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import pytest import pytest
from tools import C, cmpFiles, buildTestProject, XML_IGNORE, writeFile
from shutil import copyfile from shutil import copyfile
from tools import (
C, cmpFiles, buildTestProject, XML_IGNORE, getGuiItem, writeFile
)
from PyQt5.QtCore import Qt 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.enum import nwItemType, nwView, nwWidget
from novelwriter.tools import GuiProjectWizard from novelwriter.tools import GuiProjectWizard
from novelwriter.dialogs import GuiEditLabel from novelwriter.dialogs import GuiEditLabel, GuiAbout, GuiProjectLoad
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
from novelwriter.gui.outline import GuiOutlineView from novelwriter.gui.outline import GuiOutlineView
from novelwriter.gui.projtree import GuiProjectTree from novelwriter.gui.projtree import GuiProjectTree
@@ -62,7 +65,39 @@ def testGuiMain_ProjectBlocker(nwGUI):
assert nwGUI.showProjectWordListDialog() is False assert nwGUI.showProjectWordListDialog() is False
assert nwGUI.showWritingStatsDialog() 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 @pytest.mark.gui
+6
View File
@@ -150,7 +150,10 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncPath):
# Check handling of broken theme settings # Check handling of broken theme settings
mainConf.guiTheme = "not_a_theme" mainConf.guiTheme = "not_a_theme"
availThemes = mainTheme._availThemes
mainTheme._availThemes = {}
assert mainTheme.loadTheme() is False assert mainTheme.loadTheme() is False
mainTheme._availThemes = availThemes
# Check handling of unreadable file # Check handling of unreadable file
mainConf.guiTheme = "default" mainConf.guiTheme = "default"
@@ -216,8 +219,11 @@ def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncPath):
assert mainTheme.listSyntax() == mainTheme._syntaxList assert mainTheme.listSyntax() == mainTheme._syntaxList
# Check handling of broken theme settings # Check handling of broken theme settings
availSyntax = mainTheme._availSyntax
mainTheme._availSyntax = {}
mainConf.guiSyntax = "not_a_syntax" mainConf.guiSyntax = "not_a_syntax"
assert mainTheme.loadSyntax() is False assert mainTheme.loadSyntax() is False
mainTheme._availSyntax = availSyntax
# Check handling of unreadable file # Check handling of unreadable file
mainConf.guiSyntax = "default_light" mainConf.guiSyntax = "default_light"