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] 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