Move make alert to separate module and add a global singleton for pointers

This commit is contained in:
Veronica Berglyd Olsen
2023-08-07 17:01:44 +02:00
parent c3b3120bc1
commit 8959af1f30
6 changed files with 169 additions and 53 deletions
+8 -5
View File
@@ -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 <https://www.gnu.org/licenses/>.
"""
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_())
+78
View File
@@ -0,0 +1,78 @@
"""
novelWriter Alert Functions
=============================
File History:
Created: 2023-08-07 [2.1b2]
This file is a part of novelWriter
Copyright 20182023, 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 <https://www.gnu.org/licenses/>.
"""
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 = "<br>".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}<br>{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
+65 -3
View File
@@ -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 20182023, 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 <https://www.gnu.org/licenses/>.
"""
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):
+7 -2
View File
@@ -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 <https://www.gnu.org/licenses/>.
"""
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"),
-1
View File
@@ -125,7 +125,6 @@ class nwAlert(Enum):
INFO = 0
WARN = 1
ERROR = 2
BUG = 3
# END Enum nwAlert
+11 -42
View File
@@ -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 = "<br>".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}<br>{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 += "<br>%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