diff --git a/novelwriter/shared.py b/novelwriter/shared.py
index 9bad600a..5383765a 100644
--- a/novelwriter/shared.py
+++ b/novelwriter/shared.py
@@ -45,7 +45,7 @@ logger = logging.getLogger(__name__)
class SharedData(QObject):
__slots__ = (
- "_gui", "_theme", "_project", "_spelling", "_lockedBy", "_alert",
+ "_gui", "_theme", "_project", "_spelling", "_lockedBy", "_lastAlert",
"_idleTime", "_idleRefTime",
)
@@ -68,7 +68,7 @@ class SharedData(QObject):
# Settings
self._lockedBy = None
- self._alert = None
+ self._lastAlert = ""
self._idleTime = 0.0
self._idleRefTime = time()
@@ -122,9 +122,9 @@ class SharedData(QObject):
return self._idleTime
@property
- def alert(self) -> _GuiAlert | None:
- """Return a pointer to the last alert box."""
- return self._alert
+ def lastAlert(self) -> str:
+ """Return the last alert message."""
+ return self._lastAlert
##
# Methods
@@ -238,44 +238,53 @@ class SharedData(QObject):
def info(self, text: str, info: str = "", details: str = "", log: bool = True) -> None:
"""Open an information alert box."""
- self._alert = _GuiAlert(self.mainGui, self.theme)
- self._alert.setMessage(text, info, details)
- self._alert.setAlertType(_GuiAlert.INFO, False)
+ alert = _GuiAlert(self.mainGui, self.theme)
+ alert.setMessage(text, info, details)
+ alert.setAlertType(_GuiAlert.INFO, False)
+ self._lastAlert = alert.logMessage
if log:
- logger.info(self._alert.logMessage, stacklevel=2)
- self._alert.exec_()
+ logger.info(self._lastAlert, stacklevel=2)
+ alert.exec_()
+ alert.deleteLater()
return
def warn(self, text: str, info: str = "", details: str = "", log: bool = True) -> None:
"""Open a warning alert box."""
- self._alert = _GuiAlert(self.mainGui, self.theme)
- self._alert.setMessage(text, info, details)
- self._alert.setAlertType(_GuiAlert.WARN, False)
+ alert = _GuiAlert(self.mainGui, self.theme)
+ alert.setMessage(text, info, details)
+ alert.setAlertType(_GuiAlert.WARN, False)
+ self._lastAlert = alert.logMessage
if log:
- logger.warning(self._alert.logMessage, stacklevel=2)
- self._alert.exec_()
+ logger.warning(self._lastAlert, stacklevel=2)
+ alert.exec_()
+ alert.deleteLater()
return
def error(self, text: str, info: str = "", details: str = "", log: bool = True,
exc: Exception | None = None) -> None:
"""Open an error alert box."""
- self._alert = _GuiAlert(self.mainGui, self.theme)
- self._alert.setMessage(text, info, details)
- self._alert.setAlertType(_GuiAlert.ERROR, False)
+ alert = _GuiAlert(self.mainGui, self.theme)
+ alert.setMessage(text, info, details)
+ alert.setAlertType(_GuiAlert.ERROR, False)
if exc:
- self._alert.setException(exc)
+ alert.setException(exc)
+ self._lastAlert = alert.logMessage
if log:
- logger.error(self._alert.logMessage, stacklevel=2)
- self._alert.exec_()
+ logger.error(self._lastAlert, stacklevel=2)
+ alert.exec_()
+ alert.deleteLater()
return
def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
"""Open a question box."""
- self._alert = _GuiAlert(self.mainGui, self.theme)
- self._alert.setMessage(text, info, details)
- self._alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
- self._alert.exec_()
- return self._alert.result() == QMessageBox.Yes
+ alert = _GuiAlert(self.mainGui, self.theme)
+ alert.setMessage(text, info, details)
+ alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
+ self._lastAlert = alert.logMessage
+ alert.exec_()
+ isYes = alert.result() == QMessageBox.StandardButton.Yes
+ alert.deleteLater()
+ return isYes
##
# Internal Functions
@@ -312,6 +321,11 @@ class _GuiAlert(QMessageBox):
super().__init__(parent=parent)
self._theme = theme
self._message = ""
+ logger.debug("Ready: _GuiAlert")
+ return
+
+ def __del__(self) -> None: # pragma: no cover
+ logger.debug("Delete: _GuiAlert")
return
@property
diff --git a/tests/test_base/test_base_shared.py b/tests/test_base/test_base_shared.py
index 4e3de75a..0f105284 100644
--- a/tests/test_base/test_base_shared.py
+++ b/tests/test_base/test_base_shared.py
@@ -22,13 +22,13 @@ from __future__ import annotations
import pytest
+from tools import buildTestProject
from mocked import MockGuiMain, MockTheme
from PyQt5.QtWidgets import QMessageBox
+from novelwriter.shared import SharedData
from novelwriter.core.project import NWProject
-from novelwriter.shared import SharedData, _GuiAlert
-from tests.tools import buildTestProject
@pytest.mark.base
@@ -62,8 +62,6 @@ def testBaseSharedData_Init():
assert shared.projectIdleTime == 0.0
assert shared.projectLock is None
- assert shared.alert is None
-
# END Test testBaseSharedData_Init
@@ -124,7 +122,7 @@ def testBaseSharedData_Projects(fncPath, caplog):
@pytest.mark.base
-def testBaseSharedData_Alerts(monkeypatch, caplog):
+def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog):
"""Test SharedData class alert helper functions."""
monkeypatch.setattr(QMessageBox, "exec_", lambda *a: None)
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.Yes)
@@ -135,56 +133,38 @@ def testBaseSharedData_Alerts(monkeypatch, caplog):
mockTheme = MockTheme()
shared.initSharedData(mockGui, mockTheme) # type: ignore
- assert shared.alert is None
+ assert shared.lastAlert == ""
# Info box
caplog.clear()
shared.info("Hello World", info="foo", details="bar")
- assert isinstance(shared.alert, _GuiAlert)
- assert shared.alert.text() == "Hello World"
- assert shared.alert.informativeText() == "foo"
- assert shared.alert.detailedText() == "bar"
+ assert shared.lastAlert == "Hello World foo bar"
assert caplog.text.strip().startswith("INFO")
assert caplog.text.strip().endswith("Hello World foo bar")
- shared._alert = None
# Warning box
caplog.clear()
shared.warn("Oops!", info="foo", details="bar")
- assert isinstance(shared.alert, _GuiAlert)
- assert shared.alert.text() == "Oops!"
- assert shared.alert.informativeText() == "foo"
- assert shared.alert.detailedText() == "bar"
+ assert shared.lastAlert == "Oops! foo bar"
assert caplog.text.strip().startswith("WARNING")
assert caplog.text.strip().endswith("Oops! foo bar")
- shared._alert = None
# Error box
caplog.clear()
shared.error("Oh noes!", info="foo", details="bar")
- assert isinstance(shared.alert, _GuiAlert)
- assert shared.alert.text() == "Oh noes!"
- assert shared.alert.informativeText() == "foo"
- assert shared.alert.detailedText() == "bar"
+ assert shared.lastAlert == "Oh noes! foo bar"
assert caplog.text.strip().startswith("ERROR")
assert caplog.text.strip().endswith("Oh noes! foo bar")
- shared._alert = None
# Error box with exception
caplog.clear()
shared.error("Oh noes!", info="foo", details="bar", exc=Exception("Boom!"))
- assert isinstance(shared.alert, _GuiAlert)
- assert shared.alert.text() == "Oh noes!"
- assert shared.alert.informativeText() == "foo
Exception: Boom!"
- assert shared.alert.detailedText() == "bar"
+ assert shared.lastAlert == "Oh noes! foo bar"
assert caplog.text.strip().startswith("ERROR")
assert caplog.text.strip().endswith("Oh noes! foo bar")
- shared._alert = None
# Question box
assert shared.question("Why?") is True
- assert isinstance(shared.alert, _GuiAlert)
- assert shared.alert.text() == "Why?"
- shared._alert = None
+ assert shared.lastAlert == "Why?"
# END Test testBaseSharedData_Alerts
diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py
index 475c9ce1..6cf3b04a 100644
--- a/tests/test_core/test_core_project.py
+++ b/tests/test_core/test_core_project.py
@@ -206,40 +206,35 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.NOT_NWX_FILE))
assert theProject.openProject(fncPath) is False
- lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
- assert "Project file does not appear" in lastMsg
+ assert "Project file does not appear" in SHARED.lastAlert
# Unknown project file version
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.UNKNOWN_VERSION))
assert theProject.openProject(fncPath) is False
- lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
- assert "Unknown or unsupported novelWriter project file" in lastMsg
+ assert "Unknown or unsupported novelWriter project file" in SHARED.lastAlert
# Other parse error
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.CANNOT_PARSE))
assert theProject.openProject(fncPath) is False
- lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
- assert "Failed to parse project xml" in lastMsg
+ assert "Failed to parse project xml" in SHARED.lastAlert
# Won't convert legacy file
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
assert theProject.openProject(fncPath) is False
- lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
- assert "The file format of your project is about to be" in lastMsg
+ assert "The file format of your project is about to be" in SHARED.lastAlert
# Won't open project from newer version
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: 0x99999999))
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
assert theProject.openProject(fncPath) is False
- lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
- assert "This project was saved by a newer version" in lastMsg
+ assert "This project was saved by a newer version" in SHARED.lastAlert
# Fail checking items should still pass
with monkeypatch.context() as mp:
@@ -254,8 +249,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
mp.setattr("novelwriter.core.index.NWIndex.loadIndex", lambda *a: True)
theProject.index._indexBroken = True
assert theProject.openProject(fncPath) is True
- lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
- assert "The file format of your project is about to be" in lastMsg
+ assert "The file format of your project is about to be" in SHARED.lastAlert
assert theProject.index._indexBroken is False
theProject.closeProject()
diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py
index 721cc130..91c1ac13 100644
--- a/tests/test_gui/test_gui_mainmenu.py
+++ b/tests/test_gui/test_gui_mainmenu.py
@@ -656,8 +656,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
nwGUI.mainMenu.aFileDetails.activate(QAction.Trigger)
path = str(projPath / "content" / "000000000000f.nwd")
- logMsg = SHARED.alert.logMessage if SHARED.alert else ""
- assert logMsg.endswith(f"File Location: {path}")
+ assert SHARED.lastAlert.endswith(f"File Location: {path}")
# qtbot.stop()
diff --git a/tests/test_tools/test_tools_dictionaries.py b/tests/test_tools/test_tools_dictionaries.py
index eee0f0f7..02cc5dc0 100644
--- a/tests/test_tools/test_tools_dictionaries.py
+++ b/tests/test_tools/test_tools_dictionaries.py
@@ -44,8 +44,7 @@ def testToolDictionaries_Main(qtbot, monkeypatch, nwGUI, fncPath):
with monkeypatch.context() as mp:
mp.setattr(enchant, "get_user_config_dir", lambda *a: causeException)
nwGUI.showDictionariesDialog()
- assert SHARED.alert is not None
- assert SHARED.alert.logMessage == "Could not initialise the dialog."
+ assert SHARED.lastAlert == "Could not initialise the dialog."
# Open the tool
nwGUI.showDictionariesDialog()
@@ -57,17 +56,16 @@ def testToolDictionaries_Main(qtbot, monkeypatch, nwGUI, fncPath):
assert nwDicts.inPath.text() == str(fncPath)
# Allow Open Dir
- SHARED._alert = None
+ SHARED._lastAlert = ""
with monkeypatch.context() as mp:
mp.setattr(QDesktopServices, "openUrl", lambda *a: None)
nwDicts._doOpenInstallLocation()
- assert SHARED.alert is None
+ assert SHARED.lastAlert == ""
# Fail Open Dir
nwDicts.inPath.setText("/foo/bar")
nwDicts._doOpenInstallLocation()
- assert SHARED.alert is not None
- assert SHARED.alert.logMessage == "Path not found."
+ assert SHARED.lastAlert == "Path not found."
nwDicts.inPath.setText(str(fncPath))
# Create Mock Dicts