From 1d1dae0c7ec305f5b605df8b0efdf8b90013eb48 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 29 Nov 2023 16:03:48 +0100
Subject: [PATCH 1/3] Remove caching of the alert object in the shared instance
---
novelwriter/shared.py | 66 +++++++++++++--------
tests/test_base/test_base_shared.py | 38 +++---------
tests/test_core/test_core_project.py | 18 ++----
tests/test_gui/test_gui_mainmenu.py | 3 +-
tests/test_tools/test_tools_dictionaries.py | 10 ++--
5 files changed, 60 insertions(+), 75 deletions(-)
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
From b5263150847359eb7012c33b7cd5bf8b78d4d6ef Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 29 Nov 2023 16:05:14 +0100
Subject: [PATCH 2/3] Clean up some redundant code and fix annotations
---
novelwriter/common.py | 13 ----------
novelwriter/core/project.py | 2 +-
novelwriter/core/spellcheck.py | 2 +-
novelwriter/gui/editordocument.py | 2 +-
novelwriter/gui/projtree.py | 2 +-
novelwriter/tools/manuscript.py | 40 ++++++++++++-----------------
novelwriter/tools/projwizard.py | 2 +-
tests/test_base/test_base_common.py | 23 +++++------------
8 files changed, 29 insertions(+), 57 deletions(-)
diff --git a/novelwriter/common.py b/novelwriter/common.py
index 0f61bd89..91b2fae9 100644
--- a/novelwriter/common.py
+++ b/novelwriter/common.py
@@ -38,7 +38,6 @@ from urllib.request import pathname2url
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtCore import QCoreApplication, QUrl
-from PyQt5.QtWidgets import QWidget, qApp
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
from novelwriter.error import logException
@@ -508,18 +507,6 @@ def openExternalPath(path: Path) -> bool:
return False
-# =============================================================================================== #
-# Other Functions
-# =============================================================================================== #
-
-def getGuiItem(objName: str) -> QWidget | None:
- """Returns a QtWidget based on its objectName."""
- for qWidget in qApp.topLevelWidgets():
- if qWidget.objectName() == objName:
- return qWidget
- return None
-
-
# =============================================================================================== #
# Classes
# =============================================================================================== #
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index f66f4549..1f74bb43 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -80,7 +80,7 @@ class NWProject:
return
- def __del__(self): # pragma: no cover
+ def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: NWProject")
return
diff --git a/novelwriter/core/spellcheck.py b/novelwriter/core/spellcheck.py
index e2a022e9..852fbd48 100644
--- a/novelwriter/core/spellcheck.py
+++ b/novelwriter/core/spellcheck.py
@@ -56,7 +56,7 @@ class NWSpellEnchant:
logger.debug("Ready: NWSpellEnchant")
return
- def __del__(self): # pragma: no cover
+ def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: NWSpellEnchant")
return
diff --git a/novelwriter/gui/editordocument.py b/novelwriter/gui/editordocument.py
index aa00ec7c..3f6661e0 100644
--- a/novelwriter/gui/editordocument.py
+++ b/novelwriter/gui/editordocument.py
@@ -50,7 +50,7 @@ class GuiTextDocument(QTextDocument):
return
- def __del__(self): # pragma: no cover
+ def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiTextDocument")
return
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index 607e694a..c2355707 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -1650,7 +1650,7 @@ class _TreeContextMenu(QMenu):
return
- def __del__(self): # pragma: no cover
+ def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: _TreeContextMenu")
return
diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py
index 5f7b6856..d68a74e7 100644
--- a/novelwriter/tools/manuscript.py
+++ b/novelwriter/tools/manuscript.py
@@ -66,7 +66,7 @@ class GuiManuscript(QDialog):
D_KEY = Qt.ItemDataRole.UserRole
- def __init__(self, mainGui: GuiMain):
+ def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
logger.debug("Create: GuiManuscript")
@@ -173,7 +173,7 @@ class GuiManuscript(QDialog):
self.btnBuild.clicked.connect(self._buildManuscript)
self.btnClose = QPushButton(self.tr("Close"))
- self.btnClose.clicked.connect(self._doClose)
+ self.btnClose.clicked.connect(self.close)
self.processBox = QGridLayout()
self.processBox.addWidget(self.btnPreview, 0, 0)
@@ -217,11 +217,11 @@ class GuiManuscript(QDialog):
return
- def __del__(self): # pragma: no cover
+ def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiManuscript")
return
- def loadContent(self):
+ def loadContent(self) -> None:
"""Load dialog content from project data."""
if len(self._builds) == 0:
build = BuildSettings()
@@ -255,7 +255,7 @@ class GuiManuscript(QDialog):
# Events
##
- def closeEvent(self, event: QCloseEvent):
+ def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the user closing the window so we can save GUI
settings. We also check that we don't have a build settings
dialog open.
@@ -274,7 +274,7 @@ class GuiManuscript(QDialog):
##
@pyqtSlot()
- def _createNewBuild(self):
+ def _createNewBuild(self) -> None:
"""Open the build settings dialog for a new build."""
build = BuildSettings()
build.setName(self.tr("My Manuscript"))
@@ -282,7 +282,7 @@ class GuiManuscript(QDialog):
return
@pyqtSlot()
- def _editSelectedBuild(self):
+ def _editSelectedBuild(self) -> None:
"""Edit the currently selected build settings entry."""
build = self._getSelectedBuild()
if build is not None:
@@ -299,7 +299,7 @@ class GuiManuscript(QDialog):
return
@pyqtSlot()
- def _deleteSelectedBuild(self):
+ def _deleteSelectedBuild(self) -> None:
"""Delete the currently selected build settings entry."""
build = self._getSelectedBuild()
if build is not None:
@@ -309,7 +309,7 @@ class GuiManuscript(QDialog):
return
@pyqtSlot(BuildSettings)
- def _processNewSettings(self, build: BuildSettings):
+ def _processNewSettings(self, build: BuildSettings) -> None:
"""Process new build settings from the settings dialog."""
self._builds.setBuild(build)
self._updateBuildItem(build)
@@ -319,7 +319,7 @@ class GuiManuscript(QDialog):
return
@pyqtSlot()
- def _generatePreview(self):
+ def _generatePreview(self) -> None:
"""Run the document builder on the current build settings for
the preview widget.
"""
@@ -359,7 +359,7 @@ class GuiManuscript(QDialog):
return
@pyqtSlot()
- def _buildManuscript(self):
+ def _buildManuscript(self) -> None:
"""Open the build dialog and build the manuscript."""
build = self._getSelectedBuild()
if isinstance(build, BuildSettings):
@@ -373,24 +373,18 @@ class GuiManuscript(QDialog):
return
@pyqtSlot()
- def _printDocument(self):
+ def _printDocument(self) -> None:
"""Open the print preview dialog."""
thePreview = QPrintPreviewDialog(self)
thePreview.paintRequested.connect(self.docPreview.printPreview)
thePreview.exec_()
return
- @pyqtSlot()
- def _doClose(self):
- """Forward the close button to the default close method."""
- self.close()
- return
-
##
# Internal Functions
##
- def _updatePreview(self, data: dict, build: BuildSettings):
+ def _updatePreview(self, data: dict, build: BuildSettings) -> None:
"""Update the preview widget and set relevant values."""
self.docPreview.setContent(data)
self.docPreview.setBuildName(build.name)
@@ -415,7 +409,7 @@ class GuiManuscript(QDialog):
return build
return None
- def _saveSettings(self):
+ def _saveSettings(self) -> None:
"""Save the user GUI settings."""
buildOrder = []
for i in range(self.buildList.count()):
@@ -454,7 +448,7 @@ class GuiManuscript(QDialog):
return
- def _openSettingsDialog(self, build: BuildSettings):
+ def _openSettingsDialog(self, build: BuildSettings) -> None:
"""Open the build settings dialog."""
for obj in self.mainGui.children():
# Don't open a second dialog if one exists
@@ -475,7 +469,7 @@ class GuiManuscript(QDialog):
return
- def _updateBuildsList(self):
+ def _updateBuildsList(self) -> None:
"""Update the list of available builds."""
self.buildList.clear()
for key, name in self._builds.builds():
@@ -487,7 +481,7 @@ class GuiManuscript(QDialog):
self._buildMap[key] = bItem
return
- def _updateBuildItem(self, build: BuildSettings):
+ def _updateBuildItem(self, build: BuildSettings) -> None:
"""Update the entry of a specific build item."""
bItem = self._buildMap.get(build.buildID, None)
if isinstance(bItem, QListWidgetItem):
diff --git a/novelwriter/tools/projwizard.py b/novelwriter/tools/projwizard.py
index b47da1cf..5c3b1832 100644
--- a/novelwriter/tools/projwizard.py
+++ b/novelwriter/tools/projwizard.py
@@ -80,7 +80,7 @@ class GuiProjectWizard(QWizard):
return
- def __del__(self): # pragma: no cover
+ def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiProjectWizard")
return
diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py
index 2ca1a35f..785944d6 100644
--- a/tests/test_base/test_base_common.py
+++ b/tests/test_base/test_base_common.py
@@ -21,8 +21,6 @@ along with this program. If not, see .
from __future__ import annotations
import time
-from PyQt5.QtCore import QUrl
-from PyQt5.QtGui import QDesktopServices
import pytest
from pathlib import Path
@@ -31,14 +29,16 @@ from xml.etree import ElementTree as ET
from tools import writeFile
from mocked import causeOSError
-from novelwriter.guimain import GuiMain
+from PyQt5.QtGui import QDesktopServices
+from PyQt5.QtCore import QUrl
+
from novelwriter.common import (
checkBool, checkFloat, checkHandle, checkInt, checkIntTuple, checkPath,
checkString, checkStringNone, checkUuid, formatInt, formatTime,
- formatTimeStamp, fuzzyTime, getFileSize, getGuiItem, hexToInt, isHandle,
- isItemClass, isItemLayout, isItemType, isTitleTag, jsonEncode,
- makeFileNameSafe, minmax, numberToRoman, NWConfigParser, openExternalPath,
- readTextFile, simplified, transferCase, xmlIndent, yesNo
+ formatTimeStamp, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass,
+ isItemLayout, isItemType, isTitleTag, jsonEncode, makeFileNameSafe, minmax,
+ numberToRoman, NWConfigParser, openExternalPath, readTextFile, simplified,
+ transferCase, xmlIndent, yesNo
)
@@ -655,15 +655,6 @@ def testBaseCommon_openExternalPath(monkeypatch, tstPaths):
# END Test testBaseCommon_openExternalPath
-@pytest.mark.base
-def testBaseCommon_getGuiItem(nwGUI):
- """Check the GUI item function."""
- assert getGuiItem("gibberish") is None
- assert isinstance(getGuiItem("GuiMain"), GuiMain)
-
-# END Test testBaseCommon_getGuiItem
-
-
@pytest.mark.base
def testBaseCommon_NWConfigParser(fncPath):
"""Test the NWConfigParser subclass."""
From 1118f01c91a6bf10a5617ddc80c936ed633c8dce Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 29 Nov 2023 16:05:58 +0100
Subject: [PATCH 3/3] Combine all event filters in one module, and apply the
status tip filter to the sidebar as well
---
.../{wheeleventfilter.py => eventfilters.py} | 18 ++++++++++++++----
novelwriter/gui/doceditor.py | 2 +-
novelwriter/gui/docviewer.py | 2 +-
novelwriter/gui/mainmenu.py | 17 ++++-------------
novelwriter/gui/sidebar.py | 6 ++++--
...eventfilter.py => test_ext_eventfilters.py} | 6 +++---
6 files changed, 27 insertions(+), 24 deletions(-)
rename novelwriter/extensions/{wheeleventfilter.py => eventfilters.py} (80%)
rename tests/test_ext/{test_ext_wheeleventfilter.py => test_ext_eventfilters.py} (92%)
diff --git a/novelwriter/extensions/wheeleventfilter.py b/novelwriter/extensions/eventfilters.py
similarity index 80%
rename from novelwriter/extensions/wheeleventfilter.py
rename to novelwriter/extensions/eventfilters.py
index 220fa324..9c53354e 100644
--- a/novelwriter/extensions/wheeleventfilter.py
+++ b/novelwriter/extensions/eventfilters.py
@@ -1,9 +1,10 @@
"""
-novelWriter – Custom Object: Wheel Event Filter
-===============================================
+novelWriter – Custom Objects: Event Filters
+===========================================
File History:
-Created: 2023-08-31 [2.1rc1]
+Created: 2023-08-31 [2.1rc1] WheelEventFilter
+Created: 2023-11-28 [2.2] StatusTipFilter
This file is a part of novelWriter
Copyright 2018–2023, Veronica Berglyd Olsen
@@ -23,7 +24,7 @@ along with this program. If not, see .
"""
from __future__ import annotations
-from PyQt5.QtGui import QWheelEvent
+from PyQt5.QtGui import QStatusTipEvent, QWheelEvent
from PyQt5.QtCore import QEvent, QObject
from PyQt5.QtWidgets import QWidget
@@ -63,3 +64,12 @@ class WheelEventFilter(QObject):
return False
# END Class WheelEventFilter
+
+
+class StatusTipFilter(QObject):
+
+ def eventFilter(self, obj: QObject, event: QEvent) -> bool:
+ """Filter out status tip events on menus."""
+ return True if isinstance(event, QStatusTipEvent) else super().eventFilter(obj, event)
+
+# END Class StatusTipFilter
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index 4d4ac393..8ea95769 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -62,7 +62,7 @@ from novelwriter.tools.lipsum import GuiLipsum
from novelwriter.core.document import NWDocument
from novelwriter.gui.dochighlight import GuiDocHighlighter
from novelwriter.gui.editordocument import GuiTextDocument
-from novelwriter.extensions.wheeleventfilter import WheelEventFilter
+from novelwriter.extensions.eventfilters import WheelEventFilter
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index d548f0d4..44ccb1d5 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -46,7 +46,7 @@ from novelwriter.enum import nwItemType, nwDocAction, nwDocMode
from novelwriter.error import logException
from novelwriter.constants import nwUnicode
from novelwriter.core.tohtml import ToHtml
-from novelwriter.extensions.wheeleventfilter import WheelEventFilter
+from novelwriter.extensions.eventfilters import WheelEventFilter
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py
index a601fb99..e442f9e8 100644
--- a/novelwriter/gui/mainmenu.py
+++ b/novelwriter/gui/mainmenu.py
@@ -3,8 +3,7 @@ novelWriter – GUI Main Menu
===========================
File History:
-Created: 2019-04-27 [0.0.1] GuiMainMenu
-Created: 2023-11-28 [2.2] StatusTipFilter
+Created: 2019-04-27 [0.0.1]
This file is a part of novelWriter
Copyright 2018–2023, Veronica Berglyd Olsen
@@ -29,14 +28,15 @@ import logging
from typing import TYPE_CHECKING
from pathlib import Path
-from PyQt5.QtGui import QDesktopServices, QStatusTipEvent
-from PyQt5.QtCore import QEvent, QObject, QUrl, pyqtSignal, pyqtSlot
+from PyQt5.QtGui import QDesktopServices
+from PyQt5.QtCore import QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QMenuBar, QAction
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocAction, nwDocInsert, nwWidget
from novelwriter.common import openExternalPath
from novelwriter.constants import nwConst, trConst, nwKeyWords, nwLabels, nwUnicode
+from novelwriter.extensions.eventfilters import StatusTipFilter
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
@@ -971,12 +971,3 @@ class GuiMainMenu(QMenuBar):
return
# END Class GuiMainMenu
-
-
-class StatusTipFilter(QObject):
-
- def eventFilter(self, obj: QObject, event: QEvent) -> bool:
- """Filter out status tip events."""
- return True if isinstance(event, QStatusTipEvent) else super().eventFilter(obj, event)
-
-# END Class StatusTipFilter
diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py
index 9cfbf118..b15908a2 100644
--- a/novelwriter/gui/sidebar.py
+++ b/novelwriter/gui/sidebar.py
@@ -27,12 +27,13 @@ import logging
from typing import TYPE_CHECKING
-from PyQt5.QtCore import QEvent, QPoint, Qt, QSize, pyqtSignal
from PyQt5.QtGui import QPalette
+from PyQt5.QtCore import QEvent, QPoint, Qt, QSize, pyqtSignal
from PyQt5.QtWidgets import QMenu, QToolButton, QVBoxLayout, QWidget
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwView
+from novelwriter.extensions.eventfilters import StatusTipFilter
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
@@ -54,6 +55,7 @@ class GuiSideBar(QWidget):
iPx = CONFIG.pxInt(24)
iconSize = QSize(iPx, iPx)
self.setContentsMargins(0, 0, 0, 0)
+ self.installEventFilter(StatusTipFilter(mainGui))
# Buttons
self.tbProject = QToolButton(self)
@@ -162,7 +164,7 @@ class GuiSideBar(QWidget):
class _PopRightMenu(QMenu):
- def event(self, event: QEvent):
+ def event(self, event: QEvent) -> bool:
"""Overload the show event and move the menu popup location."""
if event.type() == QEvent.Show:
parent = self.parent()
diff --git a/tests/test_ext/test_ext_wheeleventfilter.py b/tests/test_ext/test_ext_eventfilters.py
similarity index 92%
rename from tests/test_ext/test_ext_wheeleventfilter.py
rename to tests/test_ext/test_ext_eventfilters.py
index 2d348358..844d4403 100644
--- a/tests/test_ext/test_ext_wheeleventfilter.py
+++ b/tests/test_ext/test_ext_eventfilters.py
@@ -26,7 +26,7 @@ from PyQt5.QtGui import QKeyEvent, QWheelEvent
from PyQt5.QtCore import QEvent, QObject, QPoint, Qt
from PyQt5.QtWidgets import QWidget
-from novelwriter.extensions.wheeleventfilter import WheelEventFilter
+from novelwriter.extensions.eventfilters import WheelEventFilter
class MockWidget(QWidget):
@@ -42,7 +42,7 @@ class MockWidget(QWidget):
@pytest.mark.gui
-def testExtWheelEventFilter_Main():
+def testExtEventFilters_WheelEventFilter():
"""Test the WheelEventFilter class."""
obj = QObject()
widget = MockWidget()
@@ -65,4 +65,4 @@ def testExtWheelEventFilter_Main():
eFilter.eventFilter(obj, event)
assert widget.count == 1
-# END Test testExtWheelEventFilter_Main
+# END Test testExtEventFilters_WheelEventFilter