From 8959af1f30d9e92a3147ba0ac79de78e921e74fa Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 7 Aug 2023 17:01:44 +0200
Subject: [PATCH 01/10] Move make alert to separate module and add a global
singleton for pointers
---
novelwriter/__init__.py | 13 ++++---
novelwriter/alert.py | 78 ++++++++++++++++++++++++++++++++++++++++
novelwriter/config.py | 68 +++++++++++++++++++++++++++++++++--
novelwriter/constants.py | 9 +++--
novelwriter/enum.py | 1 -
novelwriter/guimain.py | 53 ++++++---------------------
6 files changed, 169 insertions(+), 53 deletions(-)
create mode 100644 novelwriter/alert.py
diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py
index 4731b73b..1114c47f 100644
--- a/novelwriter/__init__.py
+++ b/novelwriter/__init__.py
@@ -21,6 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import sys
import getopt
@@ -29,7 +30,7 @@ import logging
from PyQt5.QtWidgets import QApplication, QErrorMessage
from novelwriter.error import exceptionHandler, logException
-from novelwriter.config import Config
+from novelwriter.config import Config, Global
##
# Version Scheme
@@ -71,13 +72,13 @@ logger = logging.getLogger(__name__)
# Main Program
##
-# Load the main config as a global object
+# Create the global singleton instances
CONFIG = Config()
+GLOBAL = Global()
-def main(sysArgs=None):
- """Parse command line, set up logging, and launch main GUI.
- """
+def main(sysArgs: list | None = None):
+ """Parse command line, set up logging, and launch main GUI."""
if sysArgs is None:
sysArgs = sys.argv[1:]
@@ -227,6 +228,7 @@ def main(sysArgs=None):
from novelwriter.guimain import GuiMain
if testMode:
nwGUI = GuiMain()
+ GLOBAL.setGUI(nwGUI)
return nwGUI
else:
@@ -246,6 +248,7 @@ def main(sysArgs=None):
# Launch main GUI
nwGUI = GuiMain()
+ GLOBAL.setGUI(nwGUI)
nwGUI.postLaunchTasks(cmdOpen)
sys.exit(nwApp.exec_())
diff --git a/novelwriter/alert.py b/novelwriter/alert.py
new file mode 100644
index 00000000..eb84107d
--- /dev/null
+++ b/novelwriter/alert.py
@@ -0,0 +1,78 @@
+"""
+novelWriter – Alert Functions
+=============================
+
+File History:
+Created: 2023-08-07 [2.1b2]
+
+This file is a part of novelWriter
+Copyright 2018–2023, Veronica Berglyd Olsen
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+"""
+from __future__ import annotations
+
+import logging
+
+from PyQt5.QtWidgets import QMessageBox
+
+from novelwriter import GLOBAL
+from novelwriter.enum import nwAlert
+from novelwriter.constants import nwLabels
+
+logger = logging.getLogger(__name__)
+
+
+def makeAlert(message: list[str] | str, level: nwAlert, exc: Exception | None = None) -> None:
+ """Alert both the user and the logger at the same time. The
+ message can be either a string or a list of strings.
+ """
+ if isinstance(message, list):
+ message = list(filter(None, message)) # Strip empty strings
+ popMsg = "
".join(message)
+ logMsg = " ".join(message)
+ else:
+ popMsg = str(message)
+ logMsg = str(message)
+
+ kw = {}
+ if exc is not None:
+ kw["exc_info"] = exc
+ popMsg = f"{popMsg}
{type(exc).__name__}: {str(exc)}"
+
+ # Write to Log
+ if level == nwAlert.INFO:
+ logger.info(logMsg, **kw)
+ elif level == nwAlert.WARN:
+ logger.warning(logMsg, **kw)
+ elif level == nwAlert.ERROR:
+ logger.error(logMsg, **kw)
+
+ # Popup
+ msgBox = QMessageBox()
+ if level == nwAlert.INFO:
+ msgBox.information(GLOBAL.gui, nwLabels.ALERT_NAME[level], popMsg)
+ elif level == nwAlert.WARN:
+ msgBox.warning(GLOBAL.gui, nwLabels.ALERT_NAME[level], popMsg)
+ elif level == nwAlert.ERROR:
+ msgBox.critical(GLOBAL.gui, nwLabels.ALERT_NAME[level], popMsg)
+
+ return
+
+
+def askQuestion(title: str, question: str) -> bool:
+ """Ask the user a Yes/No question, and return the answer."""
+ msgBox = QMessageBox()
+ msgRes = msgBox.question(GLOBAL.gui, title, question, QMessageBox.Yes | QMessageBox.No)
+ return msgRes == QMessageBox.Yes
diff --git a/novelwriter/config.py b/novelwriter/config.py
index 60e750a9..d1fb2c88 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -1,10 +1,10 @@
"""
novelWriter – Config Class
==========================
-Class holding the user preferences and handling the config file
File History:
-Created: 2018-09-22 [0.0.1]
+Created: 2018-09-22 [0.0.1] Config
+Created: 2023-08-07 [2.1b2] NWGlobal
This file is a part of novelWriter
Copyright 2018–2023, Veronica Berglyd Olsen
@@ -22,7 +22,6 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-
from __future__ import annotations
import sys
@@ -30,6 +29,7 @@ import json
import logging
from time import time
+from typing import TYPE_CHECKING
from pathlib import Path
from PyQt5.QtGui import QFontDatabase
@@ -42,6 +42,11 @@ from novelwriter.error import logException, formatException
from novelwriter.common import checkPath, formatTimeStamp, NWConfigParser
from novelwriter.constants import nwFiles, nwUnicode
+if TYPE_CHECKING: # pragma: no cover
+ from novelwriter.guimain import GuiMain
+ from novelwriter.gui.theme import GuiTheme
+ from novelwriter.core.project import NWProject
+
logger = logging.getLogger(__name__)
@@ -793,6 +798,63 @@ class Config:
# END Class Config
+class Global:
+ """Singleton: Global Pointers
+
+ This class holds pointers to the core singletons:
+ * The GuiMain instance
+ * The GuiTheme instance
+ * The NWProject instance
+ """
+
+ def __init__(self) -> None:
+ self._gui: GuiMain | None = None
+ self._theme: GuiTheme | None = None
+ self._project: NWProject | None = None
+ return
+
+ @property
+ def gui(self) -> GuiMain:
+ """The main gui instance."""
+ if self._gui is None:
+ raise Exception("GLOBAL not fully initialised")
+ return self._gui
+
+ @property
+ def theme(self) -> GuiTheme:
+ """The main gui theme instance."""
+ if self._theme is None:
+ raise Exception("GLOBAL not fully initialised")
+ return self._theme
+
+ @property
+ def project(self) -> NWProject:
+ """The main project instance."""
+ if self._project is None:
+ raise Exception("GLOBAL not fully initialised")
+ return self._project
+
+ def setGUI(self, gui: GuiMain) -> None:
+ """Set the GUI instance. Can only be set once."""
+ if self._gui is None:
+ self._gui = gui
+ return
+
+ def setTheme(self, theme: GuiTheme) -> None:
+ """Set the theme instance. Can only be set once."""
+ if self._theme is None:
+ self._theme = theme
+ return
+
+ def setProject(self, project: NWProject) -> None:
+ """Set the project instance. Can only be set once."""
+ if self._project is None:
+ self._project = project
+ return
+
+# END Class Global
+
+
class RecentProjects:
def __init__(self, config):
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index 5a306a73..cb77570f 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -1,7 +1,6 @@
"""
novelWriter – Constants
=======================
-Constants and maps for translating flags and enums to text
File History:
Created: 2019-04-28 [0.0.1]
@@ -24,9 +23,10 @@ along with this program. If not, see .
"""
from __future__ import annotations
+
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
-from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
+from novelwriter.enum import nwAlert, nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
def trConst(text: str) -> str:
@@ -163,6 +163,11 @@ class nwLabels:
nwItemLayout.DOCUMENT: QT_TRANSLATE_NOOP("Constant", "Novel Document"),
nwItemLayout.NOTE: QT_TRANSLATE_NOOP("Constant", "Project Note"),
}
+ ALERT_NAME = {
+ nwAlert.INFO: QT_TRANSLATE_NOOP("Constant", "Information"),
+ nwAlert.WARN: QT_TRANSLATE_NOOP("Constant", "Warning"),
+ nwAlert.ERROR: QT_TRANSLATE_NOOP("Constant", "Error"),
+ }
ITEM_DESCRIPTION = {
"none": QT_TRANSLATE_NOOP("Constant", "None"),
"root": QT_TRANSLATE_NOOP("Constant", "Root Folder"),
diff --git a/novelwriter/enum.py b/novelwriter/enum.py
index d96e4b2f..ab91b67e 100644
--- a/novelwriter/enum.py
+++ b/novelwriter/enum.py
@@ -125,7 +125,6 @@ class nwAlert(Enum):
INFO = 0
WARN = 1
ERROR = 2
- BUG = 3
# END Enum nwAlert
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index c43789ef..82814d2f 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -37,7 +37,8 @@ from PyQt5.QtWidgets import (
QStackedWidget, QVBoxLayout, QWidget
)
-from novelwriter import CONFIG, __hexversion__
+from novelwriter import CONFIG, GLOBAL, __hexversion__
+from novelwriter.alert import askQuestion, makeAlert
from novelwriter.gui.theme import GuiTheme
from novelwriter.gui.sidebar import GuiSideBar
from novelwriter.gui.outline import GuiOutlineView
@@ -112,9 +113,13 @@ class GuiMain(QMainWindow):
# Core Classes
# ============
- # Core Classes and Settings
- self.mainTheme = GuiTheme()
- self.theProject = NWProject(self)
+ # Core Classes
+ self.mainTheme = GuiTheme()
+ self.theProject = NWProject(self)
+ GLOBAL.setTheme(self.mainTheme)
+ GLOBAL.setProject(self.theProject)
+
+ # Core Settings
self.hasProject = False
self.isFocusMode = False
self.idleRefTime = time()
@@ -1102,48 +1107,12 @@ class GuiMain(QMainWindow):
"""Alert both the user and the logger at the same time. The
message can be either a string or a list of strings.
"""
- if isinstance(message, list):
- message = list(filter(None, message)) # Strip empty strings
- popMsg = "
".join(message)
- logMsg = " ".join(message)
- else:
- popMsg = str(message)
- logMsg = str(message)
-
- kw = {}
- if exception is not None:
- kw["exc_info"] = exception
- popMsg = f"{popMsg}
{type(exception).__name__}: {str(exception)}"
-
- # Write to Log
- if level == nwAlert.INFO:
- logger.info(logMsg, **kw)
- elif level == nwAlert.WARN:
- logger.warning(logMsg, **kw)
- elif level == nwAlert.ERROR:
- logger.error(logMsg, **kw)
- elif level == nwAlert.BUG:
- logger.error(logMsg, **kw)
-
- # Popup
- msgBox = QMessageBox()
- if level == nwAlert.INFO:
- msgBox.information(self, self.tr("Information"), popMsg)
- elif level == nwAlert.WARN:
- msgBox.warning(self, self.tr("Warning"), popMsg)
- elif level == nwAlert.ERROR:
- msgBox.critical(self, self.tr("Error"), popMsg)
- elif level == nwAlert.BUG:
- popMsg += "
%s" % self.tr("This is a bug!")
- msgBox.critical(self, self.tr("Internal Error"), popMsg)
-
+ makeAlert(message, level, exception)
return
def askQuestion(self, title: str, question: str) -> bool:
"""Ask the user a Yes/No question, and return the answer."""
- msgBox = QMessageBox()
- msgRes = msgBox.question(self, title, question, QMessageBox.Yes | QMessageBox.No)
- return msgRes == QMessageBox.Yes
+ return askQuestion(title, question)
def reportConfErr(self) -> bool:
"""Checks if the Config module has any errors to report, and let
From ec010bf18e3361c596aed4dbc01572c2e94cd4bf Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 7 Aug 2023 17:02:18 +0200
Subject: [PATCH 02/10] Add new icons for alert boxes
---
.../assets/icons/typicons_dark/icons.conf | 4 ++++
.../icons/typicons_dark/typ_delete-full.svg | 4 ++++
.../icons/typicons_dark/typ_info-large-full.svg | 4 ++++
.../icons/typicons_dark/typ_lightbulb-full.svg | 4 ++++
.../icons/typicons_dark/typ_warning-full.svg | 4 ++++
.../assets/icons/typicons_light/icons.conf | 4 ++++
.../icons/typicons_light/typ_delete-full.svg | 4 ++++
.../typicons_light/typ_info-large-full.svg | 4 ++++
.../icons/typicons_light/typ_lightbulb-full.svg | 4 ++++
.../icons/typicons_light/typ_warning-full.svg | 4 ++++
novelwriter/gui/theme.py | 17 +++++++++--------
11 files changed, 49 insertions(+), 8 deletions(-)
create mode 100644 novelwriter/assets/icons/typicons_dark/typ_delete-full.svg
create mode 100644 novelwriter/assets/icons/typicons_dark/typ_info-large-full.svg
create mode 100644 novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg
create mode 100644 novelwriter/assets/icons/typicons_dark/typ_warning-full.svg
create mode 100644 novelwriter/assets/icons/typicons_light/typ_delete-full.svg
create mode 100644 novelwriter/assets/icons/typicons_light/typ_info-large-full.svg
create mode 100644 novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg
create mode 100644 novelwriter/assets/icons/typicons_light/typ_warning-full.svg
diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf
index 8e354654..d209978a 100644
--- a/novelwriter/assets/icons/typicons_dark/icons.conf
+++ b/novelwriter/assets/icons/typicons_dark/icons.conf
@@ -17,6 +17,10 @@ licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
[Map]
add = typ_plus.svg
+alert_error = typ_info-large-full.svg
+alert_info = typ_delete-full.svg
+alert_question = typ_lightbulb-full.svg
+alert_warn = typ_warning-full.svg
backward = typ_chevron-left.svg
bookmark = typ_bookmark.svg
browse = typ_folder-open.svg
diff --git a/novelwriter/assets/icons/typicons_dark/typ_delete-full.svg b/novelwriter/assets/icons/typicons_dark/typ_delete-full.svg
new file mode 100644
index 00000000..2dbe7364
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/typ_delete-full.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/typ_info-large-full.svg b/novelwriter/assets/icons/typicons_dark/typ_info-large-full.svg
new file mode 100644
index 00000000..c9a13390
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/typ_info-large-full.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg b/novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg
new file mode 100644
index 00000000..7397b602
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/typ_warning-full.svg b/novelwriter/assets/icons/typicons_dark/typ_warning-full.svg
new file mode 100644
index 00000000..e6022e06
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/typ_warning-full.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf
index a52469ea..37946169 100644
--- a/novelwriter/assets/icons/typicons_light/icons.conf
+++ b/novelwriter/assets/icons/typicons_light/icons.conf
@@ -17,6 +17,10 @@ licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
[Map]
add = typ_plus.svg
+alert_error = typ_info-large-full.svg
+alert_info = typ_delete-full.svg
+alert_question = typ_lightbulb-full.svg
+alert_warn = typ_warning-full.svg
backward = typ_chevron-left.svg
bookmark = typ_bookmark.svg
browse = typ_folder-open.svg
diff --git a/novelwriter/assets/icons/typicons_light/typ_delete-full.svg b/novelwriter/assets/icons/typicons_light/typ_delete-full.svg
new file mode 100644
index 00000000..5a4dd8cd
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/typ_delete-full.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/typ_info-large-full.svg b/novelwriter/assets/icons/typicons_light/typ_info-large-full.svg
new file mode 100644
index 00000000..80a43508
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/typ_info-large-full.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg b/novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg
new file mode 100644
index 00000000..daf4254c
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/typ_warning-full.svg b/novelwriter/assets/icons/typicons_light/typ_warning-full.svg
new file mode 100644
index 00000000..d6a3b52c
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/typ_warning-full.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index f951fc79..c65fad5d 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -445,14 +445,15 @@ class GuiIcons:
ICON_KEYS = {
# Project and GUI Icons
- "novelwriter", "build_excluded", "build_filtered", "build_included", "cls_archive",
- "cls_character", "cls_custom", "cls_entity", "cls_none", "cls_novel", "cls_object",
- "cls_plot", "cls_timeline", "cls_trash", "cls_world", "proj_chapter", "proj_details",
- "proj_document", "proj_folder", "proj_note", "proj_nwx", "proj_section", "proj_scene",
- "proj_stats", "proj_title", "search_cancel", "search_case", "search_loop",
- "search_preserve", "search_project", "search_regex", "search_word", "status_idle",
- "status_lang", "status_lines", "status_stats", "status_time", "view_build", "view_editor",
- "view_novel", "view_outline",
+ "novelwriter", "alert_error", "alert_info", "alert_question", "alert_warn",
+ "build_excluded", "build_filtered", "build_included", "cls_archive", "cls_character",
+ "cls_custom", "cls_entity", "cls_none", "cls_novel", "cls_object", "cls_plot",
+ "cls_timeline", "cls_trash", "cls_world", "proj_chapter", "proj_details", "proj_document",
+ "proj_folder", "proj_note", "proj_nwx", "proj_section", "proj_scene", "proj_stats",
+ "proj_title", "search_cancel", "search_case", "search_loop", "search_preserve",
+ "search_project", "search_regex", "search_word", "status_idle", "status_lang",
+ "status_lines", "status_stats", "status_time", "view_build", "view_editor", "view_novel",
+ "view_outline",
# General Button Icons
"add", "backward", "bookmark", "browse", "checked", "close", "cross", "down", "edit",
From 90f9fa44510f71f05be16d491f74313abafd27c2 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 7 Aug 2023 20:19:51 +0200
Subject: [PATCH 03/10] Rewrite the makeAlert and askQuestion functions to use
recommended interface
---
novelwriter/__init__.py | 8 +-
novelwriter/alert.py | 78 --------
.../assets/icons/typicons_dark/icons.conf | 4 +-
.../assets/icons/typicons_light/icons.conf | 4 +-
novelwriter/config.py | 4 +-
novelwriter/constants.py | 1 +
novelwriter/enum.py | 1 +
novelwriter/error.py | 38 ++--
novelwriter/guimain.py | 167 ++++++++++--------
9 files changed, 123 insertions(+), 182 deletions(-)
delete mode 100644 novelwriter/alert.py
diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py
index 1114c47f..d83415f7 100644
--- a/novelwriter/__init__.py
+++ b/novelwriter/__init__.py
@@ -30,7 +30,7 @@ import logging
from PyQt5.QtWidgets import QApplication, QErrorMessage
from novelwriter.error import exceptionHandler, logException
-from novelwriter.config import Config, Global
+from novelwriter.config import Config, NWApp
##
# Version Scheme
@@ -74,7 +74,7 @@ logger = logging.getLogger(__name__)
# Create the global singleton instances
CONFIG = Config()
-GLOBAL = Global()
+APP = NWApp()
def main(sysArgs: list | None = None):
@@ -228,7 +228,7 @@ def main(sysArgs: list | None = None):
from novelwriter.guimain import GuiMain
if testMode:
nwGUI = GuiMain()
- GLOBAL.setGUI(nwGUI)
+ APP.setGUI(nwGUI)
return nwGUI
else:
@@ -248,7 +248,7 @@ def main(sysArgs: list | None = None):
# Launch main GUI
nwGUI = GuiMain()
- GLOBAL.setGUI(nwGUI)
+ APP.setGUI(nwGUI)
nwGUI.postLaunchTasks(cmdOpen)
sys.exit(nwApp.exec_())
diff --git a/novelwriter/alert.py b/novelwriter/alert.py
deleted file mode 100644
index eb84107d..00000000
--- a/novelwriter/alert.py
+++ /dev/null
@@ -1,78 +0,0 @@
-"""
-novelWriter – Alert Functions
-=============================
-
-File History:
-Created: 2023-08-07 [2.1b2]
-
-This file is a part of novelWriter
-Copyright 2018–2023, Veronica Berglyd Olsen
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-"""
-from __future__ import annotations
-
-import logging
-
-from PyQt5.QtWidgets import QMessageBox
-
-from novelwriter import GLOBAL
-from novelwriter.enum import nwAlert
-from novelwriter.constants import nwLabels
-
-logger = logging.getLogger(__name__)
-
-
-def makeAlert(message: list[str] | str, level: nwAlert, exc: Exception | None = None) -> None:
- """Alert both the user and the logger at the same time. The
- message can be either a string or a list of strings.
- """
- if isinstance(message, list):
- message = list(filter(None, message)) # Strip empty strings
- popMsg = "
".join(message)
- logMsg = " ".join(message)
- else:
- popMsg = str(message)
- logMsg = str(message)
-
- kw = {}
- if exc is not None:
- kw["exc_info"] = exc
- popMsg = f"{popMsg}
{type(exc).__name__}: {str(exc)}"
-
- # Write to Log
- if level == nwAlert.INFO:
- logger.info(logMsg, **kw)
- elif level == nwAlert.WARN:
- logger.warning(logMsg, **kw)
- elif level == nwAlert.ERROR:
- logger.error(logMsg, **kw)
-
- # Popup
- msgBox = QMessageBox()
- if level == nwAlert.INFO:
- msgBox.information(GLOBAL.gui, nwLabels.ALERT_NAME[level], popMsg)
- elif level == nwAlert.WARN:
- msgBox.warning(GLOBAL.gui, nwLabels.ALERT_NAME[level], popMsg)
- elif level == nwAlert.ERROR:
- msgBox.critical(GLOBAL.gui, nwLabels.ALERT_NAME[level], popMsg)
-
- return
-
-
-def askQuestion(title: str, question: str) -> bool:
- """Ask the user a Yes/No question, and return the answer."""
- msgBox = QMessageBox()
- msgRes = msgBox.question(GLOBAL.gui, title, question, QMessageBox.Yes | QMessageBox.No)
- return msgRes == QMessageBox.Yes
diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf
index d209978a..32a523f7 100644
--- a/novelwriter/assets/icons/typicons_dark/icons.conf
+++ b/novelwriter/assets/icons/typicons_dark/icons.conf
@@ -17,8 +17,8 @@ licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
[Map]
add = typ_plus.svg
-alert_error = typ_info-large-full.svg
-alert_info = typ_delete-full.svg
+alert_error = typ_delete-full.svg
+alert_info = typ_info-large-full.svg
alert_question = typ_lightbulb-full.svg
alert_warn = typ_warning-full.svg
backward = typ_chevron-left.svg
diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf
index 37946169..4a2ce078 100644
--- a/novelwriter/assets/icons/typicons_light/icons.conf
+++ b/novelwriter/assets/icons/typicons_light/icons.conf
@@ -17,8 +17,8 @@ licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
[Map]
add = typ_plus.svg
-alert_error = typ_info-large-full.svg
-alert_info = typ_delete-full.svg
+alert_error = typ_delete-full.svg
+alert_info = typ_info-large-full.svg
alert_question = typ_lightbulb-full.svg
alert_warn = typ_warning-full.svg
backward = typ_chevron-left.svg
diff --git a/novelwriter/config.py b/novelwriter/config.py
index d1fb2c88..7bbf125e 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -798,7 +798,7 @@ class Config:
# END Class Config
-class Global:
+class NWApp:
"""Singleton: Global Pointers
This class holds pointers to the core singletons:
@@ -852,7 +852,7 @@ class Global:
self._project = project
return
-# END Class Global
+# END Class NWApp
class RecentProjects:
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index cb77570f..c3abf581 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -167,6 +167,7 @@ class nwLabels:
nwAlert.INFO: QT_TRANSLATE_NOOP("Constant", "Information"),
nwAlert.WARN: QT_TRANSLATE_NOOP("Constant", "Warning"),
nwAlert.ERROR: QT_TRANSLATE_NOOP("Constant", "Error"),
+ nwAlert.ASK: QT_TRANSLATE_NOOP("Constant", "Question"),
}
ITEM_DESCRIPTION = {
"none": QT_TRANSLATE_NOOP("Constant", "None"),
diff --git a/novelwriter/enum.py b/novelwriter/enum.py
index ab91b67e..aeab3f5e 100644
--- a/novelwriter/enum.py
+++ b/novelwriter/enum.py
@@ -125,6 +125,7 @@ class nwAlert(Enum):
INFO = 0
WARN = 1
ERROR = 2
+ ASK = 3
# END Enum nwAlert
diff --git a/novelwriter/error.py b/novelwriter/error.py
index 73c08290..8b4eeba6 100644
--- a/novelwriter/error.py
+++ b/novelwriter/error.py
@@ -1,7 +1,6 @@
"""
novelWriter – Exception Handling
================================
-Error handling function and error dialog
File History:
Created: 2020-08-02 [0.10.2]
@@ -22,18 +21,24 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import sys
import random
import logging
+from typing import TYPE_CHECKING
+
from PyQt5.QtGui import QFont, QFontDatabase
-from PyQt5.QtCore import Qt
+from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import (
- qApp, QDialog, QGridLayout, QStyle, QPlainTextEdit, QLabel,
+ QWidget, qApp, QDialog, QGridLayout, QStyle, QPlainTextEdit, QLabel,
QDialogButtonBox
)
+if TYPE_CHECKING: # pragma: no cover
+ from types import TracebackType
+
logger = logging.getLogger(__name__)
@@ -41,15 +46,15 @@ logger = logging.getLogger(__name__)
# Utility Functions
# =============================================================================================== #
-def logException():
- """Log the content of an exception message.
- """
+def logException() -> None:
+ """Log the content of an exception message."""
exType, exValue, _ = sys.exc_info()
if exType is not None:
logger.error("%s: %s", exType.__name__, str(exValue))
+ return
-def formatException(exc):
+def formatException(exc) -> str:
"""Format an exception as a string the same way the default
exception handler does.
"""
@@ -62,7 +67,7 @@ def formatException(exc):
class NWErrorMessage(QDialog):
- def __init__(self, parent):
+ def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
self.setObjectName("NWErrorMessage")
@@ -113,7 +118,7 @@ class NWErrorMessage(QDialog):
return
- def setMessage(self, exType, exValue, exTrace):
+ def setMessage(self, exType: type, exValue: BaseException, exTrace: TracebackType) -> None:
"""Generate a message and append session data, error info and
error traceback.
"""
@@ -142,7 +147,7 @@ class NWErrorMessage(QDialog):
enchantVersion = "Unknown"
try:
- exTrace = "\n".join(format_tb(exTrace))
+ txtTrace = "\n".join(format_tb(exTrace))
self.msgBody.setPlainText((
"Environment:\n"
f"novelWriter Version: {__version__}\n"
@@ -151,7 +156,7 @@ class NWErrorMessage(QDialog):
f"Qt: {QT_VERSION_STR}, PyQt: {PYQT_VERSION_STR}\n"
f"enchant: {enchantVersion}\n\n"
f"{exType.__name__}:\n{str(exValue)}\n\n"
- f"Traceback:\n{exTrace}\n"
+ f"Traceback:\n{txtTrace}\n"
))
except Exception:
self.msgBody.setPlainText("Failed to generate error report ...")
@@ -162,18 +167,17 @@ class NWErrorMessage(QDialog):
# Slots
##
- def _doClose(self):
- """Close the dialog.
- """
+ @pyqtSlot()
+ def _doClose(self) -> None:
+ """Close the dialog."""
self.close()
return
# END Class NWErrorMessage
-def exceptionHandler(exType, exValue, exTrace):
- """Function to catch unhandled global exceptions.
- """
+def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackType) -> None:
+ """Function to catch unhandled global exceptions."""
from traceback import print_tb
from PyQt5.QtWidgets import qApp
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 82814d2f..67e674e1 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -31,14 +31,13 @@ from pathlib import Path
from datetime import datetime
from PyQt5.QtCore import Qt, QTimer, QThreadPool, pyqtSlot
-from PyQt5.QtGui import QCloseEvent, QCursor, QIcon, QKeySequence
+from PyQt5.QtGui import QCloseEvent, QCursor, QIcon, QKeySequence, QPixmap
from PyQt5.QtWidgets import (
qApp, QDialog, QFileDialog, QMainWindow, QMessageBox, QShortcut, QSplitter,
QStackedWidget, QVBoxLayout, QWidget
)
-from novelwriter import CONFIG, GLOBAL, __hexversion__
-from novelwriter.alert import askQuestion, makeAlert
+from novelwriter import CONFIG, APP, __hexversion__
from novelwriter.gui.theme import GuiTheme
from novelwriter.gui.sidebar import GuiSideBar
from novelwriter.gui.outline import GuiOutlineView
@@ -67,7 +66,7 @@ from novelwriter.enum import (
nwDocAction, nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwView
)
from novelwriter.common import getGuiItem, hexToInt
-from novelwriter.constants import nwFiles
+from novelwriter.constants import nwFiles, nwLabels, trConst
logger = logging.getLogger(__name__)
@@ -116,8 +115,8 @@ class GuiMain(QMainWindow):
# Core Classes
self.mainTheme = GuiTheme()
self.theProject = NWProject(self)
- GLOBAL.setTheme(self.mainTheme)
- GLOBAL.setProject(self.theProject)
+ APP.setTheme(self.mainTheme)
+ APP.setProject(self.theProject)
# Core Settings
self.hasProject = False
@@ -318,6 +317,16 @@ class GuiMain(QMainWindow):
# Handle Windows Mode
self.showNormal()
+ # Cache Icons
+
+ aPx = CONFIG.pxInt(48)
+ self.alertPix: dict[nwAlert, QPixmap] = {
+ nwAlert.INFO: self.mainTheme.getPixmap("alert_info", (aPx, aPx)),
+ nwAlert.WARN: self.mainTheme.getPixmap("alert_warn", (aPx, aPx)),
+ nwAlert.ERROR: self.mainTheme.getPixmap("alert_error", (aPx, aPx)),
+ nwAlert.ASK: self.mainTheme.getPixmap("alert_question", (aPx, aPx)),
+ }
+
logger.debug("Ready: GUI")
if __hexversion__[-2] == "a" and logger.getEffectiveLevel() > logging.DEBUG:
@@ -325,7 +334,7 @@ class GuiMain(QMainWindow):
"You are running an untested development version of novelWriter. "
"Please be careful when working on a live project "
"and make sure you take regular backups."
- ), nwAlert.WARN)
+ ), level=nwAlert.WARN)
logger.info("novelWriter is ready ...")
self.setStatus(self.tr("novelWriter is ready ..."))
@@ -385,7 +394,7 @@ class GuiMain(QMainWindow):
if not self.closeProject():
self.makeAlert(self.tr(
"Cannot create a new project when another project is open."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
if projData is None:
@@ -403,7 +412,7 @@ class GuiMain(QMainWindow):
self.makeAlert(self.tr(
"A project already exists in that location. "
"Please choose another folder."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
logger.info("Creating new project")
@@ -425,13 +434,10 @@ class GuiMain(QMainWindow):
return True
if not isYes:
- msgYes = self.askQuestion(
- self.tr("Close Project"),
- "%s
%s" % (
- self.tr("Close the current project?"),
- self.tr("Changes are saved automatically.")
- )
- )
+ msgYes = self.askQuestion("%s
%s" % (
+ self.tr("Close the current project?"),
+ self.tr("Changes are saved automatically.")
+ ))
if not msgYes:
return False
@@ -443,10 +449,7 @@ class GuiMain(QMainWindow):
if self.theProject.data.doBackup and CONFIG.backupOnClose:
doBackup = True
if CONFIG.askBeforeBackup:
- msgYes = self.askQuestion(
- self.tr("Backup Project"),
- self.tr("Backup the current project?")
- )
+ msgYes = self.askQuestion(self.tr("Backup the current project?"))
if not msgYes:
doBackup = False
@@ -485,19 +488,28 @@ class GuiMain(QMainWindow):
# Try to open the project
if not self.theProject.openProject(projFile):
# The project open failed.
-
lockStatus = self.theProject.getLockStatus()
if lockStatus is None:
# The project is not locked, so failed for some other
# reason handled by the project class.
return False
+ lockText = self.tr(
+ "The project is already open by another instance of "
+ "novelWriter, and is therefore locked. Override lock "
+ "and continue anyway?"
+ )
+ lockInfo = self.tr(
+ "Note: If the program or the computer previously "
+ "crashed, the lock can safely be overridden. However, "
+ "overriding it is not recommended if the project is "
+ "open in another instance of novelWriter. Doing so "
+ "may corrupt the project."
+ )
try:
- lockDetails = (
- "
%s" % self.tr(
- "The project was locked by the computer "
- "'{0}' ({1} {2}), last active on {3}."
- )
+ lockDetails = self.tr(
+ "The project was locked by the computer "
+ "'{0}' ({1} {2}), last active on {3}."
).format(
lockStatus[0], lockStatus[1], lockStatus[2],
datetime.fromtimestamp(int(lockStatus[3])).strftime("%x %X")
@@ -505,27 +517,7 @@ class GuiMain(QMainWindow):
except Exception:
lockDetails = ""
- msgBox = QMessageBox()
- msgRes = msgBox.warning(
- self, self.tr("Project Locked"),
- "%s
%s
%s" % (
- self.tr(
- "The project is already open by another instance of "
- "novelWriter, and is therefore locked. Override lock "
- "and continue anyway?"
- ),
- self.tr(
- "Note: If the program or the computer previously "
- "crashed, the lock can safely be overridden. However, "
- "overriding it is not recommended if the project is "
- "open in another instance of novelWriter. Doing so "
- "may corrupt the project."
- ),
- lockDetails
- ),
- QMessageBox.Yes | QMessageBox.No, QMessageBox.No
- )
- if msgRes == QMessageBox.Yes:
+ if self.askQuestion(lockText, info=lockInfo, details=lockDetails, level=nwAlert.WARN):
if not self.theProject.openProject(projFile, overrideLock=True):
return False
else:
@@ -565,9 +557,7 @@ class GuiMain(QMainWindow):
# Check if we need to rebuild the index
if self.theProject.index.indexBroken:
- self.makeAlert(self.tr(
- "The project index is outdated or broken. Rebuilding index."
- ), nwAlert.INFO)
+ self.makeAlert(self.tr("The project index is outdated or broken. Rebuilding index."))
self.rebuildIndex()
# Make sure the changed status is set to false on things opened
@@ -756,23 +746,20 @@ class GuiMain(QMainWindow):
except Exception as exc:
self.makeAlert(self.tr(
"Could not read file. The file must be an existing text file."
- ), nwAlert.ERROR, exception=exc)
+ ), level=nwAlert.ERROR, exception=exc)
return False
if self.docEditor.docHandle() is None:
self.makeAlert(self.tr(
"Please open a document to import the text file into."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
if not self.docEditor.isEmpty():
- msgYes = self.askQuestion(
- self.tr("Import Document"),
- self.tr(
- "Importing the file will overwrite the current content of "
- "the document. Do you want to proceed?"
- )
- )
+ msgYes = self.askQuestion(self.tr(
+ "Importing the file will overwrite the current content of "
+ "the document. Do you want to proceed?"
+ ))
if not msgYes:
return False
@@ -871,9 +858,7 @@ class GuiMain(QMainWindow):
qApp.restoreOverrideCursor()
if not beQuiet:
- self.makeAlert(self.tr(
- "The project index has been successfully rebuilt."
- ), nwAlert.INFO)
+ self.makeAlert(self.tr("The project index has been successfully rebuilt."))
return True
@@ -921,7 +906,7 @@ class GuiMain(QMainWindow):
if dlgConf.needsRestart:
self.makeAlert(self.tr(
"Some changes will not be applied until novelWriter has been restarted."
- ), nwAlert.INFO)
+ ))
if dlgConf.refreshTree:
self.projView.populateTree()
@@ -1102,17 +1087,48 @@ class GuiMain(QMainWindow):
return
- def makeAlert(self, message: list[str] | str, level: nwAlert = nwAlert.INFO,
- exception: Exception | None = None) -> None:
+ def makeAlert(self, text: str, info: str = "", details: str = "",
+ level: nwAlert = nwAlert.INFO, exception: Exception | None = None) -> None:
"""Alert both the user and the logger at the same time. The
message can be either a string or a list of strings.
"""
- makeAlert(message, level, exception)
+ logText = " ".join(filter(None, [text, info, details]))
+ if level == nwAlert.INFO:
+ logger.info(logText, stacklevel=2)
+ elif level == nwAlert.WARN:
+ logger.warning(logText, stacklevel=2)
+ elif level == nwAlert.ERROR:
+ logger.error(logText, stacklevel=2, exc_info=exception)
+
+ if exception is not None:
+ excText = f"{type(exception).__name__}: {str(exception)}"
+ info = f"{info}
{excText}" if info else excText
+
+ msgBox = QMessageBox(self)
+ msgBox.setWindowTitle(trConst(nwLabels.ALERT_NAME[level]))
+ msgBox.setText(text)
+ msgBox.setInformativeText(info)
+ msgBox.setDetailedText(details)
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
+ msgBox.setIconPixmap(self.alertPix[level])
+ msgBox.adjustSize()
+ msgBox.exec_()
+
return
- def askQuestion(self, title: str, question: str) -> bool:
+ def askQuestion(self, text: str, info: str = "", details: str = "",
+ level: nwAlert = nwAlert.ASK) -> bool:
"""Ask the user a Yes/No question, and return the answer."""
- return askQuestion(title, question)
+ msgBox = QMessageBox(self)
+ msgBox.setWindowTitle(trConst(nwLabels.ALERT_NAME[level]))
+ msgBox.setText(text)
+ msgBox.setInformativeText(info)
+ msgBox.setDetailedText(details)
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
+ msgBox.setIconPixmap(self.alertPix[level])
+ msgBox.adjustSize()
+ msgBox.exec_()
+ return msgBox.result() == QMessageBox.Yes
def reportConfErr(self) -> bool:
"""Checks if the Config module has any errors to report, and let
@@ -1120,7 +1136,7 @@ class GuiMain(QMainWindow):
errors since it is initialised before the GUI itself.
"""
if CONFIG.hasError:
- self.makeAlert(CONFIG.errorText(), nwAlert.ERROR)
+ self.makeAlert(CONFIG.errorText(), level=nwAlert.ERROR)
return True
return False
@@ -1131,13 +1147,10 @@ class GuiMain(QMainWindow):
def closeMain(self) -> bool:
"""Save everything, and close novelWriter."""
if self.hasProject:
- msgYes = self.askQuestion(
- self.tr("Exit"),
- "%s
%s" % (
- self.tr("Do you want to exit novelWriter?"),
- self.tr("Changes are saved automatically.")
- )
- )
+ msgYes = self.askQuestion("%s
%s" % (
+ self.tr("Do you want to exit novelWriter?"),
+ self.tr("Changes are saved automatically.")
+ ))
if not msgYes:
return False
@@ -1388,7 +1401,7 @@ class GuiMain(QMainWindow):
"from the Tools menu, or by pressing {1}."
).format(
tag, "F9"
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return None, None
return tHandle, sTitle
From 3911bc10c6392e245a9830d377d01c634251635f Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 7 Aug 2023 20:20:04 +0200
Subject: [PATCH 04/10] Clean up code and tests
---
novelwriter/core/coretools.py | 4 +-
novelwriter/core/project.py | 61 +++++++++++------------
novelwriter/dialogs/projload.py | 11 ++--
novelwriter/dialogs/projsettings.py | 11 ++--
novelwriter/dialogs/wordlist.py | 4 +-
novelwriter/gui/doceditor.py | 55 ++++++++++----------
novelwriter/gui/projtree.py | 54 +++++++++-----------
novelwriter/tools/manusbuild.py | 3 +-
novelwriter/tools/manuscript.py | 6 +--
novelwriter/tools/manussettings.py | 6 +--
novelwriter/tools/writingstats.py | 14 +++---
tests/conftest.py | 6 +--
tests/mocked.py | 16 +++---
tests/test_core/test_core_project.py | 6 +--
tests/test_gui/test_gui_guimain.py | 2 +-
tests/test_gui/test_gui_i18n.py | 9 ++--
tests/test_gui/test_gui_mainmenu.py | 10 ++--
tests/test_gui/test_gui_projtree.py | 10 ++--
tests/test_tools/test_tools_manusbuild.py | 2 +-
19 files changed, 131 insertions(+), 159 deletions(-)
diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py
index e7791613..114c92ff 100644
--- a/novelwriter/core/coretools.py
+++ b/novelwriter/core/coretools.py
@@ -479,7 +479,7 @@ class ProjectBuilder:
except Exception as exc:
self.mainGui.makeAlert(self.tr(
"Failed to create a new example project."
- ), nwAlert.ERROR, exception=exc)
+ ), level=nwAlert.ERROR, exception=exc)
return False
else:
@@ -487,7 +487,7 @@ class ProjectBuilder:
"Failed to create a new example project. "
"Could not find the necessary files. "
"They seem to be missing from this installation."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
return True
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index 20fb6dc2..88450946 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -173,9 +173,10 @@ class NWProject(QObject):
if self._tree.checkType(tHandle, nwItemType.FILE):
delDoc = self._storage.getDocument(tHandle)
if not delDoc.deleteDocument():
- self.mainGui.makeAlert([
- self.tr("Could not delete document file."), delDoc.getError()
- ], nwAlert.ERROR)
+ self.mainGui.makeAlert(
+ self.tr("Could not delete document file."),
+ info=delDoc.getError(), level=nwAlert.ERROR
+ )
return False
self._index.deleteHandle(tHandle)
@@ -227,7 +228,7 @@ class NWProject(QObject):
if not self._storage.openProjectInPlace(projPath):
self.mainGui.makeAlert(self.tr(
"Could not open project with path: {0}"
- ).format(projPath), nwAlert.ERROR)
+ ).format(projPath), level=nwAlert.ERROR)
return False
# Project Lock
@@ -266,17 +267,17 @@ class NWProject(QObject):
if xmlReader.state == XMLReadState.NOT_NWX_FILE:
self.mainGui.makeAlert(self.tr(
"Project file does not appear to be a novelWriterXML file."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
elif xmlReader.state == XMLReadState.UNKNOWN_VERSION:
self.mainGui.makeAlert(self.tr(
"Unknown or unsupported novelWriter project file format. "
"The project cannot be opened by this version of novelWriter. "
"The file was saved with novelWriter version {0}."
- ).format(appVersion), nwAlert.ERROR)
+ ).format(appVersion), level=nwAlert.ERROR)
else:
self.mainGui.makeAlert(self.tr(
"Failed to parse project xml."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
self.clearProject()
return False
@@ -285,14 +286,11 @@ class NWProject(QObject):
# ====================
if xmlReader.state == XMLReadState.WAS_LEGACY:
- msgYes = self.mainGui.askQuestion(
- self.tr("File Version"),
- self.tr(
- "The file format of your project is about to be updated. "
- "If you proceed, older versions of novelWriter will no "
- "longer be able to open this project. Continue?"
- )
- )
+ msgYes = self.mainGui.askQuestion(self.tr(
+ "The file format of your project is about to be updated. "
+ "If you proceed, older versions of novelWriter will no "
+ "longer be able to open this project. Continue?"
+ ))
if not msgYes:
self.clearProject()
return False
@@ -301,16 +299,13 @@ class NWProject(QObject):
# =========================
if xmlReader.hexVersion > hexToInt(__hexversion__):
- msgYes = self.mainGui.askQuestion(
- self.tr("Version Conflict"),
- self.tr(
- "This project was saved by a newer version of "
- "novelWriter, version {0}. This is version {1}. If you "
- "continue to open the project, some attributes and "
- "settings may not be preserved, but the overall project "
- "should be fine. Continue opening the project?"
- ).format(appVersion, __version__)
- )
+ msgYes = self.mainGui.askQuestion(self.tr(
+ "This project was saved by a newer version of "
+ "novelWriter, version {0}. This is version {1}. If you "
+ "continue to open the project, some attributes and "
+ "settings may not be preserved, but the overall project "
+ "should be fine. Continue opening the project?"
+ ).format(appVersion, __version__))
if not msgYes:
self.clearProject()
return False
@@ -333,7 +328,7 @@ class NWProject(QObject):
if orphans > 0:
self.mainGui.makeAlert(self.tr(
"Found {0} orphaned file(s) in the project. {1} file(s) were recovered."
- ).format(orphans, recovered), nwAlert.WARN)
+ ).format(orphans, recovered), level=nwAlert.WARN)
self._index.loadIndex()
if xmlReader.state == XMLReadState.WAS_LEGACY:
@@ -357,7 +352,7 @@ class NWProject(QObject):
if not self._storage.isOpen():
self.mainGui.makeAlert(self.tr(
"There is no project open."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
saveTime = time()
@@ -382,7 +377,7 @@ class NWProject(QObject):
if not xmlWriter.write(self._data, content, saveTime, editTime):
self.mainGui.makeAlert(self.tr(
"Failed to save project."
- ), nwAlert.ERROR, exception=xmlWriter.error)
+ ), level=nwAlert.ERROR, exception=xmlWriter.error)
return False
# Save other project data
@@ -426,14 +421,14 @@ class NWProject(QObject):
self.mainGui.makeAlert(self.tr(
"Cannot backup project because no valid backup path is set. "
"Please set a valid backup location in Preferences."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
if not self._data.name:
self.mainGui.makeAlert(self.tr(
"Cannot backup project because no project name is set. "
"Please set a Project Name in Project Settings."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
cleanName = makeFileNameSafe(self._data.name)
@@ -443,7 +438,7 @@ class NWProject(QObject):
except Exception as exc:
self.mainGui.makeAlert(self.tr(
"Could not create backup folder."
- ), nwAlert.ERROR, exception=exc)
+ ), level=nwAlert.ERROR, exception=exc)
return False
timeStamp = formatTimeStamp(time(), fileSafe=True)
@@ -453,11 +448,11 @@ class NWProject(QObject):
if doNotify:
self.mainGui.makeAlert(self.tr(
"Backup archive file written to: {0} [{1}B]"
- ).format(str(archName), formatInt(size)), nwAlert.INFO)
+ ).format(str(archName), formatInt(size)))
else:
self.mainGui.makeAlert(self.tr(
"Could not write backup archive."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
self.mainGui.setStatus(self.tr(
diff --git a/novelwriter/dialogs/projload.py b/novelwriter/dialogs/projload.py
index 04cef031..916ca820 100644
--- a/novelwriter/dialogs/projload.py
+++ b/novelwriter/dialogs/projload.py
@@ -225,13 +225,10 @@ class GuiProjectLoad(QDialog):
selList = self.listBox.selectedItems()
if selList:
projName = selList[0].text(self.C_NAME)
- msgYes = self.mainGui.askQuestion(
- self.tr("Remove Entry"),
- self.tr(
- "Remove '{0}' from the recent projects list? "
- "The project files will not be deleted."
- ).format(projName)
- )
+ msgYes = self.mainGui.askQuestion(self.tr(
+ "Remove '{0}' from the recent projects list? "
+ "The project files will not be deleted."
+ ).format(projName))
if msgYes:
CONFIG.recentProjects.remove(
selList[0].data(self.C_NAME, self.D_PATH)
diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py
index f496b5e8..a639f42c 100644
--- a/novelwriter/dialogs/projsettings.py
+++ b/novelwriter/dialogs/projsettings.py
@@ -1,7 +1,6 @@
"""
novelWriter – GUI Project Settings
==================================
-GUI classes for the project settings dialog
File History:
Created: 2018-09-29 [0.0.1]
@@ -22,9 +21,12 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import logging
+from typing import TYPE_CHECKING
+
from PyQt5.QtGui import QIcon, QPixmap, QColor
from PyQt5.QtCore import Qt, QLocale, pyqtSlot
from PyQt5.QtWidgets import (
@@ -39,6 +41,9 @@ from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.pageddialog import NPagedDialog
from novelwriter.extensions.configlayout import NConfigLayout
+if TYPE_CHECKING: # pragma: no cover
+ from novelwriter.guimain import GuiMain
+
logger = logging.getLogger(__name__)
@@ -49,7 +54,7 @@ class GuiProjectSettings(NPagedDialog):
TAB_IMPORT = 2
TAB_REPLACE = 3
- def __init__(self, mainGui, focusTab=TAB_MAIN):
+ def __init__(self, mainGui: GuiMain, focusTab: int = TAB_MAIN) -> None:
super().__init__(parent=mainGui)
logger.debug("Create: GuiProjectSettings")
@@ -441,7 +446,7 @@ class GuiProjectEditStatus(QWidget):
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
self.mainGui.makeAlert(self.tr(
"Cannot delete a status item that is in use."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
else:
self.listBox.takeTopLevelItem(iRow)
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py
index 55503376..9f807162 100644
--- a/novelwriter/dialogs/wordlist.py
+++ b/novelwriter/dialogs/wordlist.py
@@ -128,13 +128,13 @@ class GuiWordList(QDialog):
if word == "":
self.mainGui.makeAlert(self.tr(
"Cannot add a blank word."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return
if self.listBox.findItems(word, Qt.MatchExactly):
self.mainGui.makeAlert(self.tr(
"The word '{0}' is already in the word list."
- ).format(word), nwAlert.ERROR)
+ ).format(word), level=nwAlert.ERROR)
return
self.listBox.addItem(word)
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index b30357d2..9734bc45 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -1,7 +1,6 @@
"""
novelWriter – GUI Document Editor
=================================
-GUI classes for the main document editor
File History:
Created: 2018-09-29 [0.0.1] GuiDocEditor
@@ -28,12 +27,14 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import bisect
import logging
from enum import Enum
from time import time
+from typing import TYPE_CHECKING
from PyQt5.QtCore import (
Qt, QSize, QTimer, pyqtSlot, pyqtSignal, QRegExp, QRegularExpression,
@@ -44,9 +45,8 @@ from PyQt5.QtGui import (
QPalette, QTextDocument, QCursor, QPixmap
)
from PyQt5.QtWidgets import (
- qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel,
- QToolBar, QToolButton, QHBoxLayout, QGridLayout, QLineEdit, QPushButton,
- QFrame
+ QAction, qApp, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
+ QPushButton, QShortcut, QTextEdit, QToolBar, QToolButton, QWidget
)
from novelwriter import CONFIG
@@ -57,6 +57,9 @@ from novelwriter.core.index import countWords
from novelwriter.core.spellcheck import NWSpellEnchant
from novelwriter.gui.dochighlight import GuiDocHighlighter
+if TYPE_CHECKING: # pragma: no cover
+ from novelwriter.guimain import GuiMain
+
logger = logging.getLogger(__name__)
@@ -75,7 +78,7 @@ class GuiDocEditor(QTextEdit):
novelStructureChanged = pyqtSignal()
novelItemMetaChanged = pyqtSignal(str)
- def __init__(self, mainGui):
+ def __init__(self, mainGui: GuiMain):
super().__init__(parent=mainGui)
logger.debug("Create: GuiDocEditor")
@@ -357,7 +360,7 @@ class GuiDocEditor(QTextEdit):
).format(
f"{docSize/1.0e6:.2f}",
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
self.clearEditor()
return False
@@ -457,7 +460,7 @@ class GuiDocEditor(QTextEdit):
).format(
f"{docSize/1.0e6:.2f}",
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
@@ -496,20 +499,19 @@ class GuiDocEditor(QTextEdit):
if not self._nwDocument.writeDocument(docText):
saveOk = False
if self._nwDocument._currHash != self._nwDocument._prevHash:
- msgYes = self.mainGui.askQuestion(
- self.tr("File Changed on Disk"),
- self.tr(
- "This document has been changed outside of novelWriter "
- "while it was open. Overwrite the file on disk?"
- )
- )
+ msgYes = self.mainGui.askQuestion(self.tr(
+ "This document has been changed outside of novelWriter "
+ "while it was open. Overwrite the file on disk?"
+ ))
if msgYes:
saveOk = self._nwDocument.writeDocument(docText, forceWrite=True)
if not saveOk:
- self.mainGui.makeAlert([
- self.tr("Could not save document."), self._nwDocument.getError()
- ], nwAlert.ERROR)
+ self.mainGui.makeAlert(
+ self.tr("Could not save document."),
+ info=self._nwDocument.getError(),
+ level=nwAlert.ERROR
+ )
return False
@@ -726,7 +728,7 @@ class GuiDocEditor(QTextEdit):
self.mainGui.makeAlert(self.tr(
"Spell checking requires the package PyEnchant. "
"It does not appear to be installed."
- ), nwAlert.INFO)
+ ))
theMode = False
if self.spEnchant.spellLanguage is None:
@@ -868,17 +870,10 @@ class GuiDocEditor(QTextEdit):
if self._nwDocument is None:
logger.error("No document open")
return False
-
- msgBox = QMessageBox()
- msgBox.information(
- self,
- self.tr("File Location"),
- "%s
%s" % (
- self.tr("The currently open file is saved in:"),
- self._nwDocument.getFileLocation()
- ),
+ self.mainGui.makeAlert(
+ self.tr("The currently open file is saved in:"),
+ info=self._nwDocument.getFileLocation()
)
-
return
def insertText(self, theInsert):
@@ -1116,7 +1111,7 @@ class GuiDocEditor(QTextEdit):
"The maximum size of a single novelWriter document is {0} MB."
).format(
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
self.undo()
return
@@ -1675,7 +1670,7 @@ class GuiDocEditor(QTextEdit):
if not theCursor.hasSelection():
self.mainGui.makeAlert(self.tr(
"Please select some text before calling replace quotes."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
posS = theCursor.selectionStart()
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index d77797f8..de547135 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -589,7 +589,7 @@ class GuiProjectTree(QTreeWidget):
if sHandle is None or pItem is None:
self.mainGui.makeAlert(self.tr(
"Did not find anywhere to add the file or folder!"
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
# Collect some information about the selected item
@@ -600,7 +600,7 @@ class GuiProjectTree(QTreeWidget):
if self.theProject.tree.isTrash(sHandle):
self.mainGui.makeAlert(self.tr(
"Cannot add new files or folders to the Trash folder."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
# Set default label and determine if new item is to be added
@@ -838,7 +838,7 @@ class GuiProjectTree(QTreeWidget):
if trashHandle is None:
self.mainGui.makeAlert(self.tr(
"There is currently no Trash folder in this project."
- ), nwAlert.INFO)
+ ))
return False
theTrash = self.getTreeFromHandle(trashHandle)
@@ -849,11 +849,10 @@ class GuiProjectTree(QTreeWidget):
if nTrash == 0:
self.mainGui.makeAlert(self.tr(
"The Trash folder is already empty."
- ), nwAlert.INFO)
+ ))
return False
msgYes = self.mainGui.askQuestion(
- self.tr("Empty Trash"),
self.tr("Permanently delete {0} file(s) from Trash?").format(nTrash)
)
if not msgYes:
@@ -900,7 +899,6 @@ class GuiProjectTree(QTreeWidget):
if askFirst:
msgYes = self.mainGui.askQuestion(
- self.tr("Delete"),
self.tr("Move '{0}' to Trash?").format(nwItemS.itemName),
)
if not msgYes:
@@ -937,7 +935,7 @@ class GuiProjectTree(QTreeWidget):
if trItemS.childCount() > 0:
self.mainGui.makeAlert(self.tr(
"Root folders can only be deleted when they are empty."
- ), nwAlert.ERROR)
+ ), level=nwAlert.ERROR)
return False
logger.debug("Permanently deleting root folder '%s'", tHandle)
@@ -956,7 +954,6 @@ class GuiProjectTree(QTreeWidget):
else:
if askFirst:
msgYes = self.mainGui.askQuestion(
- self.tr("Delete"),
self.tr("Permanently delete '{0}'?").format(nwItemS.itemName)
)
if not msgYes:
@@ -1525,13 +1522,10 @@ class GuiProjectTree(QTreeWidget):
"""Convert a folder to a note or document."""
tItem = self.theProject.tree[tHandle]
if tItem is not None and tItem.isFolderType():
- msgYes = self.mainGui.askQuestion(
- self.tr("Convert Folder"),
- self.tr(
- "Do you want to convert the folder to a {0}? "
- "This action cannot be reversed."
- ).format(trConst(nwLabels.LAYOUT_NAME[itemLayout]))
- )
+ msgYes = self.mainGui.askQuestion(self.tr(
+ "Do you want to convert the folder to a {0}? "
+ "This action cannot be reversed."
+ ).format(trConst(nwLabels.LAYOUT_NAME[itemLayout])))
if msgYes and itemLayout == nwItemLayout.DOCUMENT and tItem.documentAllowed():
tItem.setType(nwItemType.FILE)
tItem.setLayout(nwItemLayout.DOCUMENT)
@@ -1570,9 +1564,7 @@ class GuiProjectTree(QTreeWidget):
mrgData = dlgMerge.getData()
mrgList = mrgData.get("finalItems", [])
if not mrgList:
- self.mainGui.makeAlert([
- self.tr("No documents selected for merging.")
- ], nwAlert.INFO)
+ self.mainGui.makeAlert(self.tr("No documents selected for merging."))
return False
# Save the open document first, in case it's part of merge
@@ -1595,9 +1587,10 @@ class GuiProjectTree(QTreeWidget):
docMerger.appendText(sHandle, True, mLabel)
if not docMerger.writeTargetDoc():
- self.mainGui.makeAlert([
- self.tr("Could not write document content."), docMerger.getError()
- ], nwAlert.ERROR)
+ self.mainGui.makeAlert(
+ self.tr("Could not write document content."),
+ info=docMerger.getError(), level=nwAlert.ERROR
+ )
return False
self.theProject.index.reIndexHandle(mHandle)
@@ -1658,9 +1651,10 @@ class GuiProjectTree(QTreeWidget):
self.revealNewTreeItem(dHandle, nHandle=nHandle, wordCount=True)
self._alertTreeChange(dHandle, flush=False)
if not writeOk:
- self.mainGui.makeAlert([
- self.tr("Could not write document content."), docSplit.getError()
- ], nwAlert.ERROR)
+ self.mainGui.makeAlert(
+ self.tr("Could not write document content."),
+ info=docSplit.getError(), level=nwAlert.ERROR
+ )
if splitData.get("moveToTrash", False):
self.moveItemToTrash(tHandle, askFirst=False, flush=True)
@@ -1680,13 +1674,11 @@ class GuiProjectTree(QTreeWidget):
if nItems == 0:
return False
elif nItems == 1:
- qTitle = self.tr("Duplicate Document")
- qText = self.tr("Do you want to duplicate this document?")
+ question = self.tr("Do you want to duplicate this document?")
else:
- qTitle = self.tr("Duplicate from Here")
- qText = self.tr("Do you want to duplicate this item and all child items?")
+ question = self.tr("Do you want to duplicate this item and all child items?")
- if not self.mainGui.askQuestion(qTitle, qText):
+ if not self.mainGui.askQuestion(question):
return False
docDup = DocDuplicator(self.theProject)
@@ -1698,7 +1690,7 @@ class GuiProjectTree(QTreeWidget):
dupCount += 1
if dupCount != nItems:
- self.mainGui.makeAlert(self.tr("Could not duplicate all items."), nwAlert.WARN)
+ self.mainGui.makeAlert(self.tr("Could not duplicate all items."), level=nwAlert.WARN)
self.saveTreeOrder()
@@ -1757,7 +1749,7 @@ class GuiProjectTree(QTreeWidget):
else:
self.mainGui.makeAlert(self.tr(
"There is nowhere to add item with name '{0}'."
- ).format(nwItem.itemName), nwAlert.ERROR)
+ ).format(nwItem.itemName), level=nwAlert.ERROR)
return None
byIndex = -1
diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py
index 6f1b0d16..81df33c2 100644
--- a/novelwriter/tools/manusbuild.py
+++ b/novelwriter/tools/manusbuild.py
@@ -310,7 +310,7 @@ class GuiManuscriptBuild(QDialog):
self.buildProgress.setValue(0)
bPath = Path(self.buildPath.text())
if not bPath.is_dir():
- self.mainGui.makeAlert(self.tr("Output folder does not exist."), nwAlert.ERROR)
+ self.mainGui.makeAlert(self.tr("Output folder does not exist."), level=nwAlert.ERROR)
return False
bExt = nwLabels.BUILD_EXT[bFormat]
@@ -318,7 +318,6 @@ class GuiManuscriptBuild(QDialog):
if buildPath.exists():
if not self.mainGui.askQuestion(
- self.tr("File Exists"),
self.tr("The file already exists. Do you want to overwrite it?")
):
return False
diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py
index 8292ceb3..e767dced 100644
--- a/novelwriter/tools/manuscript.py
+++ b/novelwriter/tools/manuscript.py
@@ -270,11 +270,7 @@ class GuiManuscript(QDialog):
"""Delete the currently selected build settings entry."""
build = self._getSelectedBuild()
if build is not None:
- proceed = self.mainGui.askQuestion(
- self.tr("Delete Build"),
- self.tr("Delete build '{0}'?".format(build.name))
- )
- if proceed:
+ if self.mainGui.askQuestion(self.tr("Delete build '{0}'?".format(build.name))):
self._builds.removeBuild(build.buildID)
self._updateBuildsList()
return
diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py
index a3f22d2f..ed2c78b8 100644
--- a/novelwriter/tools/manussettings.py
+++ b/novelwriter/tools/manussettings.py
@@ -248,11 +248,7 @@ class GuiBuildSettings(QDialog):
it's ok to reject them.
"""
if self._build.changed:
- doSave = self.mainGui.askQuestion(
- self.tr("Build Settings"),
- self.tr("Do you want to save your changes?")
- )
- if doSave:
+ if self.mainGui.askQuestion(self.tr("Do you want to save your changes?")):
self._emitBuildData()
self._build.resetChangedState()
return
diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py
index c2d1b892..c3b0b1b1 100644
--- a/novelwriter/tools/writingstats.py
+++ b/novelwriter/tools/writingstats.py
@@ -415,13 +415,15 @@ class GuiWritingStats(QDialog):
# Report to user
if wSuccess:
- self.mainGui.makeAlert([
- self.tr("{0} file successfully written to:").format(textFmt), savePath
- ], nwAlert.INFO)
+ self.mainGui.makeAlert(
+ self.tr("{0} file successfully written to:").format(textFmt),
+ info=savePath
+ )
else:
- self.mainGui.makeAlert([
- self.tr("Failed to write {0} file.").format(textFmt), errMsg
- ], nwAlert.ERROR)
+ self.mainGui.makeAlert(
+ self.tr("Failed to write {0} file.").format(textFmt),
+ info=errMsg, level=nwAlert.ERROR
+ )
return wSuccess
diff --git a/tests/conftest.py b/tests/conftest.py
index 744fdff5..42d0afb4 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -145,10 +145,8 @@ def mockGUI():
@pytest.fixture(scope="function")
def nwGUI(qtbot, monkeypatch, functionFixture):
"""Create an instance of the novelWriter GUI."""
- monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Ok)
- monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
- monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Ok)
- monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "exec_", lambda *a: None)
+ monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.Yes)
nwGUI = main(["--testmode", f"--config={_TMP_CONF}", f"--data={_TMP_CONF}"])
qtbot.addWidget(nwGUI)
diff --git a/tests/mocked.py b/tests/mocked.py
index 57381b0a..b9a64fb0 100644
--- a/tests/mocked.py
+++ b/tests/mocked.py
@@ -39,22 +39,22 @@ class MockGuiMain(QObject):
# Test Variables
self.askResponse = True
self.lastAlert = ""
- self.lastQuestion = ("", "")
+ self.lastQuestion = ""
return
def postLaunchTasks(self, cmdOpen):
return
- def makeAlert(self, message, level=0, exception=None):
- assert isinstance(message, str) or isinstance(message, list)
- print("%s: %s" % (str(level), message))
- self.lastAlert = str(message)
+ def makeAlert(self, text, info="", detals="", level=0, exception=None):
+ assert isinstance(text, str)
+ print("%s: %s" % (str(level), text))
+ self.lastAlert = str(text)
return
- def askQuestion(self, title, qustion):
- print("Question: %s" % qustion)
- self.lastQuestion = (title, qustion)
+ def askQuestion(self, text, info="", details="", level=3):
+ print("Question: %s" % text)
+ self.lastQuestion = text
return self.askResponse
def setStatus(self, theMessage):
diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py
index 410d2e97..598f861e 100644
--- a/tests/test_core/test_core_project.py
+++ b/tests/test_core/test_core_project.py
@@ -224,7 +224,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
mockGUI.askResponse = False
assert theProject.openProject(fncPath) is False
- assert "The file format of your project is about to be" in mockGUI.lastQuestion[1]
+ assert "The file format of your project is about to be" in mockGUI.lastQuestion
mockGUI.askResponse = True
# Won't open project from newer version
@@ -232,7 +232,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: 0x99999999))
mockGUI.askResponse = False
assert theProject.openProject(fncPath) is False
- assert "This project was saved by a newer version" in mockGUI.lastQuestion[1]
+ assert "This project was saved by a newer version" in mockGUI.lastQuestion
mockGUI.askResponse = True
# Fail checking items should still pass
@@ -249,7 +249,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
mockGUI.askResponse = True
theProject.index._indexBroken = True
assert theProject.openProject(fncPath) is True
- assert "The file format of your project is about to be" in mockGUI.lastQuestion[1]
+ assert "The file format of your project is about to be" in mockGUI.lastQuestion
assert theProject.index._indexBroken is False
theProject.closeProject()
diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py
index 17dc381f..5ab8ea25 100644
--- a/tests/test_gui/test_gui_guimain.py
+++ b/tests/test_gui/test_gui_guimain.py
@@ -114,7 +114,7 @@ def testGuiMain_NewProject(monkeypatch, nwGUI, projPath):
# Close project
with monkeypatch.context() as mp:
nwGUI.hasProject = True
- mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
+ mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
assert nwGUI.newProject(projData={"projPath": projPath}) is False
# No project path
diff --git a/tests/test_gui/test_gui_i18n.py b/tests/test_gui/test_gui_i18n.py
index 8c9cb849..a53ef837 100644
--- a/tests/test_gui/test_gui_i18n.py
+++ b/tests/test_gui/test_gui_i18n.py
@@ -34,12 +34,9 @@ LANG_DATA = CONFIG.listLanguages(CONFIG.LANG_NW)
@pytest.mark.skipif(not LANG_DATA, reason="No i18n Data")
@pytest.mark.parametrize("language", [a for a, b in LANG_DATA])
def testI18n_Localisation(qtbot, monkeypatch, language, fncPath):
- """test loading the gui with a specific language.
- """
- monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Ok)
- monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
- monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Ok)
- monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ """Test loading the gui with a specific language."""
+ monkeypatch.setattr(QMessageBox, "exec_", lambda *a: None)
+ monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.Yes)
# Set the test langauge
CONFIG.guiLocale = language
diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py
index 367ba3c7..c3cf85f7 100644
--- a/tests/test_gui/test_gui_mainmenu.py
+++ b/tests/test_gui/test_gui_mainmenu.py
@@ -642,7 +642,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
# The document isn't empty, so the message box should pop
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "question", lambda *a, **k: QMessageBox.No)
+ mp.setattr(QMessageBox, "result", lambda *a, **k: QMessageBox.No)
assert not nwGUI.importDocument()
assert nwGUI.docEditor.getText() == "Bar"
@@ -655,16 +655,16 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
theMessage = ""
- def recordMsg(*args):
+ def recordMsg(*args, **kwargs):
nonlocal theMessage
- theMessage = args[3]
+ theMessage = "%s|%s" % (args[0], kwargs["info"])
return None
assert not theMessage
- monkeypatch.setattr(QMessageBox, "information", recordMsg)
+ monkeypatch.setattr(nwGUI, "makeAlert", recordMsg)
nwGUI.mainMenu.aFileDetails.activate(QAction.Trigger)
- theBits = theMessage.split("
")
+ theBits = theMessage.split("|")
assert len(theBits) == 2
assert theBits[0] == "The currently open file is saved in:"
assert theBits[1] == str(projPath / "content" / "000000000000f.nwd")
diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py
index 45e0ac04..bd645619 100644
--- a/tests/test_gui/test_gui_projtree.py
+++ b/tests/test_gui/test_gui_projtree.py
@@ -397,7 +397,7 @@ def testGuiProjTree_MoveItemToTrash(qtbot, caplog, monkeypatch, nwGUI, projPath,
# User cancels action
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
+ mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
assert projTree.moveItemToTrash(C.hTitlePage) is False
assert theProject.tree.isTrash(C.hTitlePage) is False
@@ -444,7 +444,7 @@ def testGuiProjTree_PermanentlyDeleteItem(qtbot, caplog, monkeypatch, nwGUI, pro
# User cancels action
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
+ mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
assert projTree.permDeleteItem(C.hTitlePage) is False
assert C.hTitlePage in theProject.tree
@@ -496,7 +496,7 @@ def testGuiProjTree_EmptyTrash(qtbot, caplog, monkeypatch, nwGUI, projPath, mock
# User cancels
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
+ mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
assert projTree.emptyTrash() is False
assert C.hTitlePage in theProject.tree
assert C.hChapterDir in theProject.tree
@@ -617,7 +617,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# Click no on the dialog
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
+ mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
projTree._covertFolderToFile(hNewFolderOne, nwItemLayout.DOCUMENT)
assert nwGUI.theProject.tree[hNewFolderOne].isFolderType()
@@ -864,7 +864,7 @@ def testGuiProjTree_Duplicate(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mock
# Duplicate title page, but select no
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
+ mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
assert projTree._duplicateFromHandle(C.hTitlePage) is False
assert len(nwGUI.theProject.tree) == 8
diff --git a/tests/test_tools/test_tools_manusbuild.py b/tests/test_tools/test_tools_manusbuild.py
index d5105685..7e78f073 100644
--- a/tests/test_tools/test_tools_manusbuild.py
+++ b/tests/test_tools/test_tools_manusbuild.py
@@ -129,7 +129,7 @@ def testManuscriptBuild_Main(
manus.buildPath.setText(str(fncPath))
manus.buildName.setText("TestBuild")
with monkeypatch.context() as mp:
- mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
+ mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
assert manus._runBuild() is False
# Finish
From 7756368113e764db61f33d80bf88625a0eb1546d Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 7 Aug 2023 20:25:51 +0200
Subject: [PATCH 05/10] Fix some minor issues with new alert code
---
novelwriter/guimain.py | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 67e674e1..3ffd0768 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -137,6 +137,7 @@ class GuiMain(QMainWindow):
# =============
# Sizes
+ iPx = self.mainTheme.fontPixelSize
mPx = CONFIG.pxInt(4)
hWd = CONFIG.pxInt(4)
@@ -305,6 +306,15 @@ class GuiMain(QMainWindow):
# Forward Functions
self.setStatus = self.mainStatus.setStatus
+ # Cache Alert Pixmaps
+ pxSize = (2*iPx, 2*iPx)
+ self.alertPix: dict[nwAlert, QPixmap] = {
+ nwAlert.INFO: self.mainTheme.getPixmap("alert_info", pxSize),
+ nwAlert.WARN: self.mainTheme.getPixmap("alert_warn", pxSize),
+ nwAlert.ERROR: self.mainTheme.getPixmap("alert_error", pxSize),
+ nwAlert.ASK: self.mainTheme.getPixmap("alert_question", pxSize),
+ }
+
# Check that config loaded fine
self.reportConfErr()
@@ -317,16 +327,6 @@ class GuiMain(QMainWindow):
# Handle Windows Mode
self.showNormal()
- # Cache Icons
-
- aPx = CONFIG.pxInt(48)
- self.alertPix: dict[nwAlert, QPixmap] = {
- nwAlert.INFO: self.mainTheme.getPixmap("alert_info", (aPx, aPx)),
- nwAlert.WARN: self.mainTheme.getPixmap("alert_warn", (aPx, aPx)),
- nwAlert.ERROR: self.mainTheme.getPixmap("alert_error", (aPx, aPx)),
- nwAlert.ASK: self.mainTheme.getPixmap("alert_question", (aPx, aPx)),
- }
-
logger.debug("Ready: GUI")
if __hexversion__[-2] == "a" and logger.getEffectiveLevel() > logging.DEBUG:
@@ -1109,7 +1109,7 @@ class GuiMain(QMainWindow):
msgBox.setText(text)
msgBox.setInformativeText(info)
msgBox.setDetailedText(details)
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
+ msgBox.setStandardButtons(QMessageBox.Ok)
msgBox.setIconPixmap(self.alertPix[level])
msgBox.adjustSize()
msgBox.exec_()
From 470017cdea6dc7be15ff9443b0c5d8a395f2ca21 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 7 Aug 2023 20:45:25 +0200
Subject: [PATCH 06/10] Remove the global APP singleton again
---
novelwriter/__init__.py | 5 +---
novelwriter/config.py | 63 -----------------------------------------
novelwriter/guimain.py | 4 +--
3 files changed, 2 insertions(+), 70 deletions(-)
diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py
index d83415f7..bb42ba17 100644
--- a/novelwriter/__init__.py
+++ b/novelwriter/__init__.py
@@ -30,7 +30,7 @@ import logging
from PyQt5.QtWidgets import QApplication, QErrorMessage
from novelwriter.error import exceptionHandler, logException
-from novelwriter.config import Config, NWApp
+from novelwriter.config import Config
##
# Version Scheme
@@ -74,7 +74,6 @@ logger = logging.getLogger(__name__)
# Create the global singleton instances
CONFIG = Config()
-APP = NWApp()
def main(sysArgs: list | None = None):
@@ -228,7 +227,6 @@ def main(sysArgs: list | None = None):
from novelwriter.guimain import GuiMain
if testMode:
nwGUI = GuiMain()
- APP.setGUI(nwGUI)
return nwGUI
else:
@@ -248,7 +246,6 @@ def main(sysArgs: list | None = None):
# Launch main GUI
nwGUI = GuiMain()
- APP.setGUI(nwGUI)
nwGUI.postLaunchTasks(cmdOpen)
sys.exit(nwApp.exec_())
diff --git a/novelwriter/config.py b/novelwriter/config.py
index 7bbf125e..f7a94c1b 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -29,7 +29,6 @@ import json
import logging
from time import time
-from typing import TYPE_CHECKING
from pathlib import Path
from PyQt5.QtGui import QFontDatabase
@@ -42,11 +41,6 @@ from novelwriter.error import logException, formatException
from novelwriter.common import checkPath, formatTimeStamp, NWConfigParser
from novelwriter.constants import nwFiles, nwUnicode
-if TYPE_CHECKING: # pragma: no cover
- from novelwriter.guimain import GuiMain
- from novelwriter.gui.theme import GuiTheme
- from novelwriter.core.project import NWProject
-
logger = logging.getLogger(__name__)
@@ -798,63 +792,6 @@ class Config:
# END Class Config
-class NWApp:
- """Singleton: Global Pointers
-
- This class holds pointers to the core singletons:
- * The GuiMain instance
- * The GuiTheme instance
- * The NWProject instance
- """
-
- def __init__(self) -> None:
- self._gui: GuiMain | None = None
- self._theme: GuiTheme | None = None
- self._project: NWProject | None = None
- return
-
- @property
- def gui(self) -> GuiMain:
- """The main gui instance."""
- if self._gui is None:
- raise Exception("GLOBAL not fully initialised")
- return self._gui
-
- @property
- def theme(self) -> GuiTheme:
- """The main gui theme instance."""
- if self._theme is None:
- raise Exception("GLOBAL not fully initialised")
- return self._theme
-
- @property
- def project(self) -> NWProject:
- """The main project instance."""
- if self._project is None:
- raise Exception("GLOBAL not fully initialised")
- return self._project
-
- def setGUI(self, gui: GuiMain) -> None:
- """Set the GUI instance. Can only be set once."""
- if self._gui is None:
- self._gui = gui
- return
-
- def setTheme(self, theme: GuiTheme) -> None:
- """Set the theme instance. Can only be set once."""
- if self._theme is None:
- self._theme = theme
- return
-
- def setProject(self, project: NWProject) -> None:
- """Set the project instance. Can only be set once."""
- if self._project is None:
- self._project = project
- return
-
-# END Class NWApp
-
-
class RecentProjects:
def __init__(self, config):
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 3ffd0768..6076d120 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
QStackedWidget, QVBoxLayout, QWidget
)
-from novelwriter import CONFIG, APP, __hexversion__
+from novelwriter import CONFIG, __hexversion__
from novelwriter.gui.theme import GuiTheme
from novelwriter.gui.sidebar import GuiSideBar
from novelwriter.gui.outline import GuiOutlineView
@@ -115,8 +115,6 @@ class GuiMain(QMainWindow):
# Core Classes
self.mainTheme = GuiTheme()
self.theProject = NWProject(self)
- APP.setTheme(self.mainTheme)
- APP.setProject(self.theProject)
# Core Settings
self.hasProject = False
From 7cb77e8524d4ab11a2cbd3fa4708c4da412cd6f2 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 7 Aug 2023 20:49:04 +0200
Subject: [PATCH 07/10] Update comment
---
novelwriter/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py
index bb42ba17..242c7275 100644
--- a/novelwriter/__init__.py
+++ b/novelwriter/__init__.py
@@ -72,7 +72,7 @@ logger = logging.getLogger(__name__)
# Main Program
##
-# Create the global singleton instances
+# Global config singleton
CONFIG = Config()
From abfe7ee7ff6fee9c9e2e8b470c5830069d78ccbc Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 7 Aug 2023 20:57:33 +0200
Subject: [PATCH 08/10] Clean up a few minor things
---
novelwriter/config.py | 1 -
novelwriter/constants.py | 1 -
2 files changed, 2 deletions(-)
diff --git a/novelwriter/config.py b/novelwriter/config.py
index f7a94c1b..6bb94e52 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -4,7 +4,6 @@ novelWriter – Config Class
File History:
Created: 2018-09-22 [0.0.1] Config
-Created: 2023-08-07 [2.1b2] NWGlobal
This file is a part of novelWriter
Copyright 2018–2023, Veronica Berglyd Olsen
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index c3abf581..f74ce319 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -23,7 +23,6 @@ along with this program. If not, see .
"""
from __future__ import annotations
-
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
from novelwriter.enum import nwAlert, nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
From de66f1e21b12e2e367d61fe7b939c1a6b7758268 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 8 Aug 2023 09:33:42 +0200
Subject: [PATCH 09/10] Switch out some alert icons
---
novelwriter/assets/icons/typicons_dark/icons.conf | 4 ++--
.../assets/icons/typicons_dark/typ_directions-full.svg | 4 ++++
.../assets/icons/typicons_dark/typ_info-large-full.svg | 4 ----
novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg | 2 +-
novelwriter/assets/icons/typicons_dark/typ_warning-full.svg | 2 +-
novelwriter/assets/icons/typicons_light/icons.conf | 4 ++--
.../assets/icons/typicons_light/typ_directions-full.svg | 4 ++++
.../assets/icons/typicons_light/typ_info-large-full.svg | 4 ----
.../assets/icons/typicons_light/typ_lightbulb-full.svg | 2 +-
novelwriter/assets/icons/typicons_light/typ_warning-full.svg | 2 +-
10 files changed, 16 insertions(+), 16 deletions(-)
create mode 100644 novelwriter/assets/icons/typicons_dark/typ_directions-full.svg
delete mode 100644 novelwriter/assets/icons/typicons_dark/typ_info-large-full.svg
create mode 100644 novelwriter/assets/icons/typicons_light/typ_directions-full.svg
delete mode 100644 novelwriter/assets/icons/typicons_light/typ_info-large-full.svg
diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf
index 32a523f7..d0b69572 100644
--- a/novelwriter/assets/icons/typicons_dark/icons.conf
+++ b/novelwriter/assets/icons/typicons_dark/icons.conf
@@ -18,8 +18,8 @@ licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
[Map]
add = typ_plus.svg
alert_error = typ_delete-full.svg
-alert_info = typ_info-large-full.svg
-alert_question = typ_lightbulb-full.svg
+alert_info = typ_lightbulb-full.svg
+alert_question = typ_directions-full.svg
alert_warn = typ_warning-full.svg
backward = typ_chevron-left.svg
bookmark = typ_bookmark.svg
diff --git a/novelwriter/assets/icons/typicons_dark/typ_directions-full.svg b/novelwriter/assets/icons/typicons_dark/typ_directions-full.svg
new file mode 100644
index 00000000..0075285d
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/typ_directions-full.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/typ_info-large-full.svg b/novelwriter/assets/icons/typicons_dark/typ_info-large-full.svg
deleted file mode 100644
index c9a13390..00000000
--- a/novelwriter/assets/icons/typicons_dark/typ_info-large-full.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg b/novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg
index 7397b602..569fc5e2 100644
--- a/novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg
+++ b/novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg
@@ -1,4 +1,4 @@
diff --git a/novelwriter/assets/icons/typicons_dark/typ_warning-full.svg b/novelwriter/assets/icons/typicons_dark/typ_warning-full.svg
index e6022e06..cdb68b1e 100644
--- a/novelwriter/assets/icons/typicons_dark/typ_warning-full.svg
+++ b/novelwriter/assets/icons/typicons_dark/typ_warning-full.svg
@@ -1,4 +1,4 @@
diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf
index 4a2ce078..22fd9456 100644
--- a/novelwriter/assets/icons/typicons_light/icons.conf
+++ b/novelwriter/assets/icons/typicons_light/icons.conf
@@ -18,8 +18,8 @@ licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
[Map]
add = typ_plus.svg
alert_error = typ_delete-full.svg
-alert_info = typ_info-large-full.svg
-alert_question = typ_lightbulb-full.svg
+alert_info = typ_lightbulb-full.svg
+alert_question = typ_directions-full.svg
alert_warn = typ_warning-full.svg
backward = typ_chevron-left.svg
bookmark = typ_bookmark.svg
diff --git a/novelwriter/assets/icons/typicons_light/typ_directions-full.svg b/novelwriter/assets/icons/typicons_light/typ_directions-full.svg
new file mode 100644
index 00000000..230a2774
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/typ_directions-full.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/typ_info-large-full.svg b/novelwriter/assets/icons/typicons_light/typ_info-large-full.svg
deleted file mode 100644
index 80a43508..00000000
--- a/novelwriter/assets/icons/typicons_light/typ_info-large-full.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg b/novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg
index daf4254c..46b3d88a 100644
--- a/novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg
+++ b/novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg
@@ -1,4 +1,4 @@
diff --git a/novelwriter/assets/icons/typicons_light/typ_warning-full.svg b/novelwriter/assets/icons/typicons_light/typ_warning-full.svg
index d6a3b52c..3310b350 100644
--- a/novelwriter/assets/icons/typicons_light/typ_warning-full.svg
+++ b/novelwriter/assets/icons/typicons_light/typ_warning-full.svg
@@ -1,4 +1,4 @@
From 431bc8d40143690f4d7fea29ec8fedbeb377e7d3 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 8 Aug 2023 10:39:10 +0200
Subject: [PATCH 10/10] Clarify closing dialog on build settings
---
novelwriter/tools/manussettings.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py
index ed2c78b8..fb5eeba0 100644
--- a/novelwriter/tools/manussettings.py
+++ b/novelwriter/tools/manussettings.py
@@ -244,11 +244,14 @@ class GuiBuildSettings(QDialog):
##
def _askToSaveBuild(self) -> None:
- """Check if there are unsaved changes, and if there are, ask if
- it's ok to reject them.
+ """Check if there are unsaved changes, and if there are, ask
+ whether the user wants to save them.
"""
if self._build.changed:
- if self.mainGui.askQuestion(self.tr("Do you want to save your changes?")):
+ response = self.mainGui.askQuestion(self.tr(
+ "Do you want to save your changes to '{0}'?".format(self._build.name)
+ ))
+ if response:
self._emitBuildData()
self._build.resetChangedState()
return