Improve dialog memory handling (#1899)

This commit is contained in:
Veronica Berglyd Olsen
2024-05-28 23:19:39 +02:00
committed by GitHub
30 changed files with 202 additions and 169 deletions
+8 -13
View File
@@ -33,14 +33,14 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import cssCol, readTextFile
from novelwriter.extensions.configlayout import NColourLabel
from novelwriter.extensions.modified import NNonBlockingDialog
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.versioninfo import VersionInfoWidget
from novelwriter.types import QtAlignRightTop, QtDialogClose
logger = logging.getLogger(__name__)
class GuiAbout(NNonBlockingDialog):
class GuiAbout(NDialog):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -106,7 +106,9 @@ class GuiAbout(NNonBlockingDialog):
self.setLayout(self.outerBox)
self.setSizeGripEnabled(True)
self._setStyleSheet()
self._fillCreditsPage()
logger.debug("Ready: GuiAbout")
@@ -116,11 +118,6 @@ class GuiAbout(NNonBlockingDialog):
logger.debug("Delete: GuiAbout")
return
def populateGUI(self) -> None:
"""Populate tabs with text."""
self._fillCreditsPage()
return
##
# Events
##
@@ -128,7 +125,7 @@ class GuiAbout(NNonBlockingDialog):
def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the close event and perform cleanup."""
event.accept()
self.deleteLater()
self.softDelete()
return
##
@@ -137,16 +134,14 @@ class GuiAbout(NNonBlockingDialog):
def _fillCreditsPage(self) -> None:
"""Load the content for the Credits page."""
docPath = CONFIG.assetPath("text") / "credits_en.htm"
docText = readTextFile(docPath)
if docText:
self.txtCredits.setHtml(docText)
if html := readTextFile(CONFIG.assetPath("text") / "credits_en.htm"):
self.txtCredits.setHtml(html)
else:
self.txtCredits.setHtml("Error loading credits text ...")
return
def _setStyleSheet(self) -> None:
"""Set stylesheet for all browser tabs."""
"""Set stylesheet text document."""
baseCol = cssCol(self.palette().window().color())
self.txtCredits.setStyleSheet(
f"QTextBrowser {{border: none; background: {baseCol};}} "
+15 -15
View File
@@ -27,21 +27,21 @@ from __future__ import annotations
import logging
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import (
QAbstractItemView, QDialog, QDialogButtonBox, QGridLayout, QLabel,
QListWidget, QListWidgetItem, QVBoxLayout, QWidget
QAbstractItemView, QDialogButtonBox, QGridLayout, QLabel, QListWidget,
QListWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
from novelwriter.extensions.configlayout import NColourLabel
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtDialogCancel, QtDialogOk, QtDialogReset, QtUserRole
from novelwriter.types import QtAccepted, QtDialogCancel, QtDialogOk, QtDialogReset, QtUserRole
logger = logging.getLogger(__name__)
class GuiDocMerge(QDialog):
class GuiDocMerge(NDialog):
D_HANDLE = QtUserRole
@@ -117,7 +117,7 @@ class GuiDocMerge(QDialog):
logger.debug("Delete: GuiDocMerge")
return
def getData(self) -> dict:
def data(self) -> dict:
"""Return the user's choices."""
finalItems = []
for i in range(self.listBox.count()):
@@ -130,15 +130,15 @@ class GuiDocMerge(QDialog):
return self._data
##
# Events
##
def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the close event and perform cleanup."""
event.accept()
self.deleteLater()
return
@classmethod
def getData(cls, parent: QWidget, handle: str, items: list[str]) -> tuple[dict, bool]:
"""Pop the dialog and return the result."""
cls = GuiDocMerge(parent, handle, items)
cls.exec()
data = cls.data()
accepted = cls.result() == QtAccepted
cls.softDelete()
return data, accepted
##
# Private Slots
+15 -15
View File
@@ -27,21 +27,21 @@ from __future__ import annotations
import logging
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import (
QAbstractItemView, QComboBox, QDialog, QDialogButtonBox, QGridLayout,
QLabel, QListWidget, QListWidgetItem, QVBoxLayout, QWidget
QAbstractItemView, QComboBox, QDialogButtonBox, QGridLayout, QLabel,
QListWidget, QListWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
from novelwriter.extensions.configlayout import NColourLabel
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtDialogCancel, QtDialogOk, QtUserRole
from novelwriter.types import QtAccepted, QtDialogCancel, QtDialogOk, QtUserRole
logger = logging.getLogger(__name__)
class GuiDocSplit(QDialog):
class GuiDocSplit(NDialog):
LINE_ROLE = QtUserRole
LEVEL_ROLE = QtUserRole + 1
@@ -145,7 +145,7 @@ class GuiDocSplit(QDialog):
logger.debug("Delete: GuiDocSplit")
return
def getData(self) -> tuple[dict, list]:
def data(self) -> tuple[dict, list[str]]:
"""Return the user's choices. Also save the users options for
the next time the dialog is used.
"""
@@ -178,15 +178,15 @@ class GuiDocSplit(QDialog):
return self._data, self._text
##
# Events
##
def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the close event and perform cleanup."""
event.accept()
self.deleteLater()
return
@classmethod
def getData(cls, parent: QWidget, handle: str) -> tuple[dict, list[str], bool]:
"""Pop the dialog and return the result."""
cls = GuiDocSplit(parent, handle)
cls.exec()
data, text = cls.data()
accepted = cls.result() == QtAccepted
cls.softDelete()
return data, text, accepted
##
# Private Slots
+6 -8
View File
@@ -25,18 +25,16 @@ from __future__ import annotations
import logging
from PyQt5.QtWidgets import (
QDialog, QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QVBoxLayout,
QWidget
)
from PyQt5.QtWidgets import QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QVBoxLayout, QWidget
from novelwriter import CONFIG
from novelwriter.types import QtDialogCancel, QtDialogOk
from novelwriter.extensions.modified import NDialog
from novelwriter.types import QtAccepted, QtDialogCancel, QtDialogOk
logger = logging.getLogger(__name__)
class GuiEditLabel(QDialog):
class GuiEditLabel(NDialog):
def __init__(self, parent: QWidget, text: str = "") -> None:
super().__init__(parent=parent)
@@ -91,6 +89,6 @@ class GuiEditLabel(QDialog):
cls = GuiEditLabel(parent, text=text)
cls.exec()
label = cls.itemLabel
accepted = cls.result() == QDialog.DialogCode.Accepted
cls.deleteLater()
accepted = cls.result() == QtAccepted
cls.softDelete()
return label, accepted
+8 -13
View File
@@ -27,10 +27,10 @@ from __future__ import annotations
import logging
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QKeyEvent, QKeySequence
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import (
QAbstractButton, QApplication, QCompleter, QDialog, QDialogButtonBox,
QFileDialog, QHBoxLayout, QLineEdit, QPushButton, QVBoxLayout, QWidget
QAbstractButton, QApplication, QCompleter, QDialogButtonBox, QFileDialog,
QHBoxLayout, QLineEdit, QPushButton, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
@@ -38,7 +38,9 @@ from novelwriter.common import describeFont
from novelwriter.constants import nwConst, nwUnicode
from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.extensions.configlayout import NColourLabel, NScrollableForm
from novelwriter.extensions.modified import NComboBox, NDoubleSpinBox, NIconToolButton, NSpinBox
from novelwriter.extensions.modified import (
NComboBox, NDialog, NDoubleSpinBox, NIconToolButton, NSpinBox
)
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import (
@@ -49,7 +51,7 @@ from novelwriter.types import (
logger = logging.getLogger(__name__)
class GuiPreferences(QDialog):
class GuiPreferences(NDialog):
newPreferencesReady = pyqtSignal(bool, bool, bool, bool)
@@ -769,14 +771,7 @@ class GuiPreferences(QDialog):
event.accept()
QApplication.processEvents()
self.done(nwConst.DLG_FINISHED)
self.deleteLater()
return
def keyPressEvent(self, event: QKeyEvent) -> None:
"""Overload keyPressEvent to block enter key to save."""
if event.matches(QKeySequence.StandardKey.Cancel):
self.close()
event.ignore()
self.softDelete()
return
##
+4 -4
View File
@@ -29,7 +29,7 @@ import logging
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QColor
from PyQt5.QtWidgets import (
QAbstractItemView, QApplication, QColorDialog, QDialog, QDialogButtonBox,
QAbstractItemView, QApplication, QColorDialog, QDialogButtonBox,
QHBoxLayout, QLineEdit, QMenu, QStackedWidget, QToolButton, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget
)
@@ -40,7 +40,7 @@ from novelwriter.constants import nwLabels, trConst
from novelwriter.core.status import NWStatus, StatusEntry
from novelwriter.enum import nwStatusShape
from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
from novelwriter.extensions.modified import NComboBox, NIconToolButton
from novelwriter.extensions.modified import NComboBox, NDialog, NIconToolButton
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import (
@@ -51,7 +51,7 @@ from novelwriter.types import (
logger = logging.getLogger(__name__)
class GuiProjectSettings(QDialog):
class GuiProjectSettings(NDialog):
PAGE_SETTINGS = 0
PAGE_STATUS = 1
@@ -147,7 +147,7 @@ class GuiProjectSettings(QDialog):
"""Capture the user closing the window and save settings."""
self._saveSettings()
event.accept()
self.deleteLater()
self.softDelete()
return
##
+9 -5
View File
@@ -28,18 +28,22 @@ import logging
from PyQt5.QtCore import QSize, pyqtSlot
from PyQt5.QtGui import QFontMetrics
from PyQt5.QtWidgets import (
QDialog, QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
QListWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG
from novelwriter.constants import nwQuotes, trConst
from novelwriter.types import QtAlignCenter, QtAlignTop, QtDialogCancel, QtDialogOk, QtUserRole
from novelwriter.extensions.modified import NDialog
from novelwriter.types import (
QtAccepted, QtAlignCenter, QtAlignTop, QtDialogCancel, QtDialogOk,
QtUserRole
)
logger = logging.getLogger(__name__)
class GuiQuoteSelect(QDialog):
class GuiQuoteSelect(NDialog):
_selected = ""
@@ -126,8 +130,8 @@ class GuiQuoteSelect(QDialog):
cls = GuiQuoteSelect(parent, current=current)
cls.exec()
quote = cls._selected
accepted = cls.result() == QDialog.DialogCode.Accepted
cls.deleteLater()
accepted = cls.result() == QtAccepted
cls.softDelete()
return quote, accepted
##
+4 -4
View File
@@ -30,7 +30,7 @@ from pathlib import Path
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import (
QAbstractItemView, QApplication, QDialog, QDialogButtonBox, QFileDialog,
QAbstractItemView, QApplication, QDialogButtonBox, QFileDialog,
QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout, QWidget
)
@@ -38,13 +38,13 @@ from novelwriter import CONFIG, SHARED
from novelwriter.common import formatFileFilter
from novelwriter.core.spellcheck import UserDictionary
from novelwriter.extensions.configlayout import NColourLabel
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.extensions.modified import NDialog, NIconToolButton
from novelwriter.types import QtDialogClose, QtDialogSave
logger = logging.getLogger(__name__)
class GuiWordList(QDialog):
class GuiWordList(NDialog):
newWordListReady = pyqtSignal()
@@ -140,7 +140,7 @@ class GuiWordList(QDialog):
"""Capture the close event and perform cleanup."""
self._saveGuiSettings()
event.accept()
self.deleteLater()
self.softDelete()
return
##
+22 -3
View File
@@ -31,7 +31,7 @@ from enum import Enum
from typing import TYPE_CHECKING
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QWheelEvent
from PyQt5.QtGui import QKeyEvent, QKeySequence, QWheelEvent
from PyQt5.QtWidgets import (
QApplication, QComboBox, QDialog, QDoubleSpinBox, QSpinBox, QToolButton,
QWidget
@@ -43,7 +43,26 @@ if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
class NToolDialog(QDialog):
class NDialog(QDialog):
def softDelete(self) -> None:
"""Since calling deleteLater is sometimes not safe from Python,
as the C++ object can be deleted before the Python process is
done with the object, we instead set the dialog's parent to None
so that it gets garbage collected when it runs out of scope.
"""
self.setParent(None) # type: ignore
return
def keyPressEvent(self, event: QKeyEvent) -> None:
"""Overload keyPressEvent and forward escape to close."""
if event.matches(QKeySequence.StandardKey.Cancel):
self.close()
event.ignore()
return
class NToolDialog(NDialog):
def __init__(self, parent: GuiMain) -> None:
super().__init__(parent=parent)
@@ -62,7 +81,7 @@ class NToolDialog(QDialog):
return
class NNonBlockingDialog(QDialog):
class NNonBlockingDialog(NDialog):
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent=parent)
+16 -26
View File
@@ -34,9 +34,8 @@ from time import time
from PyQt5.QtCore import QPoint, Qt, QTimer, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
from PyQt5.QtWidgets import (
QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
QLabel, QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
QWidget
QAbstractItemView, QAction, QFrame, QHBoxLayout, QHeaderView, QLabel,
QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
@@ -1397,14 +1396,10 @@ class GuiProjectTree(QTreeWidget):
if not newFile:
itemList.remove(tHandle)
dlgMerge = GuiDocMerge(SHARED.mainGui, tHandle, itemList)
dlgMerge.exec()
if dlgMerge.result() == QDialog.DialogCode.Accepted:
mrgData = dlgMerge.getData()
mrgList = mrgData.get("finalItems", [])
if not mrgList:
data, status = GuiDocMerge.getData(SHARED.mainGui, tHandle, itemList)
if status:
items = data.get("finalItems", [])
if not items:
SHARED.info(self.tr("No documents selected for merging."))
return False
@@ -1424,7 +1419,7 @@ class GuiProjectTree(QTreeWidget):
else:
return False
for sHandle in mrgList:
for sHandle in items:
docMerger.appendText(sHandle, True, mLabel)
if not docMerger.writeTargetDoc():
@@ -1441,8 +1436,8 @@ class GuiProjectTree(QTreeWidget):
self.projView.openDocumentRequest.emit(mHandle, nwDocMode.EDIT, "", False)
self.projView.setSelectedHandle(mHandle, doScroll=True)
if mrgData.get("moveToTrash", False):
for sHandle in reversed(mrgData.get("finalItems", [])):
if data.get("moveToTrash", False):
for sHandle in reversed(data.get("finalItems", [])):
trItem = self._getTreeItem(sHandle)
if isinstance(trItem, QTreeWidgetItem) and trItem.childCount() == 0:
self.moveItemToTrash(sHandle, askFirst=False, flush=False)
@@ -1468,16 +1463,11 @@ class GuiProjectTree(QTreeWidget):
logger.error("Only valid document items can be split")
return False
dlgSplit = GuiDocSplit(SHARED.mainGui, tHandle)
dlgSplit.exec()
if dlgSplit.result() == QDialog.DialogCode.Accepted:
splitData, splitText = dlgSplit.getData()
headerList = splitData.get("headerList", [])
intoFolder = splitData.get("intoFolder", False)
docHierarchy = splitData.get("docHierarchy", False)
data, text, status = GuiDocSplit.getData(SHARED.mainGui, tHandle)
if status:
headerList = data.get("headerList", [])
intoFolder = data.get("intoFolder", False)
docHierarchy = data.get("docHierarchy", False)
docSplit = DocSplitter(SHARED.project, tHandle)
if intoFolder:
@@ -1487,7 +1477,7 @@ class GuiProjectTree(QTreeWidget):
else:
docSplit.setParentItem(tItem.itemParent)
docSplit.splitDocument(headerList, splitText)
docSplit.splitDocument(headerList, text)
for writeOk, dHandle, nHandle in docSplit.writeDocuments(docHierarchy):
SHARED.project.index.reIndexHandle(dHandle)
self.revealNewTreeItem(dHandle, nHandle=nHandle, wordCount=True)
@@ -1498,7 +1488,7 @@ class GuiProjectTree(QTreeWidget):
info=docSplit.getError()
)
if splitData.get("moveToTrash", False):
if data.get("moveToTrash", False):
self.moveItemToTrash(tHandle, askFirst=False, flush=True)
self.saveTreeOrder()
+1 -2
View File
@@ -827,8 +827,7 @@ class GuiMain(QMainWindow):
def showAboutNWDialog(self) -> None:
"""Show the novelWriter about dialog."""
dialog = GuiAbout(self)
dialog.activateDialog()
dialog.populateGUI()
dialog.exec()
return
@pyqtSlot()
-4
View File
@@ -316,7 +316,6 @@ class SharedData(QObject):
if log:
logger.info(self._lastAlert, stacklevel=2)
alert.exec()
alert.deleteLater()
return
def warn(self, text: str, info: str = "", details: str = "", log: bool = True) -> None:
@@ -328,7 +327,6 @@ class SharedData(QObject):
if log:
logger.warning(self._lastAlert, stacklevel=2)
alert.exec()
alert.deleteLater()
return
def error(self, text: str, info: str = "", details: str = "", log: bool = True,
@@ -343,7 +341,6 @@ class SharedData(QObject):
if log:
logger.error(self._lastAlert, stacklevel=2)
alert.exec()
alert.deleteLater()
return
def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
@@ -354,7 +351,6 @@ class SharedData(QObject):
self._lastAlert = alert.logMessage
alert.exec()
isYes = alert.result() == QMessageBox.StandardButton.Yes
alert.deleteLater()
return isYes
##
+1 -1
View File
@@ -172,7 +172,7 @@ class GuiDictionaries(NNonBlockingDialog):
def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the user closing the window."""
event.accept()
self.deleteLater()
self.softDelete()
return
##
+5 -4
View File
@@ -28,19 +28,20 @@ import random
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import (
QDialog, QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, QSpinBox,
QVBoxLayout, QWidget
QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, QSpinBox, QVBoxLayout,
QWidget
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import readTextFile
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtAlignLeft, QtAlignRight, QtDialogClose, QtRoleAction
logger = logging.getLogger(__name__)
class GuiLipsum(QDialog):
class GuiLipsum(NDialog):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -132,7 +133,7 @@ class GuiLipsum(QDialog):
cls = GuiLipsum(parent)
cls.exec()
text = cls.lipsumText
cls.deleteLater()
cls.softDelete()
return text
##
+4 -4
View File
@@ -30,7 +30,7 @@ from pathlib import Path
from PyQt5.QtCore import QTimer, pyqtSlot
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import (
QAbstractButton, QAbstractItemView, QDialog, QDialogButtonBox, QFileDialog,
QAbstractButton, QAbstractItemView, QDialogButtonBox, QFileDialog,
QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
QPushButton, QSplitter, QVBoxLayout, QWidget
)
@@ -42,14 +42,14 @@ from novelwriter.core.buildsettings import BuildSettings
from novelwriter.core.docbuild import NWBuildDocument
from novelwriter.core.item import NWItem
from novelwriter.enum import nwBuildFmt
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.extensions.modified import NDialog, NIconToolButton
from novelwriter.extensions.simpleprogress import NProgressSimple
from novelwriter.types import QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole
logger = logging.getLogger(__name__)
class GuiManuscriptBuild(QDialog):
class GuiManuscriptBuild(NDialog):
"""GUI Tools: Manuscript Build Dialog
This is the tool for running the build itself. It can be accessed
@@ -250,7 +250,7 @@ class GuiManuscriptBuild(QDialog):
"""
self._saveSettings()
event.accept()
self.deleteLater()
self.softDelete()
return
##
+2 -3
View File
@@ -265,7 +265,7 @@ class GuiManuscript(NToolDialog):
if isinstance(obj, GuiBuildSettings) and obj.isVisible():
obj.close()
event.accept()
self.deleteLater()
self.softDelete()
return
##
@@ -891,9 +891,8 @@ class _PreviewWidget(QTextBrowser):
document within the viewport.
"""
vBar = self.verticalScrollBar()
sW = vBar.width() if vBar.isVisible() else 0
tB = self.frameWidth()
vW = self.width() - 2*tB - sW
vW = self.width() - 2*tB - vBar.width()
vH = self.height() - 2*tB
tH = self.ageLabel.height()
pS = self.buildProgress.width()
+1 -1
View File
@@ -200,7 +200,7 @@ class GuiBuildSettings(NToolDialog):
self._askToSaveBuild()
self._saveSettings()
event.accept()
self.deleteLater()
self.softDelete()
return
##
+1 -1
View File
@@ -150,7 +150,7 @@ class GuiNovelDetails(NNonBlockingDialog):
"""Capture the user closing the window and save settings."""
self._saveSettings()
event.accept()
self.deleteLater()
self.softDelete()
return
##
+5 -5
View File
@@ -34,8 +34,8 @@ from PyQt5.QtCore import (
)
from PyQt5.QtGui import QCloseEvent, QColor, QFont, QPainter, QPaintEvent, QPen
from PyQt5.QtWidgets import (
QAction, QApplication, QDialog, QFileDialog, QFormLayout, QHBoxLayout,
QLabel, QLineEdit, QListView, QMenu, QPushButton, QScrollArea, QShortcut,
QAction, QApplication, QFileDialog, QFormLayout, QHBoxLayout, QLabel,
QLineEdit, QListView, QMenu, QPushButton, QScrollArea, QShortcut,
QStackedWidget, QStyledItemDelegate, QStyleOptionViewItem, QVBoxLayout,
QWidget
)
@@ -46,7 +46,7 @@ from novelwriter.constants import nwFiles
from novelwriter.core.coretools import ProjectBuilder
from novelwriter.enum import nwItemClass
from novelwriter.extensions.configlayout import NWrappedWidgetBox
from novelwriter.extensions.modified import NIconToolButton, NSpinBox
from novelwriter.extensions.modified import NDialog, NIconToolButton, NSpinBox
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.versioninfo import VersionInfoWidget
from novelwriter.types import QtAlignLeft, QtAlignRightTop, QtSelected
@@ -56,7 +56,7 @@ logger = logging.getLogger(__name__)
PANEL_ALPHA = 178
class GuiWelcome(QDialog):
class GuiWelcome(NDialog):
openProjectRequest = pyqtSignal(Path)
@@ -196,7 +196,7 @@ class GuiWelcome(QDialog):
"""Capture the user closing the window and save settings."""
self._saveSettings()
event.accept()
self.deleteLater()
self.softDelete()
return
##
+1 -1
View File
@@ -318,7 +318,7 @@ class GuiWritingStats(NToolDialog):
def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the user closing the window."""
event.accept()
self.deleteLater()
self.softDelete()
return
##
+4 -1
View File
@@ -25,7 +25,7 @@ from __future__ import annotations
from PyQt5.QtCore import QRegularExpression, Qt
from PyQt5.QtGui import QColor, QFont, QPainter, QTextCharFormat, QTextCursor, QTextFormat
from PyQt5.QtWidgets import QDialogButtonBox, QSizePolicy, QStyle
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QSizePolicy, QStyle
# Qt Alignment Flags
@@ -80,6 +80,9 @@ QtMouseMiddle = Qt.MouseButton.MiddleButton
# Dialog Button Box Types
QtAccepted = QDialog.DialogCode.Accepted
QtRejected = QDialog.DialogCode.Rejected
QtDialogApply = QDialogButtonBox.StandardButton.Apply
QtDialogCancel = QDialogButtonBox.StandardButton.Cancel
QtDialogClose = QDialogButtonBox.StandardButton.Close
+2 -2
View File
@@ -33,9 +33,9 @@ from novelwriter.dialogs.about import GuiAbout
@pytest.mark.gui
def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI):
"""Test the novelWriter about dialogs."""
# NW About
nwGUI.showAboutNWDialog()
monkeypatch.setattr(GuiAbout, "exec", lambda *a: None)
nwGUI.showAboutNWDialog()
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiAbout) is not None, timeout=1000)
msgAbout = SHARED.findTopLevelWidget(GuiAbout)
assert isinstance(msgAbout, GuiAbout)
+7 -6
View File
@@ -23,10 +23,11 @@ from __future__ import annotations
import pytest
from PyQt5.QtCore import QItemSelectionModel
from PyQt5.QtWidgets import QDialog, QListWidgetItem
from PyQt5.QtWidgets import QListWidgetItem
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.types import QtAccepted, QtRejected
@pytest.mark.gui
@@ -47,17 +48,17 @@ def testDlgOther_QuoteSelect(qtbot, monkeypatch, nwGUI):
assert nwQuot.previewLabel.text() == lastItem
nwQuot.accept()
assert nwQuot.result() == QDialog.DialogCode.Accepted
assert nwQuot.result() == QtAccepted
assert nwQuot.selectedQuote == lastItem
nwQuot.close()
# Test Class Method
with monkeypatch.context() as mp:
mp.setattr(GuiQuoteSelect, "result", lambda *a: QDialog.DialogCode.Accepted)
mp.setattr(GuiQuoteSelect, "result", lambda *a: QtAccepted)
assert GuiQuoteSelect.getQuote(nwGUI, current="X") == ("X", True)
with monkeypatch.context() as mp:
mp.setattr(GuiQuoteSelect, "result", lambda *a: QDialog.DialogCode.Rejected)
mp.setattr(GuiQuoteSelect, "result", lambda *a: QtRejected)
assert GuiQuoteSelect.getQuote(nwGUI, current="X") == ("X", False)
# qtbot.stop()
@@ -69,13 +70,13 @@ def testDlgOther_EditLabel(qtbot, monkeypatch):
monkeypatch.setattr(GuiEditLabel, "exec", lambda *a: None)
with monkeypatch.context() as mp:
mp.setattr(GuiEditLabel, "result", lambda *a: QDialog.DialogCode.Accepted)
mp.setattr(GuiEditLabel, "result", lambda *a: QtAccepted)
newLabel, dlgOk = GuiEditLabel.getLabel(None, text="Hello World") # type: ignore
assert dlgOk is True
assert newLabel == "Hello World"
with monkeypatch.context() as mp:
mp.setattr(GuiEditLabel, "result", lambda *a: QDialog.DialogCode.Rejected)
mp.setattr(GuiEditLabel, "result", lambda *a: QtRejected)
newLabel, dlgOk = GuiEditLabel.getLabel(None, text="Hello World") # type: ignore
assert dlgOk is False
assert newLabel == "Hello World"
+27 -8
View File
@@ -25,16 +25,15 @@ import pytest
from PyQt5.QtCore import Qt
from novelwriter.dialogs.docmerge import GuiDocMerge
from novelwriter.types import QtUserRole
from novelwriter.types import QtAccepted, QtRejected, QtUserRole
from tests.tools import C, buildTestProject
@pytest.mark.gui
def testDlgMerge_Main(qtbot, nwGUI, projPath, mockRnd):
"""Test the merge documents tool.
"""
# Create a new project
def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test the merge documents tool."""
monkeypatch.setattr(GuiDocMerge, "exec", lambda *a: None)
buildTestProject(nwGUI, projPath)
# Check that the dialog kan handle invalid items
@@ -54,13 +53,16 @@ def testDlgMerge_Main(qtbot, nwGUI, projPath, mockRnd):
itemOne = nwMerge.listBox.item(0)
itemTwo = nwMerge.listBox.item(1)
assert itemOne is not None
assert itemTwo is not None
assert itemOne.data(QtUserRole) == C.hChapterDoc
assert itemTwo.data(QtUserRole) == C.hSceneDoc
assert itemOne.checkState() == Qt.CheckState.Checked
assert itemTwo.checkState() == Qt.CheckState.Checked
data = nwMerge.getData()
data = nwMerge.data()
assert data["sHandle"] == C.hChapterDir
assert data["origItems"] == [C.hChapterDir, C.hChapterDoc, C.hSceneDoc]
assert data["moveToTrash"] is False
@@ -70,7 +72,7 @@ def testDlgMerge_Main(qtbot, nwGUI, projPath, mockRnd):
itemTwo.setCheckState(Qt.CheckState.Unchecked)
nwMerge.trashSwitch.setChecked(True)
data = nwMerge.getData()
data = nwMerge.data()
assert data["sHandle"] == C.hChapterDir
assert data["origItems"] == [C.hChapterDir, C.hChapterDoc, C.hSceneDoc]
assert data["moveToTrash"] is True
@@ -79,10 +81,27 @@ def testDlgMerge_Main(qtbot, nwGUI, projPath, mockRnd):
# Restore default values
nwMerge._resetList()
data = nwMerge.getData()
data = nwMerge.data()
assert data["sHandle"] == C.hChapterDir
assert data["origItems"] == [C.hChapterDir, C.hChapterDoc, C.hSceneDoc]
assert data["moveToTrash"] is True
assert data["finalItems"] == [C.hChapterDoc, C.hSceneDoc]
# Test Class Method
with monkeypatch.context() as mp:
mp.setattr(GuiDocMerge, "result", lambda *a: QtAccepted)
data, status = GuiDocMerge.getData(
nwGUI, C.hChapterDir, [C.hChapterDir, C.hChapterDoc, C.hSceneDoc]
)
assert data["sHandle"] == C.hChapterDir
assert status is True
with monkeypatch.context() as mp:
mp.setattr(GuiDocMerge, "result", lambda *a: QtRejected)
data, status = GuiDocMerge.getData(
nwGUI, C.hChapterDir, [C.hChapterDir, C.hChapterDoc, C.hSceneDoc]
)
assert data["sHandle"] == C.hChapterDir
assert status is False
# qtbot.stop()
+18 -4
View File
@@ -25,6 +25,7 @@ import pytest
from novelwriter import SHARED
from novelwriter.dialogs.docsplit import GuiDocSplit
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.types import QtAccepted, QtRejected
from tests.tools import C, buildTestProject
@@ -33,8 +34,7 @@ from tests.tools import C, buildTestProject
def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test the split document tool."""
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
# Create a new project
monkeypatch.setattr(GuiDocSplit, "exec", lambda *a: None)
buildTestProject(nwGUI, projPath)
project = SHARED.project
@@ -74,7 +74,7 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
nwSplit.splitLevel.setCurrentIndex(3)
assert nwSplit.listBox.count() == 12
data, text = nwSplit.getData()
data, text = nwSplit.data()
assert text == docText.splitlines()
assert data["sHandle"] == hSplitDoc
assert data["spLevel"] == 4
@@ -97,5 +97,19 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
nwSplit._loadContent(C.hNovelRoot)
assert nwSplit.listBox.count() == 0
nwSplit.reject()
# Test Class Method
with monkeypatch.context() as mp:
mp.setattr(GuiDocSplit, "result", lambda *a: QtAccepted)
data, text, status = GuiDocSplit.getData(nwGUI, hSplitDoc)
assert data["sHandle"] == hSplitDoc
assert text == docText.splitlines()
assert status is True
with monkeypatch.context() as mp:
mp.setattr(GuiDocSplit, "result", lambda *a: QtRejected)
data, text, status = GuiDocSplit.getData(nwGUI, hSplitDoc)
assert data["sHandle"] == hSplitDoc
assert text == docText.splitlines()
assert status is False
# qtbot.stop()
@@ -91,7 +91,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths):
def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI):
"""Test the preferences dialog actions."""
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")])
monkeypatch.setattr(GuiPreferences, "deleteLater", lambda *a: None)
prefs = GuiPreferences(nwGUI)
prefs.show()
@@ -155,7 +154,6 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, tstPaths):
monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: spelling)
monkeypatch.setattr(CONFIG, "listLanguages", lambda *a: languages)
monkeypatch.setattr(GuiPreferences, "deleteLater", lambda *a: None)
prefs = GuiPreferences(nwGUI)
prefs.show()
@@ -23,13 +23,13 @@ from __future__ import annotations
import pytest
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QAction, QColorDialog, QDialog
from PyQt5.QtWidgets import QAction, QColorDialog
from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.projectsettings import GuiProjectSettings
from novelwriter.enum import nwItemType, nwStatusShape
from novelwriter.types import QtMouseLeft
from novelwriter.types import QtAccepted, QtMouseLeft
from tests.tools import C, buildTestProject
@@ -43,7 +43,7 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
"""
# Block the GUI blocking thread
monkeypatch.setattr(GuiProjectSettings, "exec", lambda *a: None)
monkeypatch.setattr(GuiProjectSettings, "result", lambda *a: QDialog.DialogCode.Accepted)
monkeypatch.setattr(GuiProjectSettings, "result", lambda *a: QtAccepted)
# Check that we cannot open when there is no project
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
+3 -2
View File
@@ -23,11 +23,12 @@ from __future__ import annotations
import pytest
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QAction, QDialog, QFileDialog
from PyQt5.QtWidgets import QAction, QFileDialog
from novelwriter import SHARED
from novelwriter.core.spellcheck import UserDictionary
from novelwriter.dialogs.wordlist import GuiWordList
from novelwriter.types import QtAccepted
from tests.mocked import causeOSError
from tests.tools import buildTestProject
@@ -39,7 +40,7 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, fncPath, projPath):
buildTestProject(nwGUI, projPath)
monkeypatch.setattr(GuiWordList, "exec", lambda *a: None)
monkeypatch.setattr(GuiWordList, "result", lambda *a: QDialog.DialogCode.Accepted)
monkeypatch.setattr(GuiWordList, "result", lambda *a: QtAccepted)
monkeypatch.setattr(GuiWordList, "accept", lambda *a: None)
# Open project
+10 -8
View File
@@ -26,7 +26,7 @@ import pytest
from PyQt5.QtCore import QEvent, QMimeData, QPoint, Qt, QTimer
from PyQt5.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QMouseEvent
from PyQt5.QtWidgets import QDialog, QMenu, QMessageBox, QTreeWidget, QTreeWidgetItem
from PyQt5.QtWidgets import QMenu, QMessageBox, QTreeWidget, QTreeWidgetItem
from novelwriter import CONFIG, SHARED
from novelwriter.core.item import NWItem
@@ -37,7 +37,7 @@ from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwWidget
from novelwriter.gui.projtree import GuiProjectTree, GuiProjectView, _TreeContextMenu
from novelwriter.guimain import GuiMain
from novelwriter.types import QtModeNone, QtMouseLeft, QtMouseMiddle
from novelwriter.types import QtAccepted, QtModeNone, QtMouseLeft, QtMouseMiddle, QtRejected
from tests.mocked import causeOSError
from tests.tools import C, buildTestProject
@@ -529,8 +529,9 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, projPath, mockRnd,
monkeypatch.setattr(GuiDocMerge, "__init__", lambda *a: None)
monkeypatch.setattr(GuiDocMerge, "exec", lambda *a: None)
monkeypatch.setattr(GuiDocMerge, "result", lambda *a: QDialog.DialogCode.Accepted)
monkeypatch.setattr(GuiDocMerge, "getData", lambda *a: mergeData)
monkeypatch.setattr(GuiDocMerge, "softDelete", lambda *a: None)
monkeypatch.setattr(GuiDocMerge, "result", lambda *a: QtAccepted)
monkeypatch.setattr(GuiDocMerge, "data", lambda *a: mergeData)
buildTestProject(nwGUI, projPath)
@@ -585,7 +586,7 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, projPath, mockRnd,
# User cancels merge
with monkeypatch.context() as mp:
mp.setattr(GuiDocMerge, "result", lambda *a: QDialog.DialogCode.Rejected)
mp.setattr(GuiDocMerge, "result", lambda *a: QtRejected)
assert projTree._mergeDocuments(hChapter1, True) is False
# The merge goes through
@@ -628,8 +629,9 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, projPath, mockRnd,
monkeypatch.setattr(GuiDocSplit, "__init__", lambda *a: None)
monkeypatch.setattr(GuiDocSplit, "exec", lambda *a: None)
monkeypatch.setattr(GuiDocSplit, "result", lambda *a: QDialog.DialogCode.Accepted)
monkeypatch.setattr(GuiDocSplit, "getData", lambda *a: (splitData, splitText))
monkeypatch.setattr(GuiDocSplit, "softDelete", lambda *a: None)
monkeypatch.setattr(GuiDocSplit, "result", lambda *a: QtAccepted)
monkeypatch.setattr(GuiDocSplit, "data", lambda *a: (splitData, splitText))
# Create a project
buildTestProject(nwGUI, projPath)
@@ -722,7 +724,7 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, projPath, mockRnd,
# Cancelled by user
with monkeypatch.context() as mp:
mp.setattr(GuiDocSplit, "result", lambda *a: QDialog.DialogCode.Rejected)
mp.setattr(GuiDocSplit, "result", lambda *a: QtRejected)
assert projTree._splitDocument(hSplitDoc) is False
# qtbot.stop()
-1
View File
@@ -70,7 +70,6 @@ def testToolWelcome_Main(qtbot: QtBot, monkeypatch, nwGUI, fncPath):
def testToolWelcome_Open(qtbot: QtBot, monkeypatch, nwGUI, fncPath):
"""Test the open tab in the Welcome window."""
monkeypatch.setattr(QMenu, "exec", lambda *a: None)
monkeypatch.setattr(QMenu, "deleteLater", lambda *a: None)
CONFIG.recentProjects.update("/stuff/project_one", "Project One", 12345, 1690000000)
CONFIG.recentProjects.update("/stuff/project_two", "Project Two", 54321, 1700000000)