From 8154293d920a391aaf98feb78c3e04aa8c6c6fe9 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 13 Aug 2023 19:23:32 +0200 Subject: [PATCH] Move the hasProject logic into the shared class --- novelwriter/core/project.py | 30 ++++++++--- novelwriter/gui/projtree.py | 6 +-- novelwriter/guimain.py | 57 ++++++++++----------- novelwriter/shared.py | 14 ++++- tests/mocked.py | 7 --- tests/test_dialogs/test_dlg_projsettings.py | 6 +-- tests/test_gui/test_gui_guimain.py | 4 +- tests/tools.py | 2 +- 8 files changed, 71 insertions(+), 55 deletions(-) diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index b4bc16bf..de2b4814 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -75,13 +75,20 @@ class NWProject(QObject): self._session = NWSessionLog(self) # The session record # Project Status - self._langData = {} # Localisation data - self._projChanged = False # The project has unsaved changes - self._lockedBy = None # Data on which computer has the project open + self._langData = {} # Localisation data + self._lockedBy = None # Data on which computer has the project open + self._changed = False # The project has unsaved changes + self._valid = False # The project was successfully loaded # Internal Mapping self.tr = partial(QCoreApplication.translate, "NWProject") + logger.debug("Ready: NWProject") + + return + + def __del__(self): # pragma: no cover + logger.debug("Delete: NWProject") return ## @@ -118,7 +125,11 @@ class NWProject(QObject): @property def projChanged(self) -> bool: - return self._projChanged + return self._changed + + @property + def isValid(self) -> bool: + return self._valid ## # Item Methods @@ -212,7 +223,8 @@ class NWProject(QObject): # Project Status self._langData = {} - self._projChanged = False + self._changed = False + self._valid = False return @@ -338,6 +350,8 @@ class NWProject(QObject): self._session.startSession() self._storage.writeLockFile() self.setProjectChanged(False) + self._valid = True + self.mainGui.setStatus(self.tr("Opened Project: {0}").format(self._data.name)) return True @@ -503,9 +517,9 @@ class NWProject(QObject): information to the GUI statusbar. """ if isinstance(status, bool): - self._projChanged = status - self.projectStatusChanged.emit(self._projChanged) - return self._projChanged + self._changed = status + self.projectStatusChanged.emit(self._changed) + return self._changed ## # Getters diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 8974d602..61fa87d4 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -563,7 +563,7 @@ class GuiProjectTree(QTreeWidget): make sure the item is added in a place it can be added, and that other meta data is set correctly to ensure a valid project tree. """ - if not self.mainGui.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -787,7 +787,7 @@ class GuiProjectTree(QTreeWidget): can be called on any item, and will check whether to attempt a permanent deletion or moving the item to Trash. """ - if not self.mainGui.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -823,7 +823,7 @@ class GuiProjectTree(QTreeWidget): function only asks for confirmation once, and calls the regular deleteItem function for each document in the Trash folder. """ - if not self.mainGui.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 5de89756..537fa33f 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -116,7 +116,6 @@ class GuiMain(QMainWindow): SHARED.initSharedData(self, GuiTheme()) # Core Settings - self.hasProject = False self.isFocusMode = False self.idleRefTime = time() self.idleTime = 0.0 @@ -371,7 +370,7 @@ class GuiMain(QMainWindow): logger.info("Command line path: %s", cmdOpen) self.openProject(cmdOpen) - if not self.hasProject: + if not SHARED.hasProject: self.showProjectLoadDialog() # Determine whether release notes need to be shown or not @@ -396,7 +395,7 @@ class GuiMain(QMainWindow): def newProject(self, projData: dict | None = None) -> bool: """Create a new project via the new project wizard.""" - if self.hasProject: + if SHARED.hasProject: if not self.closeProject(): self.makeAlert(self.tr( "Cannot create a new project when another project is open." @@ -435,7 +434,7 @@ class GuiMain(QMainWindow): close application event so the user doesn't get prompted twice to confirm. """ - if not self.hasProject: + if not SHARED.hasProject: # There is no project loaded, everything OK return True @@ -468,12 +467,11 @@ class GuiMain(QMainWindow): self.outlineView.closeProjectTasks() self.novelView.closeProjectTasks() - SHARED.project.closeProject(self.idleTime) + SHARED.closeProject(self.idleTime) self.idleRefTime = time() self.idleTime = 0.0 self.clearGUI() - self.hasProject = False self._changeView(nwView.PROJECT) return saveOK @@ -530,7 +528,6 @@ class GuiMain(QMainWindow): return False # Project is loaded - self.hasProject = True self.idleRefTime = time() self.idleTime = 0.0 @@ -577,7 +574,7 @@ class GuiMain(QMainWindow): def saveProject(self, autoSave: bool = False) -> bool: """Save the current project.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False self.projView.saveProjectTasks() @@ -590,7 +587,7 @@ class GuiMain(QMainWindow): def closeDocument(self, beforeOpen: bool = False) -> bool: """Close the document and clear the editor and title field.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -610,7 +607,7 @@ class GuiMain(QMainWindow): def openDocument(self, tHandle: str | None, tLine: int | None = None, changeFocus: bool = True, doScroll: bool = False) -> bool: """Open a specific document, optionally at a given line.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -642,7 +639,7 @@ class GuiMain(QMainWindow): """Opens the next document in the project tree, following the document with the given handle. Stops when reaching the end. """ - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -671,7 +668,7 @@ class GuiMain(QMainWindow): def saveDocument(self) -> bool: """Save the current documents.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False self.docEditor.saveText() @@ -679,7 +676,7 @@ class GuiMain(QMainWindow): def viewDocument(self, tHandle: str | None = None, sTitle: str | None = None) -> bool: """Load a document for viewing in the view panel.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -724,7 +721,7 @@ class GuiMain(QMainWindow): """Import the text contained in an out-of-project text file, and insert the text into the currently open document. """ - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -796,7 +793,7 @@ class GuiMain(QMainWindow): active. It is not checked that the item is actually a document. That should be handled by the openDocument function. """ - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -825,7 +822,7 @@ class GuiMain(QMainWindow): def editItemLabel(self, tHandle: str | None = None) -> bool: """Open the edit item dialog.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -842,7 +839,7 @@ class GuiMain(QMainWindow): def rebuildIndex(self, beQuiet: bool = False) -> bool: """Rebuild the entire index.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -947,7 +944,7 @@ class GuiMain(QMainWindow): @pyqtSlot(int) def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> bool: """Open the project settings dialog.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -965,7 +962,7 @@ class GuiMain(QMainWindow): def showProjectDetailsDialog(self) -> bool: """Open the project details dialog.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -984,7 +981,7 @@ class GuiMain(QMainWindow): @pyqtSlot() def showBuildManuscriptDialog(self) -> bool: """Open the build manuscript dialog.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -1004,7 +1001,7 @@ class GuiMain(QMainWindow): def showLoremIpsumDialog(self) -> bool: """Open the insert lorem ipsum text dialog.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -1022,7 +1019,7 @@ class GuiMain(QMainWindow): def showProjectWordListDialog(self) -> bool: """Open the project word list dialog.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -1037,7 +1034,7 @@ class GuiMain(QMainWindow): def showWritingStatsDialog(self) -> bool: """Open the session stats dialog.""" - if not self.hasProject: + if not SHARED.hasProject: logger.error("No project open") return False @@ -1150,7 +1147,7 @@ class GuiMain(QMainWindow): def closeMain(self) -> bool: """Save everything, and close novelWriter.""" - if self.hasProject: + if SHARED.hasProject: msgYes = self.askQuestion("%s
%s" % ( self.tr("Do you want to exit novelWriter?"), self.tr("Changes are saved automatically.") @@ -1171,7 +1168,7 @@ class GuiMain(QMainWindow): # Ignore window size if in full screen mode CONFIG.setMainWinSize(self.width(), self.height()) - if self.hasProject: + if SHARED.hasProject: self.closeProject(True) CONFIG.saveConfig() @@ -1475,7 +1472,7 @@ class GuiMain(QMainWindow): @pyqtSlot() def _timeTick(self) -> None: """Process time tick of the main timer.""" - if not self.hasProject: + if not SHARED.hasProject: return currTime = time() @@ -1496,7 +1493,7 @@ class GuiMain(QMainWindow): @pyqtSlot() def _autoSaveProject(self) -> None: """Autosave of the project. This is a timer-activated slot.""" - doSave = self.hasProject + doSave = SHARED.hasProject doSave &= SHARED.project.projChanged doSave &= SHARED.project.storage.isOpen() if doSave: @@ -1507,7 +1504,7 @@ class GuiMain(QMainWindow): @pyqtSlot() def _autoSaveDocument(self) -> None: """Autosave of the document. This is a timer-activated slot.""" - if self.hasProject and self.docEditor.docChanged(): + if SHARED.hasProject and self.docEditor.docChanged(): logger.debug("Autosaving document") self.saveDocument() return @@ -1515,7 +1512,7 @@ class GuiMain(QMainWindow): @pyqtSlot() def _updateStatusWordCount(self) -> None: """Update the word count on the status bar.""" - if not self.hasProject: + if not SHARED.hasProject: self.mainStatus.setProjectStats(0, 0) SHARED.project.updateWordCounts() @@ -1551,7 +1548,7 @@ class GuiMain(QMainWindow): def _mainStackChanged(self, index: int) -> None: """Process main window tab change.""" if index == self.idxOutlineView: - if self.hasProject: + if SHARED.hasProject: self.outlineView.refreshTree() return diff --git a/novelwriter/shared.py b/novelwriter/shared.py index 68f10461..13a83935 100644 --- a/novelwriter/shared.py +++ b/novelwriter/shared.py @@ -26,6 +26,7 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING +from pathlib import Path if TYPE_CHECKING: # pragma: no cover from novelwriter.guimain import GuiMain @@ -64,6 +65,10 @@ class SharedData: raise Exception("UserData class not properly initialised") return self._project + @property + def hasProject(self) -> bool: + return self.project.isValid + ## # Methods ## @@ -79,7 +84,14 @@ class SharedData: logger.debug("SharedData instance initialised") return - def openProject(self): + def openProject(self, path: str | Path) -> None: + return + + def saveProject(self): + return + + def closeProject(self, idleTime: float) -> None: + self.project.closeProject(idleTime) return ## diff --git a/tests/mocked.py b/tests/mocked.py index 0fdd370f..f4a85e4b 100644 --- a/tests/mocked.py +++ b/tests/mocked.py @@ -31,9 +31,6 @@ class MockGuiMain(QObject): def __init__(self): super().__init__() - self._project = None - - self.hasProject = True self.mainStatus = MockStatusBar() self.projPath = "" @@ -44,10 +41,6 @@ class MockGuiMain(QObject): return - @property - def project(self): - return self._project - def postLaunchTasks(self, cmdOpen): return diff --git a/tests/test_dialogs/test_dlg_projsettings.py b/tests/test_dialogs/test_dlg_projsettings.py index c3137265..0eeb9f6e 100644 --- a/tests/test_dialogs/test_dlg_projsettings.py +++ b/tests/test_dialogs/test_dlg_projsettings.py @@ -27,7 +27,7 @@ from PyQt5.QtGui import QColor from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QDialog, QAction, QColorDialog -from novelwriter import CONFIG +from novelwriter import CONFIG, SHARED from novelwriter.enum import nwItemType from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.projsettings import GuiProjectSettings @@ -50,8 +50,8 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI): assert getGuiItem("GuiProjectSettings") is None # Pretend we have a project - nwGUI.hasProject = True - nwGUI.project.data.setSpellLang("en") + SHARED.project._valid = True + SHARED.project.data.setSpellLang("en") # Get the dialog object nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger) diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index c46cac96..7df548d4 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -30,7 +30,7 @@ from tools import ( from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QDialog, QMessageBox, QInputDialog -from novelwriter import CONFIG +from novelwriter import CONFIG, SHARED from novelwriter.enum import nwItemType, nwView, nwWidget from novelwriter.constants import nwFiles from novelwriter.gui.outline import GuiOutlineView @@ -113,7 +113,7 @@ def testGuiMain_NewProject(monkeypatch, nwGUI, projPath): # Close project with monkeypatch.context() as mp: - nwGUI.hasProject = True + SHARED.project._valid = True mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No) assert nwGUI.newProject(projData={"projPath": projPath}) is False diff --git a/tests/tools.py b/tests/tools.py index f8f3f03c..9b896795 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -204,9 +204,9 @@ def buildTestProject(obj, projPath): project.session.startSession() project.setProjectChanged(True) project.saveProject(autoSave=True) + project._valid = True if nwGUI is not None: - nwGUI.hasProject = True nwGUI.rebuildTrees() return