diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index af167bdf..a47c3805 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -25,11 +25,11 @@ from __future__ import annotations import logging -from PyQt5.QtGui import QFont -from PyQt5.QtCore import Qt, pyqtSlot +from PyQt5.QtGui import QCloseEvent, QFont +from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import ( QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox, - QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox + QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox, qApp ) from novelwriter import CONFIG, SHARED @@ -43,6 +43,8 @@ logger = logging.getLogger(__name__) class GuiPreferences(NPagedDialog): + newPreferencesReady = pyqtSignal(bool, bool, bool, bool) + def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) @@ -68,7 +70,8 @@ class GuiPreferences(NPagedDialog): self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self) self.buttonBox.accepted.connect(self._doSave) - self.buttonBox.rejected.connect(self._doClose) + self.buttonBox.rejected.connect(self.close) + self.rejected.connect(self.close) self.addControls(self.buttonBox) self.resize(*CONFIG.preferencesWinSize) @@ -88,24 +91,16 @@ class GuiPreferences(NPagedDialog): return ## - # Properties + # Events ## - @property - def updateTheme(self) -> bool: - return self._updateTheme - - @property - def updateSyntax(self) -> bool: - return self._updateSyntax - - @property - def needsRestart(self) -> bool: - return self._needsRestart - - @property - def refreshTree(self) -> bool: - return self._refreshTree + def closeEvent(self, event: QCloseEvent) -> None: + """Capture the close event and perform cleanup.""" + logger.debug("Close: GuiPreferences") + self._saveWindowSize() + event.accept() + self.deleteLater() + return ## # Private Slots @@ -113,11 +108,7 @@ class GuiPreferences(NPagedDialog): @pyqtSlot() def _doSave(self) -> None: - """Trigger all the save functions in the tabs, and collect the - status of the saves. - """ - logger.debug("Saving new preferences") - + """Trigger save functions in the tabs and emit ready signal.""" self.tabGeneral.saveValues() self.tabProjects.saveValues() self.tabDocs.saveValues() @@ -126,19 +117,15 @@ class GuiPreferences(NPagedDialog): self.tabAuto.saveValues() self.tabQuote.saveValues() - self._saveWindowSize() CONFIG.saveConfig() - self.accept() + self.newPreferencesReady.emit( + self._needsRestart, self._refreshTree, self._updateTheme, self._updateSyntax + ) + qApp.processEvents() + self.close() return - @pyqtSlot() - def _doClose(self) -> None: - """Close the preferences without saving the changes.""" - self._saveWindowSize() - self.reject() - return - ## # Internal Functions ## diff --git a/novelwriter/dialogs/projdetails.py b/novelwriter/dialogs/projdetails.py index 6747f522..18afa3f7 100644 --- a/novelwriter/dialogs/projdetails.py +++ b/novelwriter/dialogs/projdetails.py @@ -71,8 +71,8 @@ class GuiProjectDetails(NPagedDialog): self.addTab(self.tabContents, self.tr("Contents")) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close) - self.buttonBox.button(QDialogButtonBox.Close) - self.buttonBox.rejected.connect(self._doClose) + self.buttonBox.rejected.connect(self.close) + self.rejected.connect(self.close) self.addControls(self.buttonBox) logger.debug("Ready: GuiProjectDetails") @@ -95,21 +95,11 @@ class GuiProjectDetails(NPagedDialog): def closeEvent(self, event: QCloseEvent) -> None: """Capture the close event and perform cleanup.""" + self._saveGuiSettings() event.accept() self.deleteLater() return - ## - # Private Slots - ## - - @pyqtSlot() - def _doClose(self) -> None: - """Save settings and close the dialog.""" - self._saveGuiSettings() - self.close() - return - ## # Internal Functions ## diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py index 06a3f850..f8d17af1 100644 --- a/novelwriter/dialogs/projsettings.py +++ b/novelwriter/dialogs/projsettings.py @@ -27,11 +27,11 @@ import logging from typing import TYPE_CHECKING -from PyQt5.QtGui import QIcon, QPixmap, QColor -from PyQt5.QtCore import Qt, pyqtSlot +from PyQt5.QtGui import QCloseEvent, QIcon, QPixmap, QColor +from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import ( QColorDialog, QComboBox, QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, - QPushButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget + QPushButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, qApp ) from novelwriter import CONFIG, SHARED @@ -53,6 +53,8 @@ class GuiProjectSettings(NPagedDialog): TAB_IMPORT = 2 TAB_REPLACE = 3 + newProjectSettingsReady = pyqtSignal() + def __init__(self, mainGui: GuiMain, focusTab: int = TAB_MAIN) -> None: super().__init__(parent=mainGui) @@ -86,7 +88,8 @@ class GuiProjectSettings(NPagedDialog): self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self._doSave) - self.buttonBox.rejected.connect(self._doClose) + self.buttonBox.rejected.connect(self.close) + self.rejected.connect(self.close) self.addControls(self.buttonBox) # Focus Tab @@ -100,6 +103,13 @@ class GuiProjectSettings(NPagedDialog): logger.debug("Delete: GuiProjectSettings") return + def closeEvent(self, event: QCloseEvent) -> None: + """Capture the close event and perform cleanup.""" + self._saveGuiSettings() + event.accept() + self.deleteLater() + return + ## # Private Slots ## @@ -137,18 +147,12 @@ class GuiProjectSettings(NPagedDialog): newList = self.tabReplace.getNewList() project.data.setAutoReplace(newList) - self._saveGuiSettings() - self.accept() + self.newProjectSettingsReady.emit() + qApp.processEvents() + self.close() return - @pyqtSlot() - def _doClose(self) -> None: - """Save settings and close the dialog.""" - self._saveGuiSettings() - self.reject() - return - ## # Internal Functions ## diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py index 7a264914..0611b884 100644 --- a/novelwriter/dialogs/wordlist.py +++ b/novelwriter/dialogs/wordlist.py @@ -27,11 +27,11 @@ import logging from typing import TYPE_CHECKING -from PyQt5.QtCore import Qt, pyqtSlot from PyQt5.QtGui import QCloseEvent +from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import ( QAbstractItemView, QDialog, QDialogButtonBox, QHBoxLayout, QLabel, - QLineEdit, QListWidget, QListWidgetItem, QPushButton, QVBoxLayout + QLineEdit, QListWidget, QListWidgetItem, QPushButton, QVBoxLayout, qApp ) from novelwriter import CONFIG, SHARED @@ -45,6 +45,8 @@ logger = logging.getLogger(__name__) class GuiWordList(QDialog): + newWordListReady = pyqtSignal() + def __init__(self, mainGui: GuiMain) -> None: super().__init__(parent=mainGui) @@ -166,6 +168,8 @@ class GuiWordList(QDialog): if word: userDict.add(word) userDict.save() + self.newWordListReady.emit() + qApp.processEvents() self.close() return diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index fa57fe47..2500ac3c 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -154,12 +154,12 @@ class GuiMainMenu(QMenuBar): # Project > Project Settings self.aProjectSettings = self.projMenu.addAction(self.tr("Project Settings")) self.aProjectSettings.setShortcut("Ctrl+Shift+,") - self.aProjectSettings.triggered.connect(lambda: self.mainGui.showProjectSettingsDialog()) + self.aProjectSettings.triggered.connect(self.mainGui.showProjectSettingsDialog) # Project > Project Details self.aProjectDetails = self.projMenu.addAction(self.tr("Project Details")) self.aProjectDetails.setShortcut("Shift+F6") - self.aProjectDetails.triggered.connect(lambda: self.mainGui.showProjectDetailsDialog()) + self.aProjectDetails.triggered.connect(self.mainGui.showProjectDetailsDialog) # Project > Separator self.projMenu.addSeparator() @@ -594,7 +594,7 @@ class GuiMainMenu(QMenuBar): # Insert > Placeholder Text self.aLipsumText = self.mInsBreaks.addAction(self.tr("Placeholder Text")) - self.aLipsumText.triggered.connect(lambda: self.mainGui.showLoremIpsumDialog()) + self.aLipsumText.triggered.connect(self.mainGui.showLoremIpsumDialog) return @@ -872,7 +872,7 @@ class GuiMainMenu(QMenuBar): # Tools > Project Word List self.aEditWordList = self.toolsMenu.addAction(self.tr("Project Word List")) - self.aEditWordList.triggered.connect(lambda: self.mainGui.showProjectWordListDialog()) + self.aEditWordList.triggered.connect(self.mainGui.showProjectWordListDialog) # Tools > Add Dictionaries if CONFIG.osWindows or CONFIG.isDebug: @@ -902,13 +902,13 @@ class GuiMainMenu(QMenuBar): # Tools > Writing Statistics self.aWritingStats = self.toolsMenu.addAction(self.tr("Writing Statistics")) self.aWritingStats.setShortcut("F6") - self.aWritingStats.triggered.connect(lambda: self.mainGui.showWritingStatsDialog()) + self.aWritingStats.triggered.connect(self.mainGui.showWritingStatsDialog) # Tools > Preferences self.aPreferences = self.toolsMenu.addAction(self.tr("Preferences")) self.aPreferences.setShortcut("Ctrl+,") self.aPreferences.setMenuRole(QAction.PreferencesRole) - self.aPreferences.triggered.connect(lambda: self.mainGui.showPreferencesDialog()) + self.aPreferences.triggered.connect(self.mainGui.showPreferencesDialog) return @@ -920,12 +920,12 @@ class GuiMainMenu(QMenuBar): # Help > About self.aAboutNW = self.helpMenu.addAction(self.tr("About novelWriter")) self.aAboutNW.setMenuRole(QAction.AboutRole) - self.aAboutNW.triggered.connect(lambda: self.mainGui.showAboutNWDialog()) + self.aAboutNW.triggered.connect(self.mainGui.showAboutNWDialog) # Help > About Qt5 self.aAboutQt = self.helpMenu.addAction(self.tr("About Qt5")) self.aAboutQt.setMenuRole(QAction.AboutQtRole) - self.aAboutQt.triggered.connect(lambda: self.mainGui.showAboutQtDialog()) + self.aAboutQt.triggered.connect(self.mainGui.showAboutQtDialog) # Help > Separator self.helpMenu.addSeparator() @@ -961,7 +961,7 @@ class GuiMainMenu(QMenuBar): # Document > Check for Updates self.aUpdates = self.helpMenu.addAction(self.tr("Check for New Release")) - self.aUpdates.triggered.connect(lambda: self.mainGui.showUpdatesDialog()) + self.aUpdates.triggered.connect(self.mainGui.showUpdatesDialog) return diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 33c849d4..25bf97ca 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -66,7 +66,7 @@ from novelwriter.core.coretools import ProjectBuilder from novelwriter.enum import ( nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView ) -from novelwriter.common import getGuiItem, hexToInt +from novelwriter.common import hexToInt from novelwriter.constants import nwFiles logger = logging.getLogger(__name__) @@ -866,213 +866,122 @@ class GuiMain(QMainWindow): return None + @pyqtSlot() def showPreferencesDialog(self) -> None: """Open the preferences dialog.""" - dlgConf = GuiPreferences(self) - dlgConf.exec_() - - if dlgConf.result() == QDialog.Accepted: - logger.debug("Applying new preferences") - self.initMain() - self.saveDocument() - - if dlgConf.needsRestart: - SHARED.info(self.tr( - "Some changes will not be applied until novelWriter has been restarted." - )) - - if dlgConf.refreshTree: - self.projView.populateTree() - - if dlgConf.updateTheme: - # We are doing this manually instead of connecting to - # qApp.paletteChanged since the processing order matters - SHARED.theme.loadTheme() - self.docEditor.updateTheme() - self.docViewer.updateTheme() - self.docViewerPanel.updateTheme() - self.sideBar.updateTheme() - self.projView.updateTheme() - self.novelView.updateTheme() - self.outlineView.updateTheme() - self.itemDetails.updateTheme() - self.mainStatus.updateTheme() - - if dlgConf.updateSyntax: - SHARED.theme.loadSyntax() - self.docEditor.updateSyntaxColours() - - self.docEditor.initEditor() - self.docViewer.initViewer() - self.projView.initSettings() - self.novelView.initSettings() - self.outlineView.initSettings() - - self._updateStatusWordCount() - + dialog = GuiPreferences(self) + dialog.newPreferencesReady.connect(self._processConfigChanges) + dialog.exec_() return + @pyqtSlot() @pyqtSlot(int) - def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> bool: + def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> None: """Open the project settings dialog.""" - if not SHARED.hasProject: - logger.error("No project open") - return False - - dlgProj = GuiProjectSettings(self, focusTab=focusTab) - dlgProj.exec_() - - if dlgProj.result() == QDialog.Accepted: - logger.debug("Applying new project settings") - SHARED.updateSpellCheckLanguage() - self.itemDetails.refreshDetails() - self._updateWindowTitle(SHARED.project.data.name) - - return True - - def showProjectDetailsDialog(self) -> bool: - """Open the project details dialog.""" - if not SHARED.hasProject: - logger.error("No project open") - return False - - dlgDetails = getGuiItem("GuiProjectDetails") - if dlgDetails is None: - dlgDetails = GuiProjectDetails(self) - assert isinstance(dlgDetails, GuiProjectDetails) - - dlgDetails.setModal(True) - dlgDetails.show() - dlgDetails.raise_() - dlgDetails.updateValues() - - return True + if SHARED.hasProject: + dialog = GuiProjectSettings(self, focusTab=focusTab) + dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges) + dialog.exec_() + return @pyqtSlot() - def showBuildManuscriptDialog(self) -> bool: + def showProjectDetailsDialog(self) -> None: + """Open the project details dialog.""" + if SHARED.hasProject: + dialog = GuiProjectDetails(self) + dialog.setModal(True) + dialog.show() + dialog.raise_() + qApp.processEvents() + dialog.updateValues() + return + + @pyqtSlot() + def showBuildManuscriptDialog(self) -> None: """Open the build manuscript dialog.""" - if not SHARED.hasProject: - logger.error("No project open") - return False + if SHARED.hasProject: + dialog = GuiManuscript(self) + dialog.setModal(False) + dialog.show() + dialog.raise_() + qApp.processEvents() + dialog.loadContent() + return - dlgBuild = getGuiItem("GuiManuscript") - if dlgBuild is None: - dlgBuild = GuiManuscript(self) - assert isinstance(dlgBuild, GuiManuscript) - - dlgBuild.setModal(False) - dlgBuild.show() - dlgBuild.raise_() - qApp.processEvents() - - dlgBuild.loadContent() - - return True - - def showLoremIpsumDialog(self) -> bool: + @pyqtSlot() + def showLoremIpsumDialog(self) -> None: """Open the insert lorem ipsum text dialog.""" - if not SHARED.hasProject: - logger.error("No project open") - return False + if SHARED.hasProject: + dialog = GuiLipsum(self) + dialog.setModal(False) + dialog.show() + dialog.raise_() + qApp.processEvents() + return - dlgLipsum = getGuiItem("GuiLipsum") - if dlgLipsum is None: - dlgLipsum = GuiLipsum(self) - assert isinstance(dlgLipsum, GuiLipsum) - - dlgLipsum.setModal(False) - dlgLipsum.show() - dlgLipsum.raise_() - qApp.processEvents() - - return True - - def showProjectWordListDialog(self) -> bool: + @pyqtSlot() + def showProjectWordListDialog(self) -> None: """Open the project word list dialog.""" - if not SHARED.hasProject: - logger.error("No project open") - return False + if SHARED.hasProject: + dialog = GuiWordList(self) + dialog.newWordListReady.connect(self._processWordListChanges) + dialog.exec_() + return - dlgWords = GuiWordList(self) - dlgWords.exec_() - - if dlgWords.result() == QDialog.Accepted: - logger.debug("Reloading word list") - SHARED.updateSpellCheckLanguage(reload=True) - self.docEditor.spellCheckDocument() - - return True - - def showWritingStatsDialog(self) -> bool: + @pyqtSlot() + def showWritingStatsDialog(self) -> None: """Open the session stats dialog.""" - if not SHARED.hasProject: - logger.error("No project open") - return False + if SHARED.hasProject: + dialog = GuiWritingStats(self) + dialog.setModal(False) + dialog.show() + dialog.raise_() + qApp.processEvents() + dialog.populateGUI() + return - dlgStats = getGuiItem("GuiWritingStats") - if dlgStats is None: - dlgStats = GuiWritingStats(self) - assert isinstance(dlgStats, GuiWritingStats) - - dlgStats.setModal(False) - dlgStats.show() - dlgStats.raise_() + @pyqtSlot() + def showAboutNWDialog(self, showNotes: bool = False) -> None: + """Show the novelWriter about dialog.""" + dialog = GuiAbout(self) + dialog.setModal(True) + dialog.show() + dialog.raise_() qApp.processEvents() - dlgStats.populateGUI() - - return True - - def showAboutNWDialog(self, showNotes: bool = False) -> bool: - """Show the about dialog for novelWriter.""" - dlgAbout = getGuiItem("GuiAbout") - if dlgAbout is None: - dlgAbout = GuiAbout(self) - assert isinstance(dlgAbout, GuiAbout) - - dlgAbout.setModal(True) - dlgAbout.show() - dlgAbout.raise_() - qApp.processEvents() - dlgAbout.populateGUI() - + dialog.populateGUI() if showNotes: - dlgAbout.showReleaseNotes() - - return True + dialog.showReleaseNotes() + return + @pyqtSlot() def showAboutQtDialog(self) -> None: - """Show the about dialog for Qt.""" + """Show the Qt about dialog.""" msgBox = QMessageBox(self) msgBox.aboutQt(self, "About Qt") return + @pyqtSlot() def showUpdatesDialog(self) -> None: """Show the check for updates dialog.""" - dlgUpdate = getGuiItem("GuiUpdates") - if dlgUpdate is None: - dlgUpdate = GuiUpdates(self) - assert isinstance(dlgUpdate, GuiUpdates) - - dlgUpdate.setModal(True) - dlgUpdate.show() - dlgUpdate.raise_() + dialog = GuiUpdates(self) + dialog.setModal(True) + dialog.show() + dialog.raise_() qApp.processEvents() - dlgUpdate.checkLatest() - + dialog.checkLatest() return @pyqtSlot() def showDictionariesDialog(self) -> None: """Show the download dictionaries dialog.""" - dlgDicts = GuiDictionaries(self) - dlgDicts.setModal(True) - dlgDicts.show() - dlgDicts.raise_() + dialog = GuiDictionaries(self) + dialog.setModal(True) + dialog.show() + dialog.raise_() qApp.processEvents() - if not dlgDicts.initDialog(): - dlgDicts.close() + if not dialog.initDialog(): + dialog.close() SHARED.error(self.tr("Could not initialise the dialog.")) - return def reportConfErr(self) -> bool: @@ -1238,6 +1147,65 @@ class GuiMain(QMainWindow): # Private Slots ## + @pyqtSlot(bool, bool, bool, bool) + def _processConfigChanges(self, restart: bool, tree: bool, theme: bool, syntax: bool) -> None: + """Refresh GUI based on flags from the Preferences dialog.""" + logger.debug("Applying new preferences") + self.initMain() + self.saveDocument() + + if restart: + SHARED.info(self.tr( + "Some changes will not be applied until novelWriter has been restarted." + )) + + if tree: + self.projView.populateTree() + + if theme: + # We are doing this manually instead of connecting to + # qApp.paletteChanged since the processing order matters + SHARED.theme.loadTheme() + self.docEditor.updateTheme() + self.docViewer.updateTheme() + self.docViewerPanel.updateTheme() + self.sideBar.updateTheme() + self.projView.updateTheme() + self.novelView.updateTheme() + self.outlineView.updateTheme() + self.itemDetails.updateTheme() + self.mainStatus.updateTheme() + + if syntax: + SHARED.theme.loadSyntax() + self.docEditor.updateSyntaxColours() + + self.docEditor.initEditor() + self.docViewer.initViewer() + self.projView.initSettings() + self.novelView.initSettings() + self.outlineView.initSettings() + self._updateStatusWordCount() + + return + + @pyqtSlot() + def _processProjectSettingsChanges(self) -> None: + """Refresh data dependent on project settings.""" + logger.debug("Applying new project settings") + SHARED.updateSpellCheckLanguage() + self.itemDetails.refreshDetails() + self._updateWindowTitle(SHARED.project.data.name) + return + + @pyqtSlot() + def _processWordListChanges(self) -> None: + """Reload project word list.""" + logger.debug("Reloading word list") + SHARED.updateSpellCheckLanguage(reload=True) + self.docEditor.spellCheckDocument() + return + @pyqtSlot(str, nwDocMode) def _followTag(self, tag: str, mode: nwDocMode) -> None: """Follow a tag after user interaction with a link.""" diff --git a/tests/test_dialogs/test_dlg_about.py b/tests/test_dialogs/test_dlg_about.py index ffcdb5be..2587a8d5 100644 --- a/tests/test_dialogs/test_dlg_about.py +++ b/tests/test_dialogs/test_dlg_about.py @@ -35,7 +35,7 @@ from novelwriter.dialogs.about import GuiAbout def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI): """Test the novelWriter about dialogs.""" # NW About - assert nwGUI.showAboutNWDialog(showNotes=True) is True + nwGUI.showAboutNWDialog(showNotes=True) qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000) msgAbout = getGuiItem("GuiAbout") diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index ae4d34ae..31d29c2c 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -45,23 +45,12 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths): monkeypatch.setattr(GuiPreferences, "result", lambda *a: QDialog.Accepted) monkeypatch.setattr(SHARED._spelling, "listDictionaries", lambda: [("en", "English [en]")]) - with monkeypatch.context() as mp: - mp.setattr(GuiPreferences, "updateTheme", lambda *a: True) - mp.setattr(GuiPreferences, "updateSyntax", lambda *a: True) - mp.setattr(GuiPreferences, "needsRestart", lambda *a: True) - mp.setattr(GuiPreferences, "refreshTree", lambda *a: True) - nwGUI.mainMenu.aPreferences.activate(QAction.Trigger) - qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000) - + nwGUI.mainMenu.aPreferences.activate(QAction.Trigger) + qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000) nwPrefs = getGuiItem("GuiPreferences") assert isinstance(nwPrefs, GuiPreferences) nwPrefs.show() - assert nwPrefs.updateTheme is False - assert nwPrefs.updateSyntax is False - assert nwPrefs.needsRestart is False - assert nwPrefs.refreshTree is False - # General Settings qtbot.wait(KEY_DELAY) tabGeneral = nwPrefs.tabGeneral @@ -100,8 +89,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths): qtbot.mouseClick(tabProjects.backupOnClose, Qt.LeftButton) assert tabProjects.backupOnClose.isChecked() - # qtbot.stop() - # Check Browse button monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "") assert not tabProjects._backupFolder() @@ -204,7 +191,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths): # Save and Check Config qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton) - nwPrefs._doClose() assert CONFIG.saveConfig() projFile = tstPaths.cnfDir / "novelwriter.conf" diff --git a/tests/test_dialogs/test_dlg_projdetails.py b/tests/test_dialogs/test_dlg_projdetails.py index 8ea28443..c8617c72 100644 --- a/tests/test_dialogs/test_dlg_projdetails.py +++ b/tests/test_dialogs/test_dlg_projdetails.py @@ -66,12 +66,12 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, prjLipsum): tocTab = projDet.tabContents tocTree = tocTab.tocTree assert tocTree.topLevelItemCount() == 7 - assert tocTree.topLevelItem(0).text(tocTab.C_TITLE) == "Lorem Ipsum" - assert tocTree.topLevelItem(2).text(tocTab.C_TITLE) == "Prologue" - assert tocTree.topLevelItem(3).text(tocTab.C_TITLE) == "Act One" - assert tocTree.topLevelItem(4).text(tocTab.C_TITLE) == "Chapter One" - assert tocTree.topLevelItem(5).text(tocTab.C_TITLE) == "Chapter Two" - assert tocTree.topLevelItem(6).text(tocTab.C_TITLE) == "END" + assert tocTree.topLevelItem(0).text(tocTab.C_TITLE) == "Lorem Ipsum" # type: ignore + assert tocTree.topLevelItem(2).text(tocTab.C_TITLE) == "Prologue" # type: ignore + assert tocTree.topLevelItem(3).text(tocTab.C_TITLE) == "Act One" # type: ignore + assert tocTree.topLevelItem(4).text(tocTab.C_TITLE) == "Chapter One" # type: ignore + assert tocTree.topLevelItem(5).text(tocTab.C_TITLE) == "Chapter Two" # type: ignore + assert tocTree.topLevelItem(6).text(tocTab.C_TITLE) == "END" # type: ignore # Count Pages tocTab.wpValue.setValue(100) @@ -82,8 +82,8 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, prjLipsum): thePages = ["1", "2", "1", "1", "11", "17", "0"] thePage = ["i", "ii", "1", "2", "3", "14", "31"] for i in range(7): - assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i] - assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i] + assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i] # type: ignore + assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i] # type: ignore tocTab.poValue.setValue(5) tocTab.dblValue.setChecked(True) @@ -92,8 +92,8 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, prjLipsum): thePages = ["2", "2", "2", "2", "12", "18", "0"] thePage = ["i", "iii", "1", "3", "5", "17", "35"] for i in range(7): - assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i] - assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i] + assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i] # type: ignore + assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i] # type: ignore # Re-populate assert tocTab._currentRoot is None @@ -103,7 +103,7 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, prjLipsum): # qtbot.stop() # Clean Up - projDet._doClose() + projDet.close() nwGUI.closeMain() # END Test testDlgProjDetails_Dialog diff --git a/tests/test_dialogs/test_dlg_projsettings.py b/tests/test_dialogs/test_dlg_projsettings.py index 1413bb4a..1af844d7 100644 --- a/tests/test_dialogs/test_dlg_projsettings.py +++ b/tests/test_dialogs/test_dlg_projsettings.py @@ -57,26 +57,26 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI): nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger) qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000) - projEdit = getGuiItem("GuiProjectSettings") - assert isinstance(projEdit, GuiProjectSettings) - projEdit.show() - qtbot.addWidget(projEdit) + projSettings = getGuiItem("GuiProjectSettings") + assert isinstance(projSettings, GuiProjectSettings) + projSettings.show() + qtbot.addWidget(projSettings) # Switch Tabs - projEdit._focusTab(GuiProjectSettings.TAB_REPLACE) - assert projEdit._tabBox.currentWidget() == projEdit.tabReplace + projSettings._focusTab(GuiProjectSettings.TAB_REPLACE) + assert projSettings._tabBox.currentWidget() == projSettings.tabReplace - projEdit._focusTab(GuiProjectSettings.TAB_IMPORT) - assert projEdit._tabBox.currentWidget() == projEdit.tabImport + projSettings._focusTab(GuiProjectSettings.TAB_IMPORT) + assert projSettings._tabBox.currentWidget() == projSettings.tabImport - projEdit._focusTab(GuiProjectSettings.TAB_STATUS) - assert projEdit._tabBox.currentWidget() == projEdit.tabStatus + projSettings._focusTab(GuiProjectSettings.TAB_STATUS) + assert projSettings._tabBox.currentWidget() == projSettings.tabStatus - projEdit._focusTab(GuiProjectSettings.TAB_MAIN) - assert projEdit._tabBox.currentWidget() == projEdit.tabMain + projSettings._focusTab(GuiProjectSettings.TAB_MAIN) + assert projSettings._tabBox.currentWidget() == projSettings.tabMain # Clean Up - projEdit._doClose() + projSettings.close() # qtbot.stop() # END Test testDlgProjSettings_Dialog @@ -93,10 +93,10 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockR CONFIG.setBackupPath(fncPath) # Set some values - theProject = SHARED.project - theProject.data.setSpellLang("en") - theProject.data.setAuthor("Jane Smith") - theProject.data.setAutoReplace({"A": "B", "C": "D"}) + project = SHARED.project + project.data.setSpellLang("en") + project.data.setAuthor("Jane Smith") + project.data.setAutoReplace({"A": "B", "C": "D"}) # Create Dialog projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_MAIN) @@ -130,12 +130,13 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockR assert tabMain.editAuthor.text() == "Jane Doe" projSettings._doSave() - assert theProject.data.name == "Project Name" - assert theProject.data.title == "Project Title" - assert theProject.data.author == "Jane Doe" + assert project.data.name == "Project Name" + assert project.data.title == "Project Title" + assert project.data.author == "Jane Doe" + + nwGUI._processProjectSettingsChanges() + assert nwGUI.windowTitle() == "novelWriter - Project Name" - # Clean up - projSettings._doClose() # qtbot.stop() # END Test testDlgProjSettings_Main @@ -334,9 +335,7 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat assert importItems[C.iMain]["name"] == "Main" assert importItems["i000014"]["name"] == "Final" - # Clean up # qtbot.stop() - projSettings._doClose() # END Test testDlgProjSettings_StatusImport @@ -422,8 +421,6 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncPath, projPath, mo "A": "B", "C": "D", "This": "With This Stuff" } - # Clean up # qtbot.stop() - projSettings._doClose() # END Test testDlgProjSettings_Replace diff --git a/tests/test_dialogs/test_dlg_wordlist.py b/tests/test_dialogs/test_dlg_wordlist.py index 5dc223b4..4c4ec9fa 100644 --- a/tests/test_dialogs/test_dlg_wordlist.py +++ b/tests/test_dialogs/test_dlg_wordlist.py @@ -122,6 +122,5 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, projPath): assert "word_g" in userDict # qtbot.stop() - wList.close() # END Test testDlgWordList_Dialog diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 659b44ee..a43e86e8 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -29,6 +29,7 @@ from tools import ( C, NWD_IGNORE, cmpFiles, buildTestProject, XML_IGNORE, getGuiItem, writeFile ) +from PyQt5.QtGui import QColor, QPalette from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QDialog, QMenu, QMessageBox, QInputDialog @@ -62,11 +63,6 @@ def testGuiMain_ProjectBlocker(nwGUI): assert nwGUI.openSelectedItem() is False assert nwGUI.editItemLabel() is False assert nwGUI.rebuildIndex() is False - assert nwGUI.showProjectSettingsDialog() is False - assert nwGUI.showProjectDetailsDialog() is False - assert nwGUI.showBuildManuscriptDialog() is False - assert nwGUI.showProjectWordListDialog() is False - assert nwGUI.showWritingStatsDialog() is False # END Test testGuiMain_ProjectBlocker @@ -203,6 +199,28 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # END Test testGuiMain_ProjectTreeItems +@pytest.mark.gui +def testGuiMain_UpdateTheme(qtbot, nwGUI): + """Test updating the theme in the GUI.""" + mainTheme = SHARED.theme + CONFIG.guiTheme = "default_dark" + CONFIG.guiSyntax = "default_dark" + mainTheme.loadTheme() + mainTheme.loadSyntax() + nwGUI._processConfigChanges(True, True, True, True) + + syntaxBack = QColor(*SHARED.theme.colBack) + + assert nwGUI.docEditor.palette().color(QPalette.ColorRole.Window) == syntaxBack + assert nwGUI.docEditor.docHeader.palette().color(QPalette.ColorRole.Window) == syntaxBack + assert nwGUI.docViewer.palette().color(QPalette.ColorRole.Window) == syntaxBack + assert nwGUI.docViewer.docHeader.palette().color(QPalette.ColorRole.Window) == syntaxBack + + # qtbot.stop() + +# END Test testGuiMain_UpdateTheme + + @pytest.mark.gui def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): """Test the document editor.""" diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 9bc452d7..e660c02c 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -23,7 +23,6 @@ from __future__ import annotations import pytest from pathlib import Path -from configparser import ConfigParser from mocked import causeOSError from tools import writeFile @@ -33,6 +32,7 @@ from PyQt5.QtWidgets import QApplication from novelwriter import CONFIG, SHARED from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType +from novelwriter.common import NWConfigParser from novelwriter.constants import nwLabels @@ -87,7 +87,7 @@ def testGuiTheme_Main(qtbot, nwGUI, tstPaths): # Parse Colours # ============= - parser = ConfigParser() + parser = NWConfigParser() parser["Palette"] = { "colour1": "100, 150, 200", "colour2": "100, 150, 200, 250", diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py index d734a117..d25abf92 100644 --- a/tests/test_tools/test_tools_manuscript.py +++ b/tests/test_tools/test_tools_manuscript.py @@ -30,7 +30,7 @@ from tools import C, buildTestProject, getGuiItem from mocked import causeOSError from PyQt5.QtCore import Qt, pyqtSlot -from PyQt5.QtWidgets import QDialogButtonBox +from PyQt5.QtWidgets import QAction, QDialogButtonBox from PyQt5.QtPrintSupport import QPrintPreviewDialog from novelwriter import CONFIG, SHARED @@ -49,9 +49,11 @@ def testManuscript_Init(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath: Pat SHARED.project.storage.getDocument(C.hChapterDoc).writeDocument("## A Chapter\n\n\t\tHi") allText = "New Novel\nBy Jane Doe\nA Chapter\n\t\tHi\n* * *" - manus = GuiManuscript(nwGUI) + nwGUI.mainMenu.aBuildManuscript.activate(QAction.Trigger) + qtbot.waitUntil(lambda: getGuiItem("GuiManuscript") is not None, timeout=1000) + manus = getGuiItem("GuiManuscript") + assert isinstance(manus, GuiManuscript) manus.show() - manus.loadContent() assert manus.docPreview.toPlainText().strip() == "" # Run the default build @@ -79,7 +81,6 @@ def testManuscript_Init(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath: Pat assert manus.docPreview.toPlainText().strip() == "" manus.close() - # Finish # qtbot.stop() # END Test testManuscript_Init