diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py index a0b2a1c4..226d0054 100644 --- a/novelwriter/dialogs/about.py +++ b/novelwriter/dialogs/about.py @@ -125,7 +125,7 @@ class GuiAbout(NDialog): def closeEvent(self, event: QCloseEvent) -> None: """Capture the close event and perform cleanup.""" event.accept() - self.deleteLater() + self.softDelete() return ## diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py index feead394..32b52a8d 100644 --- a/novelwriter/dialogs/docmerge.py +++ b/novelwriter/dialogs/docmerge.py @@ -137,7 +137,7 @@ class GuiDocMerge(NDialog): cls.exec() data = cls.data() accepted = cls.result() == QtAccepted - cls.deleteLater() + cls.softDelete() return data, accepted ## diff --git a/novelwriter/dialogs/docsplit.py b/novelwriter/dialogs/docsplit.py index 56f87880..5c4e586b 100644 --- a/novelwriter/dialogs/docsplit.py +++ b/novelwriter/dialogs/docsplit.py @@ -185,7 +185,7 @@ class GuiDocSplit(NDialog): cls.exec() data, text = cls.data() accepted = cls.result() == QtAccepted - cls.deleteLater() + cls.softDelete() return data, text, accepted ## diff --git a/novelwriter/dialogs/editlabel.py b/novelwriter/dialogs/editlabel.py index 05859a27..d7cf8875 100644 --- a/novelwriter/dialogs/editlabel.py +++ b/novelwriter/dialogs/editlabel.py @@ -90,5 +90,5 @@ class GuiEditLabel(NDialog): cls.exec() label = cls.itemLabel accepted = cls.result() == QtAccepted - cls.deleteLater() + cls.softDelete() return label, accepted diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index ccbe4cb0..c723ba66 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -771,7 +771,7 @@ class GuiPreferences(NDialog): event.accept() QApplication.processEvents() self.done(nwConst.DLG_FINISHED) - self.deleteLater() + self.softDelete() return ## diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index 2af8f3cb..010f50e1 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -147,7 +147,7 @@ class GuiProjectSettings(NDialog): """Capture the user closing the window and save settings.""" self._saveSettings() event.accept() - self.deleteLater() + self.softDelete() return ## diff --git a/novelwriter/dialogs/quotes.py b/novelwriter/dialogs/quotes.py index 85289f3c..441a52b6 100644 --- a/novelwriter/dialogs/quotes.py +++ b/novelwriter/dialogs/quotes.py @@ -131,7 +131,7 @@ class GuiQuoteSelect(NDialog): cls.exec() quote = cls._selected accepted = cls.result() == QtAccepted - cls.deleteLater() + cls.softDelete() return quote, accepted ## diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py index 817781ee..51ce49b6 100644 --- a/novelwriter/dialogs/wordlist.py +++ b/novelwriter/dialogs/wordlist.py @@ -140,7 +140,7 @@ class GuiWordList(NDialog): """Capture the close event and perform cleanup.""" self._saveGuiSettings() event.accept() - self.deleteLater() + self.softDelete() return ## diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index 48694106..12048497 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -45,6 +45,15 @@ if TYPE_CHECKING: # pragma: no cover 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): diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py index d0e11b46..f56e5761 100644 --- a/novelwriter/tools/dictionaries.py +++ b/novelwriter/tools/dictionaries.py @@ -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 ## diff --git a/novelwriter/tools/lipsum.py b/novelwriter/tools/lipsum.py index 5956db4b..9296d5b8 100644 --- a/novelwriter/tools/lipsum.py +++ b/novelwriter/tools/lipsum.py @@ -133,7 +133,7 @@ class GuiLipsum(NDialog): cls = GuiLipsum(parent) cls.exec() text = cls.lipsumText - cls.deleteLater() + cls.softDelete() return text ## diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py index e12a6077..a8f2f7c2 100644 --- a/novelwriter/tools/manusbuild.py +++ b/novelwriter/tools/manusbuild.py @@ -250,7 +250,7 @@ class GuiManuscriptBuild(NDialog): """ self._saveSettings() event.accept() - self.deleteLater() + self.softDelete() return ## diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 841eaa15..54960270 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -265,7 +265,7 @@ class GuiManuscript(NToolDialog): if isinstance(obj, GuiBuildSettings) and obj.isVisible(): obj.close() event.accept() - self.deleteLater() + self.softDelete() return ## diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 1488c3b8..23b2a946 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -200,7 +200,7 @@ class GuiBuildSettings(NToolDialog): self._askToSaveBuild() self._saveSettings() event.accept() - self.deleteLater() + self.softDelete() return ## diff --git a/novelwriter/tools/noveldetails.py b/novelwriter/tools/noveldetails.py index dc2ac3c2..9362c640 100644 --- a/novelwriter/tools/noveldetails.py +++ b/novelwriter/tools/noveldetails.py @@ -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 ## diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py index c7726b6f..3f47b707 100644 --- a/novelwriter/tools/welcome.py +++ b/novelwriter/tools/welcome.py @@ -196,7 +196,7 @@ class GuiWelcome(NDialog): """Capture the user closing the window and save settings.""" self._saveSettings() event.accept() - self.deleteLater() + self.softDelete() return ## diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py index c0b63085..8e42a0c0 100644 --- a/novelwriter/tools/writingstats.py +++ b/novelwriter/tools/writingstats.py @@ -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 ## diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index 12402fba..c81a539b 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -529,7 +529,7 @@ 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, "deleteLater", lambda *a: None) + monkeypatch.setattr(GuiDocMerge, "softDelete", lambda *a: None) monkeypatch.setattr(GuiDocMerge, "result", lambda *a: QtAccepted) monkeypatch.setattr(GuiDocMerge, "data", lambda *a: mergeData) @@ -629,7 +629,7 @@ 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, "deleteLater", lambda *a: None) + monkeypatch.setattr(GuiDocSplit, "softDelete", lambda *a: None) monkeypatch.setattr(GuiDocSplit, "result", lambda *a: QtAccepted) monkeypatch.setattr(GuiDocSplit, "data", lambda *a: (splitData, splitText))