Remove caching of the alert object in the shared instance
This commit is contained in:
+40
-26
@@ -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
|
||||
|
||||
@@ -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<br><b>Exception</b>: 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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user