Code and GUI text cleanup (#926)

* Move int range checkers from options class to common file
* Improve error handling and reporting
* Make some minor improvements to init and test suite
* Fix 'trypewriter' to 'typewriter' typo
* Remove menu path description in dialog help text
* Improve the help text for entries in the Format menu
* Reword a couple of dialog messages
This commit is contained in:
Veronica Berglyd Olsen
2021-11-07 18:17:17 +01:00
committed by GitHub
parent e71121f738
commit e8bb130245
23 changed files with 257 additions and 250 deletions
+5 -5
View File
@@ -3253,7 +3253,7 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/dialogs/preferences.py" line="788"/> <location filename="../novelwriter/dialogs/preferences.py" line="788"/>
<source>Also improves trypewriter scrolling for short documents.</source> <source>Also improves typewriter scrolling for short documents.</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@@ -4610,17 +4610,17 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="788"/> <location filename="../novelwriter/core/project.py" line="788"/>
<source>Cannot backup project because no backup path is set. Please set a valid backup location in Tools &gt; Preferences.</source> <source>Cannot backup project because no backup path is set. Please set a valid backup location in Preferences.</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="795"/> <location filename="../novelwriter/core/project.py" line="795"/>
<source>Cannot backup project because no project name is set. Please set a Working Title in Project &gt; Project Settings.</source> <source>Cannot backup project because no project name is set. Please set a Working Title in Project Settings.</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="802"/> <location filename="../novelwriter/core/project.py" line="802"/>
<source>Cannot backup project because the backup path does not exist. Please set a valid backup location in Tools &gt; Preferences.</source> <source>Cannot backup project because the backup path does not exist. Please set a valid backup location in Preferences.</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@@ -4630,7 +4630,7 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="821"/> <location filename="../novelwriter/core/project.py" line="821"/>
<source>Cannot backup project because the backup path is within the project folder to be backed up. Please choose a different backup path in Tools &gt; Preferences.</source> <source>Cannot backup project because the backup path is within the project folder to be backed up. Please choose a different backup path in Preferences.</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
+9 -9
View File
@@ -3253,7 +3253,7 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/dialogs/preferences.py" line="788"/> <location filename="../novelwriter/dialogs/preferences.py" line="788"/>
<source>Also improves trypewriter scrolling for short documents.</source> <source>Also improves typewriter scrolling for short documents.</source>
<translation>Améliore également le défilement machine à écrire pour les documents courts.</translation> <translation>Améliore également le défilement machine à écrire pour les documents courts.</translation>
</message> </message>
<message> <message>
@@ -4610,18 +4610,18 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="788"/> <location filename="../novelwriter/core/project.py" line="788"/>
<source>Cannot backup project because no backup path is set. Please set a valid backup location in Tools &gt; Preferences.</source> <source>Cannot backup project because no backup path is set. Please set a valid backup location in Preferences.</source>
<translation>Il est impossible de sauvegarder le projet car aucun emplacement de sauvegarde n&apos;a é défini. Veuillez en définir un dans Outils &gt; Préférences.</translation> <translation>Il est impossible de sauvegarder le projet car aucun emplacement de sauvegarde n&apos;a é défini. Veuillez en définir un dans Préférences.</translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="795"/> <location filename="../novelwriter/core/project.py" line="795"/>
<source>Cannot backup project because no project name is set. Please set a Working Title in Project &gt; Project Settings.</source> <source>Cannot backup project because no project name is set. Please set a Working Title in Project Settings.</source>
<translation>Il est impossible de sauvegarder le projet car il n&apos;a pas reçu de nom. Veuillez définir un titre de travail dans Projet &gt; Caractéristiques du projet.</translation> <translation>Il est impossible de sauvegarder le projet car il n&apos;a pas reçu de nom. Veuillez définir un titre de travail dans Caractéristiques du projet.</translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="802"/> <location filename="../novelwriter/core/project.py" line="802"/>
<source>Cannot backup project because the backup path does not exist. Please set a valid backup location in Tools &gt; Preferences.</source> <source>Cannot backup project because the backup path does not exist. Please set a valid backup location in Preferences.</source>
<translation>Il est impossible de sauvegarder le projet car l&apos;emplacement de sauvegarde défini n&apos;existe pas. Veuillez définir un emplacement correct dans Outils &gt; Préférences.</translation> <translation>Il est impossible de sauvegarder le projet car l&apos;emplacement de sauvegarde défini n&apos;existe pas. Veuillez définir un emplacement correct dans Préférences.</translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="815"/> <location filename="../novelwriter/core/project.py" line="815"/>
@@ -4630,8 +4630,8 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="821"/> <location filename="../novelwriter/core/project.py" line="821"/>
<source>Cannot backup project because the backup path is within the project folder to be backed up. Please choose a different backup path in Tools &gt; Preferences.</source> <source>Cannot backup project because the backup path is within the project folder to be backed up. Please choose a different backup path in Preferences.</source>
<translation>La sauvegarde du projet est impossible car l&apos;emplacement défini est situé dans le dossier à sauvegarder. Veuillez définir un autre répertoire de sauvegarde dans Outils &gt; Préférences.</translation> <translation>La sauvegarde du projet est impossible car l&apos;emplacement défini est situé dans le dossier à sauvegarder. Veuillez définir un autre répertoire de sauvegarde dans Préférences.</translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="828"/> <location filename="../novelwriter/core/project.py" line="828"/>
+9 -9
View File
@@ -3253,7 +3253,7 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/dialogs/preferences.py" line="788"/> <location filename="../novelwriter/dialogs/preferences.py" line="788"/>
<source>Also improves trypewriter scrolling for short documents.</source> <source>Also improves typewriter scrolling for short documents.</source>
<translation>Forbedrer funksjonen til skrivemaskin-rulling.</translation> <translation>Forbedrer funksjonen til skrivemaskin-rulling.</translation>
</message> </message>
<message> <message>
@@ -4605,18 +4605,18 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="788"/> <location filename="../novelwriter/core/project.py" line="788"/>
<source>Cannot backup project because no backup path is set. Please set a valid backup location in Tools &gt; Preferences.</source> <source>Cannot backup project because no backup path is set. Please set a valid backup location in Preferences.</source>
<translation>Kan ikke ta sikkerhetskopi av prosjektet da ingen filbane er satt. Du først sette en filbane i Verktøy &gt; Innstillinger.</translation> <translation>Kan ikke ta sikkerhetskopi av prosjektet da ingen filbane er satt. Du først sette en filbane i Innstillinger.</translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="795"/> <location filename="../novelwriter/core/project.py" line="795"/>
<source>Cannot backup project because no project name is set. Please set a Working Title in Project &gt; Project Settings.</source> <source>Cannot backup project because no project name is set. Please set a Working Title in Project Settings.</source>
<translation>Kan ikke ta sikkerhetskopi av prosjektet da ingen arbeidstittel er satt. Du først sette en arbeidstittel i Prosjekt &gt; Prosjektinnstillinger.</translation> <translation>Kan ikke ta sikkerhetskopi av prosjektet da ingen arbeidstittel er satt. Du først sette en arbeidstittel i Prosjektinnstillinger.</translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="802"/> <location filename="../novelwriter/core/project.py" line="802"/>
<source>Cannot backup project because the backup path does not exist. Please set a valid backup location in Tools &gt; Preferences.</source> <source>Cannot backup project because the backup path does not exist. Please set a valid backup location in Preferences.</source>
<translation>Kan ikke ta sikkerhetskopi av prosjektet da filbane ikke finnes. Du sette en ny filbane i Verktøy &gt; Innstillinger.</translation> <translation>Kan ikke ta sikkerhetskopi av prosjektet da filbane ikke finnes. Du sette en ny filbane i Innstillinger.</translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="815"/> <location filename="../novelwriter/core/project.py" line="815"/>
@@ -4625,8 +4625,8 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="821"/> <location filename="../novelwriter/core/project.py" line="821"/>
<source>Cannot backup project because the backup path is within the project folder to be backed up. Please choose a different backup path in Tools &gt; Preferences.</source> <source>Cannot backup project because the backup path is within the project folder to be backed up. Please choose a different backup path in Preferences.</source>
<translation>Kan ikke ta sikkerhetskopi av prosjektet da filbanen er inne i prosjektmappen. Du sette en ny filbane i Verktøy &gt; Innstillinger.</translation> <translation>Kan ikke ta sikkerhetskopi av prosjektet da filbanen er inne i prosjektmappen. Du sette en ny filbane i Innstillinger.</translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="828"/> <location filename="../novelwriter/core/project.py" line="828"/>
+8 -8
View File
@@ -3228,7 +3228,7 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/dialogs/preferences.py" line="788"/> <location filename="../novelwriter/dialogs/preferences.py" line="788"/>
<source>Also improves trypewriter scrolling for short documents.</source> <source>Also improves typewriter scrolling for short documents.</source>
<translation>Também melhora a rolagem de máquina de escrever em documentos curtos.</translation> <translation>Também melhora a rolagem de máquina de escrever em documentos curtos.</translation>
</message> </message>
<message> <message>
@@ -4580,18 +4580,18 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="788"/> <location filename="../novelwriter/core/project.py" line="788"/>
<source>Cannot backup project because no backup path is set. Please set a valid backup location in Tools &gt; Preferences.</source> <source>Cannot backup project because no backup path is set. Please set a valid backup location in Preferences.</source>
<translation>Não foi possível realizar uma cópia de segurança do projeto porquê o caminho das cópias de segurança não foi definido. Por favor, defina um caminho válido para as cópias de segurança em Ferramentas &gt; Preferências.</translation> <translation>Não foi possível realizar uma cópia de segurança do projeto porquê o caminho das cópias de segurança não foi definido. Por favor, defina um caminho válido para as cópias de segurança em Preferências.</translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="795"/> <location filename="../novelwriter/core/project.py" line="795"/>
<source>Cannot backup project because no project name is set. Please set a Working Title in Project &gt; Project Settings.</source> <source>Cannot backup project because no project name is set. Please set a Working Title in Project Settings.</source>
<translation>Não foi possível realizar a cópia de segurança do projeto porque o nome do projeto não está definido. Por favor defina o Nome do Projeto em Projeto &gt; Configurações do Projeto.</translation> <translation>Não foi possível realizar a cópia de segurança do projeto porque o nome do projeto não está definido. Por favor defina o Nome do Projeto em Configurações do Projeto.</translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="802"/> <location filename="../novelwriter/core/project.py" line="802"/>
<source>Cannot backup project because the backup path does not exist. Please set a valid backup location in Tools &gt; Preferences.</source> <source>Cannot backup project because the backup path does not exist. Please set a valid backup location in Preferences.</source>
<translation>Não foi possível realizar a cópia de segurança do projeto porque o caminho das cópias de segurança não exite. Por favor, defina um cainho válido para as cópias de segurança em Ferramentas &gt; Preferências.</translation> <translation>Não foi possível realizar a cópia de segurança do projeto porque o caminho das cópias de segurança não exite. Por favor, defina um cainho válido para as cópias de segurança em Preferências.</translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="815"/> <location filename="../novelwriter/core/project.py" line="815"/>
@@ -4600,7 +4600,7 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="821"/> <location filename="../novelwriter/core/project.py" line="821"/>
<source>Cannot backup project because the backup path is within the project folder to be backed up. Please choose a different backup path in Tools &gt; Preferences.</source> <source>Cannot backup project because the backup path is within the project folder to be backed up. Please choose a different backup path in Preferences.</source>
<translation>Não foi possível realizar a cópia de segurança do projeto porque o caminho das cópias de segurança está em um caminho dentro do diretório do projeto. Por favor, escolha um caminho diferente para as cópias de segurança em Ferramentas&gt; Preferências.</translation> <translation>Não foi possível realizar a cópia de segurança do projeto porque o caminho das cópias de segurança está em um caminho dentro do diretório do projeto. Por favor, escolha um caminho diferente para as cópias de segurança em Ferramentas&gt; Preferências.</translation>
</message> </message>
<message> <message>
+9 -9
View File
@@ -3253,7 +3253,7 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/dialogs/preferences.py" line="788"/> <location filename="../novelwriter/dialogs/preferences.py" line="788"/>
<source>Also improves trypewriter scrolling for short documents.</source> <source>Also improves typewriter scrolling for short documents.</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@@ -4616,18 +4616,18 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="788"/> <location filename="../novelwriter/core/project.py" line="788"/>
<source>Cannot backup project because no backup path is set. Please set a valid backup location in Tools &gt; Preferences.</source> <source>Cannot backup project because no backup path is set. Please set a valid backup location in Preferences.</source>
<translation> &gt; </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="795"/> <location filename="../novelwriter/core/project.py" line="795"/>
<source>Cannot backup project because no project name is set. Please set a Working Title in Project &gt; Project Settings.</source> <source>Cannot backup project because no project name is set. Please set a Working Title in Project Settings.</source>
<translation> &gt; </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="802"/> <location filename="../novelwriter/core/project.py" line="802"/>
<source>Cannot backup project because the backup path does not exist. Please set a valid backup location in Tools &gt; Preferences.</source> <source>Cannot backup project because the backup path does not exist. Please set a valid backup location in Preferences.</source>
<translation> &gt; </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="815"/> <location filename="../novelwriter/core/project.py" line="815"/>
@@ -4636,8 +4636,8 @@
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="821"/> <location filename="../novelwriter/core/project.py" line="821"/>
<source>Cannot backup project because the backup path is within the project folder to be backed up. Please choose a different backup path in Tools &gt; Preferences.</source> <source>Cannot backup project because the backup path is within the project folder to be backed up. Please choose a different backup path in Preferences.</source>
<translation> &gt; </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../novelwriter/core/project.py" line="828"/> <location filename="../novelwriter/core/project.py" line="828"/>
+5 -5
View File
@@ -215,25 +215,25 @@ def main(sysArgs=None):
errorCode = 0 errorCode = 0
if sys.hexversion < 0x030600f0: if sys.hexversion < 0x030600f0:
errorData.append( errorData.append(
"At least Python 3.6.0 is required, found %s" % CONFIG.verPyString "At least Python 3.6 is required, found %s" % CONFIG.verPyString
) )
errorCode |= 4 errorCode |= 0x04
if CONFIG.verQtValue < 50300: if CONFIG.verQtValue < 50300:
errorData.append( errorData.append(
"At least Qt5 version 5.3 is required, found %s" % CONFIG.verQtString "At least Qt5 version 5.3 is required, found %s" % CONFIG.verQtString
) )
errorCode |= 8 errorCode |= 0x08
if CONFIG.verPyQtValue < 50300: if CONFIG.verPyQtValue < 50300:
errorData.append( errorData.append(
"At least PyQt5 version 5.3 is required, found %s" % CONFIG.verPyQtString "At least PyQt5 version 5.3 is required, found %s" % CONFIG.verPyQtString
) )
errorCode |= 16 errorCode |= 0x10
try: try:
import lxml # noqa: F401 import lxml # noqa: F401
except ImportError: except ImportError:
errorData.append("Python module 'lxml' is missing") errorData.append("Python module 'lxml' is missing")
errorCode |= 32 errorCode |= 0x20
if errorData: if errorData:
errApp = QApplication([]) errApp = QApplication([])
+20
View File
@@ -174,6 +174,26 @@ def hexToInt(value, default=0):
return default return default
def checkIntRange(value, first, last, default):
"""Check that an int is in a given range. If it isn't, return the
default value.
"""
if isinstance(value, int):
if value >= first and value <= last:
return value
return default
def checkIntTuple(value, valid, default):
"""Check that an int is an element of a tuple. If it isn't, return
the default value.
"""
if isinstance(value, int):
if value in valid:
return value
return default
# =============================================================================================== # # =============================================================================================== #
# Formatting Functions # Formatting Functions
# =============================================================================================== # # =============================================================================================== #
+12 -12
View File
@@ -307,12 +307,12 @@ class Config:
if not os.path.isdir(self.confPath): if not os.path.isdir(self.confPath):
try: try:
os.mkdir(self.confPath) os.mkdir(self.confPath)
except Exception as e: except Exception as exc:
logger.error("Could not create folder: %s", self.confPath) logger.error("Could not create folder: %s", self.confPath)
logException() logException()
self.hasError = True self.hasError = True
self.errData.append("Could not create folder: %s" % self.confPath) self.errData.append("Could not create folder: %s" % self.confPath)
self.errData.append(str(e)) self.errData.append(str(exc))
self.confPath = None self.confPath = None
# Check if config file exists # Check if config file exists
@@ -330,12 +330,12 @@ class Config:
if not os.path.isdir(self.dataPath): if not os.path.isdir(self.dataPath):
try: try:
os.mkdir(self.dataPath) os.mkdir(self.dataPath)
except Exception as e: except Exception as exc:
logger.error("Could not create folder: %s", self.dataPath) logger.error("Could not create folder: %s", self.dataPath)
logException() logException()
self.hasError = True self.hasError = True
self.errData.append("Could not create folder: %s" % self.dataPath) self.errData.append("Could not create folder: %s" % self.dataPath)
self.errData.append(str(e)) self.errData.append(str(exc))
self.dataPath = None self.dataPath = None
# Host and Kernel # Host and Kernel
@@ -425,12 +425,12 @@ class Config:
try: try:
with open(cnfPath, mode="r", encoding="utf-8") as inFile: with open(cnfPath, mode="r", encoding="utf-8") as inFile:
theConf.read_file(inFile) theConf.read_file(inFile)
except Exception as e: except Exception as exc:
logger.error("Could not load config file") logger.error("Could not load config file")
logException() logException()
self.hasError = True self.hasError = True
self.errData.append("Could not load config file") self.errData.append("Could not load config file")
self.errData.append(str(e)) self.errData.append(str(exc))
return False return False
# Main # Main
@@ -655,12 +655,12 @@ class Config:
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 self.confChanged = False
except Exception as e: except Exception as exc:
logger.error("Could not save config file") logger.error("Could not save config file")
logException() logException()
self.hasError = True self.hasError = True
self.errData.append("Could not save config file") self.errData.append("Could not save config file")
self.errData.append(str(e)) self.errData.append(str(exc))
return False return False
return True return True
@@ -688,10 +688,10 @@ class Config:
"words": theEntry.get("words", 0), "words": theEntry.get("words", 0),
} }
except Exception as e: except Exception as exc:
self.hasError = True self.hasError = True
self.errData.append("Could not load recent project cache") self.errData.append("Could not load recent project cache")
self.errData.append(str(e)) self.errData.append(str(exc))
return False return False
return True return True
@@ -708,10 +708,10 @@ class Config:
try: try:
with open(cacheTemp, mode="w+", encoding="utf-8") as outFile: with open(cacheTemp, mode="w+", encoding="utf-8") as outFile:
json.dump(self.recentProj, outFile, indent=2) json.dump(self.recentProj, outFile, indent=2)
except Exception as e: except Exception as exc:
self.hasError = True self.hasError = True
self.errData.append("Could not save recent project cache") self.errData.append("Could not save recent project cache")
self.errData.append(str(e)) self.errData.append(str(exc))
return False return False
if os.path.isfile(cacheFile): if os.path.isfile(cacheFile):
+6 -6
View File
@@ -100,8 +100,8 @@ class NWDoc():
# Load the rest of the file # Load the rest of the file
theText += inFile.read() theText += inFile.read()
except Exception as e: except Exception as exc:
self._docError = str(e) self._docError = str(exc)
return None return None
else: else:
@@ -149,8 +149,8 @@ class NWDoc():
with open(docTemp, mode="w", encoding="utf-8") as outFile: with open(docTemp, mode="w", encoding="utf-8") as outFile:
outFile.write(docMeta) outFile.write(docMeta)
outFile.write(docText) outFile.write(docText)
except Exception as e: except Exception as exc:
self._docError = str(e) self._docError = str(exc)
return False return False
# If we're here, the file was successfully saved, so we can # If we're here, the file was successfully saved, so we can
@@ -184,8 +184,8 @@ class NWDoc():
try: try:
os.unlink(chkFile) os.unlink(chkFile)
logger.debug("Deleted: %s", chkFile) logger.debug("Deleted: %s", chkFile)
except Exception as e: except Exception as exc:
self._docError = str(e) self._docError = str(exc)
return False return False
return True return True
-22
View File
@@ -185,26 +185,4 @@ class OptionState():
return checkBool(self._theState[group].get(name, default), default) return checkBool(self._theState[group].get(name, default), default)
return default return default
##
# Validators
##
def validIntRange(self, value, first, last, default):
"""Check that an int is in a given range. If it isn't, return
the default value.
"""
if isinstance(value, int):
if value >= first and value <= last:
return value
return default
def validIntTuple(self, value, valid, default):
"""Check that an int is an element of a tuple. If it isn't,
return the default value.
"""
if isinstance(value, int):
if value in valid:
return value
return default
# END Class OptionState # END Class OptionState
+42 -42
View File
@@ -415,10 +415,10 @@ class NWProject():
try: try:
nwXML = etree.parse(fileName) nwXML = etree.parse(fileName)
except Exception as e: except Exception as exc:
self.theParent.makeAlert([ self.theParent.makeAlert(self.tr(
self.tr("Failed to parse project xml."), str(e) "Failed to parse project xml."
], nwAlert.ERROR) ), nwAlert.ERROR, exception=exc)
# Trying to open backup file instead # Trying to open backup file instead
backFile = fileName[:-3]+"bak" backFile = fileName[:-3]+"bak"
@@ -428,10 +428,10 @@ class NWProject():
), nwAlert.INFO) ), nwAlert.INFO)
try: try:
nwXML = etree.parse(backFile) nwXML = etree.parse(backFile)
except Exception as e: except Exception as exc:
self.theParent.makeAlert([ self.theParent.makeAlert(self.tr(
self.tr("Failed to parse project xml."), str(e) "Failed to parse project xml."
], nwAlert.ERROR) ), nwAlert.ERROR, exception=exc)
self.clearProject() self.clearProject()
return False return False
else: else:
@@ -487,8 +487,8 @@ class NWProject():
self.tr("File Version"), self.tr("File Version"),
self.tr( self.tr(
"The file format of your project is about to be updated. " "The file format of your project is about to be updated. "
"If you proceed, this project can no longer be opened by " "If you proceed, older versions of novelWriter will no "
"an older version of novelWriter. Continue?" "longer be able to open this project. Continue?"
) )
) )
if not msgYes: if not msgYes:
@@ -708,10 +708,10 @@ class NWProject():
encoding="utf-8", encoding="utf-8",
xml_declaration=True xml_declaration=True
)) ))
except Exception as e: except Exception as exc:
self.theParent.makeAlert([ self.theParent.makeAlert(self.tr(
self.tr("Failed to save project."), str(e) "Failed to save project."
], nwAlert.ERROR) ), nwAlert.ERROR, exception=exc)
return False return False
# If we're here, the file was successfully saved, # If we're here, the file was successfully saved,
@@ -788,21 +788,21 @@ class NWProject():
if self.mainConf.backupPath is None or self.mainConf.backupPath == "": if self.mainConf.backupPath is None or self.mainConf.backupPath == "":
self.theParent.makeAlert(self.tr( self.theParent.makeAlert(self.tr(
"Cannot backup project because no backup path is set. " "Cannot backup project because no backup path is set. "
"Please set a valid backup location in Tools > Preferences." "Please set a valid backup location in Preferences."
), nwAlert.ERROR) ), nwAlert.ERROR)
return False return False
if self.projName is None or self.projName == "": if self.projName is None or self.projName == "":
self.theParent.makeAlert(self.tr( self.theParent.makeAlert(self.tr(
"Cannot backup project because no project name is set. " "Cannot backup project because no project name is set. "
"Please set a Working Title in Project > Project Settings." "Please set a Working Title in Project Settings."
), nwAlert.ERROR) ), nwAlert.ERROR)
return False return False
if not os.path.isdir(self.mainConf.backupPath): if not os.path.isdir(self.mainConf.backupPath):
self.theParent.makeAlert(self.tr( self.theParent.makeAlert(self.tr(
"Cannot backup project because the backup path does not exist. " "Cannot backup project because the backup path does not exist. "
"Please set a valid backup location in Tools > Preferences." "Please set a valid backup location in Preferences."
), nwAlert.ERROR) ), nwAlert.ERROR)
return False return False
@@ -812,17 +812,17 @@ class NWProject():
try: try:
os.mkdir(baseDir) os.mkdir(baseDir)
logger.debug("Created folder: %s", baseDir) logger.debug("Created folder: %s", baseDir)
except Exception as e: except Exception as exc:
self.theParent.makeAlert([ self.theParent.makeAlert(self.tr(
self.tr("Could not create backup folder."), str(e) "Could not create backup folder."
], nwAlert.ERROR) ), nwAlert.ERROR, exception=exc)
return False return False
if os.path.commonpath([self.projPath, baseDir]) == self.projPath: if os.path.commonpath([self.projPath, baseDir]) == self.projPath:
self.theParent.makeAlert(self.tr( self.theParent.makeAlert(self.tr(
"Cannot backup project because the backup path is within the " "Cannot backup project because the backup path is within the "
"project folder to be backed up. Please choose a different " "project folder to be backed up. Please choose a different "
"backup path in Tools > Preferences." "backup path in Preferences."
), nwAlert.ERROR) ), nwAlert.ERROR)
return False return False
@@ -839,10 +839,10 @@ class NWProject():
"Backup archive file written to: {0}" "Backup archive file written to: {0}"
).format(f"{os.path.join(cleanName, archName)}.zip"), nwAlert.INFO) ).format(f"{os.path.join(cleanName, archName)}.zip"), nwAlert.INFO)
except Exception as e: except Exception as exc:
self.theParent.makeAlert([ self.theParent.makeAlert(self.tr(
self.tr("Could not write backup archive."), str(e) "Could not write backup archive."
], nwAlert.ERROR) ), nwAlert.ERROR, exception=exc)
return False return False
self.theParent.setStatus(self.tr( self.theParent.setStatus(self.tr(
@@ -872,10 +872,10 @@ class NWProject():
try: try:
shutil.unpack_archive(pkgSample, projPath) shutil.unpack_archive(pkgSample, projPath)
isSuccess = True isSuccess = True
except Exception as e: except Exception as exc:
self.theParent.makeAlert([ self.theParent.makeAlert(self.tr(
self.tr("Failed to create a new example project."), str(e) "Failed to create a new example project."
], nwAlert.ERROR) ), nwAlert.ERROR, exception=exc)
elif os.path.isdir(srcSample): elif os.path.isdir(srcSample):
@@ -894,10 +894,10 @@ class NWProject():
isSuccess = True isSuccess = True
except Exception as e: except Exception as exc:
self.theParent.makeAlert([ self.theParent.makeAlert(self.tr(
self.tr("Failed to create a new example project."), str(e) "Failed to create a new example project."
], nwAlert.ERROR) ), nwAlert.ERROR, exception=exc)
else: else:
self.theParent.makeAlert(self.tr( self.theParent.makeAlert(self.tr(
@@ -933,10 +933,10 @@ class NWProject():
try: try:
os.mkdir(projPath) os.mkdir(projPath)
logger.debug("Created folder: %s", projPath) logger.debug("Created folder: %s", projPath)
except Exception as e: except Exception as exc:
self.theParent.makeAlert([ self.theParent.makeAlert(self.tr(
self.tr("Could not create new project folder."), str(e) "Could not create new project folder."
], nwAlert.ERROR) ), nwAlert.ERROR, exception=exc)
return False return False
if os.path.isdir(projPath): if os.path.isdir(projPath):
@@ -1327,10 +1327,10 @@ class NWProject():
try: try:
os.mkdir(thePath) os.mkdir(thePath)
logger.debug("Created folder: %s", thePath) logger.debug("Created folder: %s", thePath)
except Exception as e: except Exception as exc:
self.theParent.makeAlert([ self.theParent.makeAlert(self.tr(
self.tr("Could not create folder."), str(e) "Could not create folder."
], nwAlert.ERROR) ), nwAlert.ERROR, exception=exc)
return False return False
return True return True
+1 -1
View File
@@ -776,7 +776,7 @@ class GuiPreferencesEditor(QWidget):
self.mainForm.addRow( self.mainForm.addRow(
self.tr("Scroll past end of the document"), self.tr("Scroll past end of the document"),
self.scrollPastEnd, self.scrollPastEnd,
self.tr("Also improves trypewriter scrolling for short documents.") self.tr("Also improves typewriter scrolling for short documents.")
) )
# Typewriter Scrolling # Typewriter Scrolling
+4 -4
View File
@@ -183,13 +183,13 @@ def exceptionHandler(exType, exValue, exTrace):
nwGUI.closeMain() nwGUI.closeMain()
logger.info("Emergency shutdown successful") logger.info("Emergency shutdown successful")
except Exception as e: except Exception as exc:
logger.critical("Could not close the project before exiting") logger.critical("Could not close the project before exiting")
logger.critical(str(e)) logger.critical(str(exc))
qApp.exit(1) qApp.exit(1)
except Exception as e: except Exception as exc:
logger.critical(str(e)) logger.critical(str(exc))
return return
+16 -16
View File
@@ -793,28 +793,28 @@ class GuiMainMenu(QMenuBar):
# Format > Header 1 (Partition) # Format > Header 1 (Partition)
self.aFmtHead1 = QAction(self.tr("Header 1 (Partition)"), self) self.aFmtHead1 = QAction(self.tr("Header 1 (Partition)"), self)
self.aFmtHead1.setStatusTip(self.tr("Change the block format to Header 1")) self.aFmtHead1.setStatusTip(self.tr("Set the text block format to Header 1 (Partition)"))
self.aFmtHead1.setShortcut("Ctrl+1") self.aFmtHead1.setShortcut("Ctrl+1")
self.aFmtHead1.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H1)) self.aFmtHead1.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H1))
self.fmtMenu.addAction(self.aFmtHead1) self.fmtMenu.addAction(self.aFmtHead1)
# Format > Header 2 (Chapter) # Format > Header 2 (Chapter)
self.aFmtHead2 = QAction(self.tr("Header 2 (Chapter)"), self) self.aFmtHead2 = QAction(self.tr("Header 2 (Chapter)"), self)
self.aFmtHead2.setStatusTip(self.tr("Change the block format to Header 2")) self.aFmtHead2.setStatusTip(self.tr("Set the text block format to Header 2 (Chapter)"))
self.aFmtHead2.setShortcut("Ctrl+2") self.aFmtHead2.setShortcut("Ctrl+2")
self.aFmtHead2.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H2)) self.aFmtHead2.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H2))
self.fmtMenu.addAction(self.aFmtHead2) self.fmtMenu.addAction(self.aFmtHead2)
# Format > Header 3 (Scene) # Format > Header 3 (Scene)
self.aFmtHead3 = QAction(self.tr("Header 3 (Scene)"), self) self.aFmtHead3 = QAction(self.tr("Header 3 (Scene)"), self)
self.aFmtHead3.setStatusTip(self.tr("Change the block format to Header 3")) self.aFmtHead3.setStatusTip(self.tr("Set the text block format to Header 3 (Scene)"))
self.aFmtHead3.setShortcut("Ctrl+3") self.aFmtHead3.setShortcut("Ctrl+3")
self.aFmtHead3.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H3)) self.aFmtHead3.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H3))
self.fmtMenu.addAction(self.aFmtHead3) self.fmtMenu.addAction(self.aFmtHead3)
# Format > Header 4 (Section) # Format > Header 4 (Section)
self.aFmtHead4 = QAction(self.tr("Header 4 (Section)"), self) self.aFmtHead4 = QAction(self.tr("Header 4 (Section)"), self)
self.aFmtHead4.setStatusTip(self.tr("Change the block format to Header 4")) self.aFmtHead4.setStatusTip(self.tr("Set the text block format to Header 4 (Section)"))
self.aFmtHead4.setShortcut("Ctrl+4") self.aFmtHead4.setShortcut("Ctrl+4")
self.aFmtHead4.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H4)) self.aFmtHead4.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H4))
self.fmtMenu.addAction(self.aFmtHead4) self.fmtMenu.addAction(self.aFmtHead4)
@@ -824,13 +824,13 @@ class GuiMainMenu(QMenuBar):
# Format > Novel Title # Format > Novel Title
self.aFmtTitle = QAction(self.tr("Novel Title"), self) self.aFmtTitle = QAction(self.tr("Novel Title"), self)
self.aFmtTitle.setStatusTip(self.tr("Change the block format to Novel Title")) self.aFmtTitle.setStatusTip(self.tr("Set the text block format to Novel Title"))
self.aFmtTitle.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TTL)) self.aFmtTitle.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TTL))
self.fmtMenu.addAction(self.aFmtTitle) self.fmtMenu.addAction(self.aFmtTitle)
# Format > Unnumbered Chapter # Format > Unnumbered Chapter
self.aFmtUnNum = QAction(self.tr("Unnumbered Chapter"), self) self.aFmtUnNum = QAction(self.tr("Unnumbered Chapter"), self)
self.aFmtUnNum.setStatusTip(self.tr("Change the block format to Unnumbered Chapter")) self.aFmtUnNum.setStatusTip(self.tr("Set the text block format to Unnumbered Chapter"))
self.aFmtUnNum.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_UNN)) self.aFmtUnNum.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_UNN))
self.fmtMenu.addAction(self.aFmtUnNum) self.fmtMenu.addAction(self.aFmtUnNum)
@@ -839,21 +839,21 @@ class GuiMainMenu(QMenuBar):
# Format > Align Left # Format > Align Left
self.aFmtAlignLeft = QAction(self.tr("Align Left"), self) self.aFmtAlignLeft = QAction(self.tr("Align Left"), self)
self.aFmtAlignLeft.setStatusTip(self.tr("Change the block alignment to left")) self.aFmtAlignLeft.setStatusTip(self.tr("Left-align the text block"))
self.aFmtAlignLeft.setShortcut("Ctrl+5") self.aFmtAlignLeft.setShortcut("Ctrl+5")
self.aFmtAlignLeft.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_L)) self.aFmtAlignLeft.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_L))
self.fmtMenu.addAction(self.aFmtAlignLeft) self.fmtMenu.addAction(self.aFmtAlignLeft)
# Format > Align Centre # Format > Align Centre
self.aFmtAlignCentre = QAction(self.tr("Align Centre"), self) self.aFmtAlignCentre = QAction(self.tr("Align Centre"), self)
self.aFmtAlignCentre.setStatusTip(self.tr("Change the block alignment to centre")) self.aFmtAlignCentre.setStatusTip(self.tr("Centre the text block"))
self.aFmtAlignCentre.setShortcut("Ctrl+6") self.aFmtAlignCentre.setShortcut("Ctrl+6")
self.aFmtAlignCentre.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_C)) self.aFmtAlignCentre.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_C))
self.fmtMenu.addAction(self.aFmtAlignCentre) self.fmtMenu.addAction(self.aFmtAlignCentre)
# Format > Align Right # Format > Align Right
self.aFmtAlignRight = QAction(self.tr("Align Right"), self) self.aFmtAlignRight = QAction(self.tr("Align Right"), self)
self.aFmtAlignRight.setStatusTip(self.tr("Change the block alignment to right")) self.aFmtAlignRight.setStatusTip(self.tr("Right-align the text block"))
self.aFmtAlignRight.setShortcut("Ctrl+7") self.aFmtAlignRight.setShortcut("Ctrl+7")
self.aFmtAlignRight.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_R)) self.aFmtAlignRight.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_R))
self.fmtMenu.addAction(self.aFmtAlignRight) self.fmtMenu.addAction(self.aFmtAlignRight)
@@ -863,14 +863,14 @@ class GuiMainMenu(QMenuBar):
# Format > Indent Left # Format > Indent Left
self.aFmtIndentLeft = QAction(self.tr("Indent Left"), self) self.aFmtIndentLeft = QAction(self.tr("Indent Left"), self)
self.aFmtIndentLeft.setStatusTip(self.tr("Increase the block's left margin")) self.aFmtIndentLeft.setStatusTip(self.tr("Increase the text block's left margin"))
self.aFmtIndentLeft.setShortcut("Ctrl+8") self.aFmtIndentLeft.setShortcut("Ctrl+8")
self.aFmtIndentLeft.triggered.connect(lambda: self._docAction(nwDocAction.INDENT_L)) self.aFmtIndentLeft.triggered.connect(lambda: self._docAction(nwDocAction.INDENT_L))
self.fmtMenu.addAction(self.aFmtIndentLeft) self.fmtMenu.addAction(self.aFmtIndentLeft)
# Format > Indent Right # Format > Indent Right
self.aFmtIndentRight = QAction(self.tr("Indent Right"), self) self.aFmtIndentRight = QAction(self.tr("Indent Right"), self)
self.aFmtIndentRight.setStatusTip(self.tr("Increase the block's right margin")) self.aFmtIndentRight.setStatusTip(self.tr("Increase the text block's right margin"))
self.aFmtIndentRight.setShortcut("Ctrl+9") self.aFmtIndentRight.setShortcut("Ctrl+9")
self.aFmtIndentRight.triggered.connect(lambda: self._docAction(nwDocAction.INDENT_R)) self.aFmtIndentRight.triggered.connect(lambda: self._docAction(nwDocAction.INDENT_R))
self.fmtMenu.addAction(self.aFmtIndentRight) self.fmtMenu.addAction(self.aFmtIndentRight)
@@ -880,14 +880,14 @@ class GuiMainMenu(QMenuBar):
# Format > Comment # Format > Comment
self.aFmtComment = QAction(self.tr("Comment"), self) self.aFmtComment = QAction(self.tr("Comment"), self)
self.aFmtComment.setStatusTip(self.tr("Change the block format to comment")) self.aFmtComment.setStatusTip(self.tr("Change the text block format to comment"))
self.aFmtComment.setShortcut("Ctrl+/") self.aFmtComment.setShortcut("Ctrl+/")
self.aFmtComment.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_COM)) self.aFmtComment.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_COM))
self.fmtMenu.addAction(self.aFmtComment) self.fmtMenu.addAction(self.aFmtComment)
# Format > Remove Block Format # Format > Remove Block Format
self.aFmtNoFormat = QAction(self.tr("Remove Block Format"), self) self.aFmtNoFormat = QAction(self.tr("Remove Block Format"), self)
self.aFmtNoFormat.setStatusTip(self.tr("Strips block format")) self.aFmtNoFormat.setStatusTip(self.tr("Strip text block format"))
self.aFmtNoFormat.setShortcuts(["Ctrl+0", "Ctrl+Shift+/"]) self.aFmtNoFormat.setShortcuts(["Ctrl+0", "Ctrl+Shift+/"])
self.aFmtNoFormat.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TXT)) self.aFmtNoFormat.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TXT))
self.fmtMenu.addAction(self.aFmtNoFormat) self.fmtMenu.addAction(self.aFmtNoFormat)
@@ -898,7 +898,7 @@ class GuiMainMenu(QMenuBar):
# Format > Replace Single Quotes # Format > Replace Single Quotes
self.aFmtReplSng = QAction(self.tr("Replace Single Quotes"), self) self.aFmtReplSng = QAction(self.tr("Replace Single Quotes"), self)
self.aFmtReplSng.setStatusTip( self.aFmtReplSng.setStatusTip(
self.tr("Replace all straight single quotes in selected text") self.tr("Replace all straight single quotes in the selected text")
) )
self.aFmtReplSng.triggered.connect(lambda: self._docAction(nwDocAction.REPL_SNG)) self.aFmtReplSng.triggered.connect(lambda: self._docAction(nwDocAction.REPL_SNG))
self.fmtMenu.addAction(self.aFmtReplSng) self.fmtMenu.addAction(self.aFmtReplSng)
@@ -906,7 +906,7 @@ class GuiMainMenu(QMenuBar):
# Format > Replace Double Quotes # Format > Replace Double Quotes
self.aFmtReplDbl = QAction(self.tr("Replace Double Quotes"), self) self.aFmtReplDbl = QAction(self.tr("Replace Double Quotes"), self)
self.aFmtReplDbl.setStatusTip( self.aFmtReplDbl.setStatusTip(
self.tr("Replace all straight double quotes in selected text") self.tr("Replace all straight double quotes in the selected text")
) )
self.aFmtReplDbl.triggered.connect(lambda: self._docAction(nwDocAction.REPL_DBL)) self.aFmtReplDbl.triggered.connect(lambda: self._docAction(nwDocAction.REPL_DBL))
self.fmtMenu.addAction(self.aFmtReplDbl) self.fmtMenu.addAction(self.aFmtReplDbl)
@@ -914,7 +914,7 @@ class GuiMainMenu(QMenuBar):
# Format > Remove In-Paragraph Breaks # Format > Remove In-Paragraph Breaks
self.aFmtRmBreaks = QAction(self.tr("Remove In-Paragraph Breaks"), self) self.aFmtRmBreaks = QAction(self.tr("Remove In-Paragraph Breaks"), self)
self.aFmtRmBreaks.setStatusTip( self.aFmtRmBreaks.setStatusTip(
self.tr("Removes all line breaks within paragraphs in the selected text") self.tr("Remove all line breaks within paragraphs in the selected text")
) )
self.aFmtRmBreaks.triggered.connect(lambda: self._docAction(nwDocAction.RM_BREAKS)) self.aFmtRmBreaks.triggered.connect(lambda: self._docAction(nwDocAction.RM_BREAKS))
self.fmtMenu.addAction(self.aFmtRmBreaks) self.fmtMenu.addAction(self.aFmtRmBreaks)
+34 -33
View File
@@ -500,10 +500,10 @@ class GuiMain(QMainWindow):
), ),
self.tr( self.tr(
"Note: If the program or the computer previously " "Note: If the program or the computer previously "
"crashed, the lock can safely be overridden. If, " "crashed, the lock can safely be overridden. However, "
"however, another instance of novelWriter has the " "overriding it is not recommended if the project is "
"project open, overriding the lock may corrupt the " "open in another instance of novelWriter. Doing so "
"project, and is not recommended." "may corrupt the project."
), ),
lockDetails lockDetails
), ),
@@ -730,10 +730,10 @@ class GuiMain(QMainWindow):
with open(loadFile, mode="rt", encoding="utf-8") as inFile: with open(loadFile, mode="rt", encoding="utf-8") as inFile:
theText = inFile.read() theText = inFile.read()
self.mainConf.setLastPath(loadFile) self.mainConf.setLastPath(loadFile)
except Exception as e: except Exception as exc:
self.makeAlert([ self.makeAlert(self.tr(
self.tr("Could not read file. The file must be an existing text file."), str(e) "Could not read file. The file must be an existing text file."
], nwAlert.ERROR) ), nwAlert.ERROR, exception=exc)
return False return False
if self.docEditor.docHandle() is None: if self.docEditor.docHandle() is None:
@@ -1116,51 +1116,52 @@ class GuiMain(QMainWindow):
return return
def makeAlert(self, theMessage, theLevel=nwAlert.INFO): def makeAlert(self, message, level=nwAlert.INFO, exception=None):
"""Alert both the user and the logger at the same time. The """Alert both the user and the logger at the same time. The
message can be either a string or a list of strings. message can be either a string or a list of strings.
""" """
if isinstance(theMessage, list): if isinstance(message, list):
theMessage = list(filter(None, theMessage)) # Strip empty strings message = list(filter(None, message)) # Strip empty strings
popMsg = "<br>".join(theMessage) popMsg = "<br>".join(message)
logMsg = theMessage logMsg = " ".join(message)
else: else:
popMsg = theMessage popMsg = str(message)
logMsg = [theMessage] logMsg = str(message)
kw = {}
if exception is not None:
kw["exc_info"] = exception
popMsg = f"{popMsg}<br>{type(exception).__name__}: {str(exception)}"
# Write to Log # Write to Log
if theLevel == nwAlert.INFO: if level == nwAlert.INFO:
for msgLine in logMsg: logger.info(logMsg, **kw)
logger.info(msgLine) elif level == nwAlert.WARN:
elif theLevel == nwAlert.WARN: logger.warning(logMsg, **kw)
for msgLine in logMsg: elif level == nwAlert.ERROR:
logger.warning(msgLine) logger.error(logMsg, **kw)
elif theLevel == nwAlert.ERROR: elif level == nwAlert.BUG:
for msgLine in logMsg: logger.error(logMsg, **kw)
logger.error(msgLine)
elif theLevel == nwAlert.BUG:
for msgLine in logMsg:
logger.error(msgLine)
# Popup # Popup
msgBox = QMessageBox() msgBox = QMessageBox()
if theLevel == nwAlert.INFO: if level == nwAlert.INFO:
msgBox.information(self, self.tr("Information"), popMsg) msgBox.information(self, self.tr("Information"), popMsg)
elif theLevel == nwAlert.WARN: elif level == nwAlert.WARN:
msgBox.warning(self, self.tr("Warning"), popMsg) msgBox.warning(self, self.tr("Warning"), popMsg)
elif theLevel == nwAlert.ERROR: elif level == nwAlert.ERROR:
msgBox.critical(self, self.tr("Error"), popMsg) msgBox.critical(self, self.tr("Error"), popMsg)
elif theLevel == nwAlert.BUG: elif level == nwAlert.BUG:
popMsg += "<br>%s" % self.tr("This is a bug!") popMsg += "<br>%s" % self.tr("This is a bug!")
msgBox.critical(self, self.tr("Internal Error"), popMsg) msgBox.critical(self, self.tr("Internal Error"), popMsg)
return return
def askQuestion(self, theTitle, theQuestion): def askQuestion(self, title, question):
"""Ask the user a Yes/No question. """Ask the user a Yes/No question.
""" """
msgBox = QMessageBox() msgBox = QMessageBox()
msgRes = msgBox.question(self, theTitle, theQuestion, QMessageBox.Yes | QMessageBox.No) msgRes = msgBox.question(self, title, question, QMessageBox.Yes | QMessageBox.No)
return msgRes == QMessageBox.Yes return msgRes == QMessageBox.Yes
def reportConfErr(self): def reportConfErr(self):
+14 -14
View File
@@ -872,8 +872,8 @@ class GuiBuildNovel(QDialog):
try: try:
makeOdt.saveOpenDocText(savePath) makeOdt.saveOpenDocText(savePath)
wSuccess = True wSuccess = True
except Exception as e: except Exception as exc:
errMsg = str(e) errMsg = str(exc)
elif theFmt == self.FMT_FODT: elif theFmt == self.FMT_FODT:
makeOdt = ToOdt(self.theProject, isFlat=True) makeOdt = ToOdt(self.theProject, isFlat=True)
@@ -881,8 +881,8 @@ class GuiBuildNovel(QDialog):
try: try:
makeOdt.saveFlatXML(savePath) makeOdt.saveFlatXML(savePath)
wSuccess = True wSuccess = True
except Exception as e: except Exception as exc:
errMsg = str(e) errMsg = str(exc)
elif theFmt == self.FMT_HTM: elif theFmt == self.FMT_HTM:
makeHtml = ToHtml(self.theProject) makeHtml = ToHtml(self.theProject)
@@ -893,8 +893,8 @@ class GuiBuildNovel(QDialog):
try: try:
makeHtml.saveHTML5(savePath) makeHtml.saveHTML5(savePath)
wSuccess = True wSuccess = True
except Exception as e: except Exception as exc:
errMsg = str(e) errMsg = str(exc)
elif theFmt == self.FMT_NWD: elif theFmt == self.FMT_NWD:
makeNwd = ToMarkdown(self.theProject) makeNwd = ToMarkdown(self.theProject)
@@ -906,8 +906,8 @@ class GuiBuildNovel(QDialog):
try: try:
makeNwd.saveRawMarkdown(savePath) makeNwd.saveRawMarkdown(savePath)
wSuccess = True wSuccess = True
except Exception as e: except Exception as exc:
errMsg = str(e) errMsg = str(exc)
elif theFmt in (self.FMT_MD, self.FMT_GH): elif theFmt in (self.FMT_MD, self.FMT_GH):
makeMd = ToMarkdown(self.theProject) makeMd = ToMarkdown(self.theProject)
@@ -923,8 +923,8 @@ class GuiBuildNovel(QDialog):
try: try:
makeMd.saveMarkdown(savePath) makeMd.saveMarkdown(savePath)
wSuccess = True wSuccess = True
except Exception as e: except Exception as exc:
errMsg = str(e) errMsg = str(exc)
elif theFmt == self.FMT_JSON_H or theFmt == self.FMT_JSON_M: elif theFmt == self.FMT_JSON_H or theFmt == self.FMT_JSON_M:
jsonData = { jsonData = {
@@ -968,8 +968,8 @@ class GuiBuildNovel(QDialog):
with open(savePath, mode="w", encoding="utf-8") as outFile: with open(savePath, mode="w", encoding="utf-8") as outFile:
outFile.write(json.dumps(jsonData, indent=2)) outFile.write(json.dumps(jsonData, indent=2))
wSuccess = True wSuccess = True
except Exception as e: except Exception as exc:
errMsg = str(e) errMsg = str(exc)
elif theFmt == self.FMT_PDF: elif theFmt == self.FMT_PDF:
try: try:
@@ -983,8 +983,8 @@ class GuiBuildNovel(QDialog):
self.docView.document().print(thePrinter) self.docView.document().print(thePrinter)
wSuccess = True wSuccess = True
except Exception as e: except Exception as exc:
errMsg - str(e) errMsg = str(exc)
else: else:
# If the if statements above and here match, it should not # If the if statements above and here match, it should not
+8 -11
View File
@@ -38,7 +38,7 @@ from PyQt5.QtWidgets import (
) )
from novelwriter.enum import nwAlert from novelwriter.enum import nwAlert
from novelwriter.common import formatTime, checkInt from novelwriter.common import formatTime, checkInt, checkIntRange, checkIntTuple
from novelwriter.constants import nwConst, nwFiles from novelwriter.constants import nwConst, nwFiles
from novelwriter.gui.custom import QSwitch from novelwriter.gui.custom import QSwitch
@@ -114,13 +114,10 @@ class GuiWritingStats(QDialog):
hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight) hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight)
hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight) hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight)
sortValid = (Qt.AscendingOrder, Qt.DescendingOrder) sortCol = checkIntRange(self.optState.getInt("GuiWritingStats", "sortCol", 0), 0, 2, 0)
sortCol = self.optState.validIntRange( sortOrder = checkIntTuple(
self.optState.getInt("GuiWritingStats", "sortCol", 0), 0, 2, 0
)
sortOrder = self.optState.validIntTuple(
self.optState.getInt("GuiWritingStats", "sortOrder", Qt.DescendingOrder), self.optState.getInt("GuiWritingStats", "sortOrder", Qt.DescendingOrder),
sortValid, Qt.DescendingOrder (Qt.AscendingOrder, Qt.DescendingOrder), Qt.DescendingOrder
) )
self.listBox.sortByColumn(sortCol, sortOrder) self.listBox.sortByColumn(sortCol, sortOrder)
self.listBox.setSortingEnabled(True) self.listBox.setSortingEnabled(True)
@@ -406,8 +403,8 @@ class GuiWritingStats(QDialog):
outFile.write(f'"{sD}",{tT:.0f},{wD},{wA},{wB},{tI}\n') outFile.write(f'"{sD}",{tT:.0f},{wD},{wA},{wB},{tI}\n')
wSuccess = True wSuccess = True
except Exception as e: except Exception as exc:
errMsg = str(e) errMsg = str(exc)
wSuccess = False wSuccess = False
# Report to user # Report to user
@@ -482,9 +479,9 @@ class GuiWritingStats(QDialog):
self.logData.append((dStart, sDiff, wcNovel, wcNotes, sIdle)) self.logData.append((dStart, sDiff, wcNovel, wcNotes, sIdle))
except Exception as e: except Exception as exc:
self.theParent.makeAlert([ self.theParent.makeAlert([
self.tr("Failed to read session log file."), str(e) self.tr("Failed to read session log file."), str(exc)
], nwAlert.ERROR) ], nwAlert.ERROR)
return False return False
+16 -16
View File
@@ -62,9 +62,9 @@ def extractVersion():
hexVers = getValue((aLine)) hexVers = getValue((aLine))
if aLine.startswith("__date__"): if aLine.startswith("__date__"):
relDate = getValue((aLine)) relDate = getValue((aLine))
except Exception as e: except Exception as exc:
print("Could not read file: %s" % initFile) print("Could not read file: %s" % initFile)
print(str(e)) print(str(exc))
print("novelWriter version: %s (%s) at %s" % (numVers, hexVers, relDate)) print("novelWriter version: %s (%s) at %s" % (numVers, hexVers, relDate))
@@ -127,9 +127,9 @@ def makeCheckSum(sumFile, cwd=None):
with open(shaFile, mode="w") as fOut: with open(shaFile, mode="w") as fOut:
subprocess.call(["sha256sum", sumFile], stdout=fOut, cwd=cwd) subprocess.call(["sha256sum", sumFile], stdout=fOut, cwd=cwd)
print("SHA256 Sum: %s" % shaFile) print("SHA256 Sum: %s" % shaFile)
except Exception as e: except Exception as exc:
print("Could not generate sha256 file") print("Could not generate sha256 file")
print(str(e)) print(str(exc))
return shaFile return shaFile
@@ -163,9 +163,9 @@ def installPackages(hostOS):
pkgCmd = stepCmd.split(" ") pkgCmd = stepCmd.split(" ")
try: try:
subprocess.call(pyCmd + pipCmd + pkgCmd) subprocess.call(pyCmd + pipCmd + pkgCmd)
except Exception as e: except Exception as exc:
print("Failed with error:") print("Failed with error:")
print(str(e)) print(str(exc))
sys.exit(1) sys.exit(1)
return return
@@ -243,10 +243,10 @@ def buildPdfManual():
print("PDF manual build: OK") print("PDF manual build: OK")
print("") print("")
except Exception as e: except Exception as exc:
print("PDF manual build: FAILED") print("PDF manual build: FAILED")
print("") print("")
print(str(e)) print(str(exc))
print("") print("")
print("Dependencies:") print("Dependencies:")
print(" * pip install sphinx") print(" * pip install sphinx")
@@ -286,10 +286,10 @@ def buildQtI18n():
try: try:
subprocess.call(["lrelease", "-verbose", *tsList]) subprocess.call(["lrelease", "-verbose", *tsList])
except Exception as e: except Exception as exc:
print("Qt5 Linguist tools seem to be missing") print("Qt5 Linguist tools seem to be missing")
print("On Debian/Ubuntu, install: qttools5-dev-tools pyqt5-dev-tools") print("On Debian/Ubuntu, install: qttools5-dev-tools pyqt5-dev-tools")
print(str(e)) print(str(exc))
sys.exit(1) sys.exit(1)
print("") print("")
@@ -387,10 +387,10 @@ def buildQtI18nTS(sysArgs):
# try: # try:
# subprocess.call(["pylupdate5", "-verbose", "-noobsolete", *srcList, "-ts", *tsList]) # subprocess.call(["pylupdate5", "-verbose", "-noobsolete", *srcList, "-ts", *tsList])
# except Exception as e: # except Exception as exc:
# print("PyQt5 Linguist tools seem to be missing") # print("PyQt5 Linguist tools seem to be missing")
# print("On Debian/Ubuntu, install: qttools5-dev-tools pyqt5-dev-tools") # print("On Debian/Ubuntu, install: qttools5-dev-tools pyqt5-dev-tools")
# print(str(e)) # print(str(exc))
# sys.exit(1) # sys.exit(1)
print("") print("")
@@ -922,9 +922,9 @@ def makeSimplePackage(embedPython):
sysCmd += [libDir] sysCmd += [libDir]
try: try:
subprocess.call(sysCmd) subprocess.call(sysCmd)
except Exception as e: except Exception as exc:
print("Failed with error:") print("Failed with error:")
print(str(e)) print(str(exc))
sys.exit(1) sys.exit(1)
for subDir in os.listdir(libDir): for subDir in os.listdir(libDir):
@@ -1432,9 +1432,9 @@ def innoSetup():
try: try:
subprocess.call(["iscc", "setup.iss"]) subprocess.call(["iscc", "setup.iss"])
except Exception as e: except Exception as exc:
print("Inno Setup failed with error:") print("Inno Setup failed with error:")
print(str(e)) print(str(exc))
sys.exit(1) sys.exit(1)
return return
+7 -7
View File
@@ -43,15 +43,15 @@ class MockGuiMain():
def releaseNotes(self): def releaseNotes(self):
return return
def makeAlert(self, theMessage, theLevel): def makeAlert(self, message, level=0, exception=None):
assert isinstance(theMessage, str) or isinstance(theMessage, list) assert isinstance(message, str) or isinstance(message, list)
print("%s: %s" % (str(theLevel), theMessage)) print("%s: %s" % (str(level), message))
self.lastAlert = str(theMessage) self.lastAlert = str(message)
return return
def askQuestion(self, theTitle, theQustion): def askQuestion(self, title, qustion):
print("Question: %s" % theQustion) print("Question: %s" % qustion)
self.lastQuestion = (theTitle, theQustion) self.lastQuestion = (title, qustion)
return self.askResponse return self.askResponse
def setStatus(self, theMessage): def setStatus(self, theMessage):
+26 -4
View File
@@ -32,10 +32,10 @@ from tools import writeFile
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
from novelwriter.common import ( from novelwriter.common import (
checkString, checkInt, checkFloat, checkBool, checkHandle, isHandle, checkString, checkInt, checkFloat, checkBool, checkHandle, isHandle,
isTitleTag, isItemClass, isItemType, isItemLayout, hexToInt, formatInt, isTitleTag, isItemClass, isItemType, isItemLayout, hexToInt, checkIntRange,
formatTimeStamp, formatTime, parseTimeStamp, splitVersionNumber, checkIntTuple, formatInt, formatTimeStamp, formatTime, parseTimeStamp,
transferCase, fuzzyTime, numberToRoman, jsonEncode, readTextFile, splitVersionNumber, transferCase, fuzzyTime, numberToRoman, jsonEncode,
makeFileNameSafe, sha256sum, getGuiItem, NWConfigParser readTextFile, makeFileNameSafe, sha256sum, getGuiItem, NWConfigParser
) )
@@ -216,6 +216,28 @@ def testBaseCommon_HexToInt():
# END Test testBaseCommon_HexToInt # END Test testBaseCommon_HexToInt
@pytest.mark.base
def testBaseCommon_CheckIntRange():
"""Test the checkIntRange function.
"""
assert checkIntRange(5, 0, 9, 3) == 5
assert checkIntRange(5, 0, 4, 3) == 3
assert checkIntRange(5, 0, 5, 3) == 5
assert checkIntRange(0, 0, 5, 3) == 0
# END Test testBaseCommon_CheckIntRange
@pytest.mark.base
def testBaseCommon_CheckIntTuple():
"""Test the checkIntTuple function.
"""
assert checkIntTuple(0, (0, 1, 2), 3) == 0
assert checkIntTuple(5, (0, 1, 2), 3) == 3
# END Test testBaseCommon_CheckIntTuple
@pytest.mark.base @pytest.mark.base
def testBaseCommon_FormatTimeStamp(): def testBaseCommon_FormatTimeStamp():
"""Test the formatTimeStamp function. """Test the formatTimeStamp function.
-9
View File
@@ -140,13 +140,4 @@ def testCoreOptions_SetGet(mockGUI):
assert theOpts.getBool("GuiBuildNovel", "addNovel", None) is True assert theOpts.getBool("GuiBuildNovel", "addNovel", None) is True
assert theOpts.getBool("GuiBuildNovel", "mockItem", None) is None assert theOpts.getBool("GuiBuildNovel", "mockItem", None) is None
# Check integer validators
assert theOpts.validIntRange(5, 0, 9, 3) == 5
assert theOpts.validIntRange(5, 0, 4, 3) == 3
assert theOpts.validIntRange(5, 0, 5, 3) == 5
assert theOpts.validIntRange(0, 0, 5, 3) == 0
assert theOpts.validIntTuple(0, (0, 1, 2), 3) == 0
assert theOpts.validIntTuple(5, (0, 1, 2), 3) == 3
# END Test testCoreOptions_SetGet # END Test testCoreOptions_SetGet
+2 -4
View File
@@ -40,17 +40,15 @@ stepDelay = 20
@pytest.mark.gui @pytest.mark.gui
@pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Not running on Darwin")
def testToolProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal): def testToolProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
"""Test the new project wizard. """Test the new project wizard.
Disabled for macOS because the test segfaults on QWizard.show()
""" """
# Block message box # Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
if sys.platform.startswith("darwin"):
# Disable for macOS because the test segfaults on QWizard.show()
return
## ##
# Test New Project Function # Test New Project Function
## ##
+4 -4
View File
@@ -33,14 +33,14 @@ def cmpFiles(fileOne, fileTwo, ignoreLines=None, ignoreStart=None):
try: try:
foOne = open(fileOne, mode="r", encoding="utf-8") foOne = open(fileOne, mode="r", encoding="utf-8")
except Exception as e: except Exception as exc:
print(str(e)) print(str(exc))
return False return False
try: try:
foTwo = open(fileTwo, mode="r", encoding="utf-8") foTwo = open(fileTwo, mode="r", encoding="utf-8")
except Exception as e: except Exception as exc:
print(str(e)) print(str(exc))
return False return False
txtOne = foOne.readlines() txtOne = foOne.readlines()