Clean up alert boxes and update event filters (#1631)
This commit is contained in:
@@ -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
|
||||
# =============================================================================================== #
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+14
-4
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
+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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -21,8 +21,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
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."""
|
||||
|
||||
@@ -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()
|
||||
|
||||
+3
-3
@@ -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
|
||||
@@ -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