Remove caching of the alert object in the shared instance

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