diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 05bebca0..cecb6092 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -78,9 +78,6 @@ class NWProject: self.projChanged = False # The project has unsaved changes self.projAltered = False # The project has been altered this session self.lockedBy = None # Data on which computer has the project open - self.saveCount = 0 # Meta data: number of saves - self.autoCount = 0 # Meta data: number of automatic saves - self.editTime = 0 # The accumulated edit time read from the project file # Class Settings self.projPath = None # The full path to where the currently open project is saved @@ -92,10 +89,6 @@ class NWProject: self.projLang = None # The project language, used for builds self.projFiles = [] # A list of all files in the content folder on load - # Project Meta - self.bookTitle = "" # The final title; should only be used for exports - self.bookAuthors = [] # A list of book authors - # Project Settings self.autoReplace = {} # Text to auto-replace on exports self.titleFormat = {} # The formatting of titles for exports @@ -253,12 +246,12 @@ class NWProject: self.projOpened = 0 self.projChanged = False self.projAltered = False - self.saveCount = 0 - self.autoCount = 0 # Project Tree self._projTree.clear() + self._data = NWProjectData() + # Project Settings self.projPath = None self.projMeta = None @@ -268,8 +261,6 @@ class NWProject: self.projSpell = None self.projLang = None self.projFiles = [] - self.bookTitle = "" - self.bookAuthors = [] self.autoReplace = {} self.titleFormat = { "title": "%title%", @@ -332,16 +323,18 @@ class NWProject: if not self.setProjectPath(projPath, newProject=True): return False - self.data.setName(projName) - self.setBookTitle(projTitle) - self.setBookAuthors(projAuthors) + self._data.setName(projName) + self._data.setTitle(projTitle) + self._data.setAuthors(projAuthors) hNovelRoot = self.newRoot(nwItemClass.NOVEL) hTitlePage = self.newFile(self.tr("Title Page"), hNovelRoot) - titlePage = "#! %s\n\n" % (self.bookTitle if self.bookTitle else self._data.name) - if self.bookAuthors: - titlePage = "%s>> %s %s <<\n" % (titlePage, self.tr("By"), self.getAuthors()) + titlePage = "#! %s\n\n" % (self._data.title if self._data.title else self._data.name) + if self._data.authors: + titlePage = "%s>> %s %s <<\n" % ( + titlePage, self.tr("By"), self._data.getAuthors(self.tr("and")) + ) aDoc = NWDoc(self, hTitlePage) aDoc.writeDocument(titlePage) @@ -557,14 +550,8 @@ class NWProject: # Extract Data # ============ - self.bookTitle = self._data.title - self.bookAuthors = self._data.autors - self.saveCount = self._data.saveCount - self.autoCount = self._data.autoCount - self.editTime = self._data.editTime - logger.info("Project Name: '%s'", self._data.name) - logger.info("Project Title: '%s'", self.bookTitle) + logger.info("Project Title: '%s'", self._data.title) xmlSettings = xmlData.get("settings", {}) @@ -646,9 +633,9 @@ class NWProject: logger.info("Saving project: %s", self.projPath) if autoSave: - self.autoCount += 1 + self._data.incAutoCount() else: - self.saveCount += 1 + self._data.incSaveCount() # Root element and project details logger.debug("Writing project meta") @@ -660,15 +647,15 @@ class NWProject: }) self.updateWordCounts() - editTime = int(self.editTime + saveTime - self.projOpened) + editTime = int(self._data.editTime + saveTime - self.projOpened) # Save Project Meta xProject = etree.SubElement(nwXML, "project") self._packProjectValue(xProject, "name", self._data.name) - self._packProjectValue(xProject, "title", self.bookTitle) - self._packProjectValue(xProject, "author", self.bookAuthors) - self._packProjectValue(xProject, "saveCount", str(self.saveCount)) - self._packProjectValue(xProject, "autoCount", str(self.autoCount)) + self._packProjectValue(xProject, "title", self._data.title) + self._packProjectValue(xProject, "author", self._data.authors) + self._packProjectValue(xProject, "saveCount", str(self._data.saveCount)) + self._packProjectValue(xProject, "autoCount", str(self._data.autoCount)) self._packProjectValue(xProject, "editTime", str(editTime)) # Save Project Settings @@ -955,30 +942,6 @@ class NWProject: return True - def setBookTitle(self, bookTitle): - """Set the book title, that is, the title to include in exports. - """ - self.bookTitle = simplified(bookTitle) - self.setProjectChanged(True) - return True - - def setBookAuthors(self, bookAuthors): - """A line-separated list of authors, parsed into an array. - """ - if not isinstance(bookAuthors, str): - return False - - self.bookAuthors = [] - for bookAuthor in bookAuthors.splitlines(): - bookAuthor = simplified(bookAuthor) - if bookAuthor == "": - continue - self.bookAuthors.append(bookAuthor) - - self.setProjectChanged(True) - - return True - def setProjBackup(self, doBackup): """Set whether projects should be backed up or not. The user will be notified in case required settings are missing. @@ -1116,26 +1079,11 @@ class NWProject: # Getters ## - def getAuthors(self): - """Return a formatted string of authors. - """ - nAuth = len(self.bookAuthors) - authString = "" - - if nAuth == 1: - authString = self.bookAuthors[0] - elif nAuth > 1: - authString = "%s %s %s" % ( - ", ".join(self.bookAuthors[0:-1]), self.tr("and"), self.bookAuthors[-1] - ) - - return authString - def getCurrentEditTime(self): """Get the total project edit time, including the time spent in the current session. """ - return round(self.editTime + time() - self.projOpened) + return round(self._data.editTime + time() - self.projOpened) def getProjectItems(self): """This function ensures that the item tree loaded is sent to diff --git a/novelwriter/core/projectdata.py b/novelwriter/core/projectdata.py index f5719949..49ac64af 100644 --- a/novelwriter/core/projectdata.py +++ b/novelwriter/core/projectdata.py @@ -60,7 +60,7 @@ class NWProjectData: return self._title @property - def autors(self): + def authors(self): return self._authors @property @@ -75,6 +75,44 @@ class NWProjectData: def editTime(self): return self._editTime + ## + # Methods + ## + + def addAuthor(self, value): + self._authors.append(simplified(str(value))) + self._changed = True + return + + def incSaveCount(self): + self._saveCount += 1 + self._changed = True + return + + def incAutoCount(self): + self._autoCount += 1 + self._changed = True + return + + ## + # Getters + ## + + def getAuthors(self, trAnd="and"): + """Return a formatted string of authors. + """ + nAuth = len(self._authors) + authors = "" + + if nAuth == 1: + authors = self._authors[0] + elif nAuth > 1: + authors = "%s %s %s" % ( + ", ".join(self._authors[0:-1]), trAnd, self._authors[-1] + ) + + return authors + ## # Setters ## @@ -89,9 +127,17 @@ class NWProjectData: self._changed = True return - def addAuthor(self, value): - self._authors.append(simplified(str(value))) + def setAuthors(self, value): + self._authors = [] self._changed = True + if isinstance(value, str): + for author in value.splitlines(): + author = simplified(author) + if author: + self.addAuthor(author) + self._changed = True + elif isinstance(value, list): + self._authors = value return def setSaveCount(self, value): diff --git a/novelwriter/core/toodt.py b/novelwriter/core/toodt.py index 022bda4c..b4306f30 100644 --- a/novelwriter/core/toodt.py +++ b/novelwriter/core/toodt.py @@ -261,8 +261,8 @@ class ToOdt(Tokenizer): # =============== if self._headerText == "": - theTitle = self.theProject.bookTitle - theAuth = self.theProject.getAuthors() + theTitle = self.theProject.data.title + theAuth = self.theProject.data.getAuthors(self.tr("and")) self._headerText = f"{theTitle} / {theAuth} /" # Create Roots diff --git a/novelwriter/dialogs/projdetails.py b/novelwriter/dialogs/projdetails.py index 368c25ee..0d916205 100644 --- a/novelwriter/dialogs/projdetails.py +++ b/novelwriter/dialogs/projdetails.py @@ -157,7 +157,7 @@ class GuiProjectDetailsMain(QWidget): # Header # ====== - self.bookTitle = QLabel(self.theProject.bookTitle) + self.bookTitle = QLabel(self.theProject.data.title) bookFont = self.bookTitle.font() bookFont.setPointSizeF(2.2*fPt) bookFont.setWeight(QFont.Bold) @@ -175,7 +175,9 @@ class GuiProjectDetailsMain(QWidget): self.projName.setAlignment(Qt.AlignHCenter) self.projName.setWordWrap(True) - self.bookAuthors = QLabel(self.tr("By {0}").format(self.theProject.getAuthors())) + self.bookAuthors = QLabel(self.tr("By {0}").format( + self.theProject.data.getAuthors(self.tr("and")) + )) authFont = self.bookAuthors.font() authFont.setPointSizeF(1.2*fPt) self.bookAuthors.setFont(authFont) @@ -255,7 +257,7 @@ class GuiProjectDetailsMain(QWidget): self.wordCountVal.setText(f"{nwCount:n}") self.chapCountVal.setText(f"{hCounts[2]:n}") self.sceneCountVal.setText(f"{hCounts[3]:n}") - self.revCountVal.setText(f"{self.theProject.saveCount:n}") + self.revCountVal.setText(f"{self.theProject.data.saveCount:n}") self.editTimeVal.setText(f"{edTime//3600:02d}:{edTime%3600//60:02d}") self.projPathVal.setText(self.theProject.projPath) diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py index 78165613..988b520b 100644 --- a/novelwriter/dialogs/projsettings.py +++ b/novelwriter/dialogs/projsettings.py @@ -115,8 +115,8 @@ class GuiProjectSettings(PagedDialog): doBackup = not self.tabMain.doBackup.isChecked() self.theProject.data.setName(projName) - self.theProject.setBookTitle(bookTitle) - self.theProject.setBookAuthors(bookAuthors) + self.theProject.data.setTitle(bookTitle) + self.theProject.data.setAuthors(bookAuthors) self.theProject.setProjBackup(doBackup) # Remember this as updating spell dictionary can be expensive @@ -219,7 +219,7 @@ class GuiProjectEditMain(QWidget): self.editTitle = QLineEdit() self.editTitle.setMaxLength(200) self.editTitle.setMaximumWidth(xW) - self.editTitle.setText(self.theProject.bookTitle) + self.editTitle.setText(self.theProject.data.title) self.mainForm.addRow( self.tr("Novel title"), self.editTitle, @@ -229,7 +229,7 @@ class GuiProjectEditMain(QWidget): self.editAuthors = QPlainTextEdit() self.editAuthors.setMaximumHeight(xH) self.editAuthors.setMaximumWidth(xW) - self.editAuthors.setPlainText("\n".join(self.theProject.bookAuthors)) + self.editAuthors.setPlainText("\n".join(self.theProject.data.authors)) self.mainForm.addRow( self.tr("Author(s)"), self.editAuthors, diff --git a/novelwriter/tools/build.py b/novelwriter/tools/build.py index 3529d577..e9aa37cd 100644 --- a/novelwriter/tools/build.py +++ b/novelwriter/tools/build.py @@ -973,8 +973,8 @@ class GuiBuildNovel(QDialog): jsonData = { "meta": { "workingTitle": self.theProject.data.name, - "novelTitle": self.theProject.bookTitle, - "authors": self.theProject.bookAuthors, + "novelTitle": self.theProject.data.title, + "authors": self.theProject.data.authors, "buildTime": self.buildTime, } } diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py index 9ac7271e..16ba37d4 100644 --- a/tests/test_core/test_core_project.py +++ b/tests/test_core/test_core_project.py @@ -555,22 +555,22 @@ def testCoreProject_Save(monkeypatch, nwMinimal, mockGUI, refDir): assert os.path.isfile(backFile) is False # Successful save - saveCount = theProject.saveCount - autoCount = theProject.autoCount + saveCount = theProject.data.saveCount + autoCount = theProject.data.autoCount assert theProject.saveProject() is True - assert theProject.saveCount == saveCount + 1 - assert theProject.autoCount == autoCount + assert theProject.data.saveCount == saveCount + 1 + assert theProject.data.autoCount == autoCount assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE) # Check that a second save creates a .bak file assert os.path.isfile(backFile) is True # Successful autosave - saveCount = theProject.saveCount - autoCount = theProject.autoCount + saveCount = theProject.data.saveCount + autoCount = theProject.data.autoCount assert theProject.saveProject(autoSave=True) is True - assert theProject.saveCount == saveCount - assert theProject.autoCount == autoCount + 1 + assert theProject.data.saveCount == saveCount + assert theProject.data.autoCount == autoCount + 1 assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE) # Close test project @@ -902,30 +902,31 @@ def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir, mockRnd): assert theProject.data.name == "A Name" # Project Title - theProject.setBookTitle(" A Title ") - assert theProject.bookTitle == "A Title" + theProject.data.setTitle(" A Title ") + assert theProject.data.title == "A Title" # Project Authors # Check that the list is cleaned up and that it can be extracted as # a properly formatted string, depending on number of names - assert not theProject.setBookAuthors([]) - assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ") - assert theProject.bookAuthors == ["Jane Doe", "John Doh"] + theProject.data.setAuthors([]) + assert theProject.data.authors == [] + theProject.data.setAuthors(" Jane Doe \n John Doh \n ") + assert theProject.data.authors == ["Jane Doe", "John Doh"] - assert theProject.setBookAuthors("") - assert theProject.getAuthors() == "" + theProject.data.setAuthors("") + assert theProject.data.getAuthors() == "" - assert theProject.setBookAuthors("Jane Doe") - assert theProject.getAuthors() == "Jane Doe" + theProject.data.setAuthors("Jane Doe") + assert theProject.data.getAuthors() == "Jane Doe" - assert theProject.setBookAuthors("Jane Doe\nJohn Doh") - assert theProject.getAuthors() == "Jane Doe and John Doh" + theProject.data.setAuthors("Jane Doe\nJohn Doh") + assert theProject.data.getAuthors() == "Jane Doe and John Doh" - assert theProject.setBookAuthors("Jane Doe\nJohn Doh\nBod Owens") - assert theProject.getAuthors() == "Jane Doe, John Doh and Bod Owens" + theProject.data.setAuthors("Jane Doe\nJohn Doh\nBod Owens") + assert theProject.data.getAuthors() == "Jane Doe, John Doh and Bod Owens" # Edit Time - theProject.editTime = 1234 + theProject.data.setEditTime(1234) theProject.projOpened = 1600000000 with monkeypatch.context() as mp: mp.setattr("novelwriter.core.project.time", lambda: 1600005600) diff --git a/tests/test_dialogs/test_dlg_projdetails.py b/tests/test_dialogs/test_dlg_projdetails.py index 84360d11..c7d86670 100644 --- a/tests/test_dialogs/test_dlg_projdetails.py +++ b/tests/test_dialogs/test_dlg_projdetails.py @@ -55,7 +55,7 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, nwLipsum): assert projDet.tabMain.wordCountVal.text() == f"{3000:n}" assert projDet.tabMain.chapCountVal.text() == f"{3:n}" assert projDet.tabMain.sceneCountVal.text() == f"{5:n}" - assert projDet.tabMain.revCountVal.text() == f"{nwGUI.theProject.saveCount:n}" + assert projDet.tabMain.revCountVal.text() == f"{nwGUI.theProject.data.saveCount:n}" assert projDet.tabMain.projPathVal.text() == nwLipsum diff --git a/tests/test_dialogs/test_dlg_projsettings.py b/tests/test_dialogs/test_dlg_projsettings.py index 7e8b4e1d..71c90ba8 100644 --- a/tests/test_dialogs/test_dlg_projsettings.py +++ b/tests/test_dialogs/test_dlg_projsettings.py @@ -96,7 +96,7 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd # Set some values theProject = nwGUI.theProject theProject.setSpellLang("en") - theProject.setBookAuthors("Jane Smith\nJohn Smith") + theProject.data.setAuthors("Jane Smith\nJohn Smith") theProject.setAutoReplace({"A": "B", "C": "D"}) # Create Dialog @@ -137,8 +137,8 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd projSettings._doSave() assert theProject.data.name == "Project Name" - assert theProject.bookTitle == "Project Title" - assert theProject.bookAuthors == ["Jane Doe", "John Doh"] + assert theProject.data.title == "Project Title" + assert theProject.data.authors == ["Jane Doe", "John Doh"] # Clean up projSettings._doClose() diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 4ea895e6..fda4edea 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -172,9 +172,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock assert nwGUI.theProject.tree.trashRoot() is None assert nwGUI.theProject.projPath is None assert nwGUI.theProject.projMeta is None - assert nwGUI.theProject.data.name == "New Project" - assert nwGUI.theProject.bookTitle == "" - assert len(nwGUI.theProject.bookAuthors) == 0 + assert nwGUI.theProject.data.name == "" + assert nwGUI.theProject.data.title == "" + assert nwGUI.theProject.data.authors == [] assert not nwGUI.theProject.spellCheck # Check the files @@ -195,8 +195,8 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock assert nwGUI.theProject.projPath == fncProj assert nwGUI.theProject.projMeta == os.path.join(fncProj, "meta") assert nwGUI.theProject.data.name == "New Project" - assert nwGUI.theProject.bookTitle == "New Novel" - assert len(nwGUI.theProject.bookAuthors) == 1 + assert nwGUI.theProject.data.title == "New Novel" + assert nwGUI.theProject.data.authors == ["Jane Doe"] assert nwGUI.theProject.spellCheck is False # Check that tree items have been created diff --git a/tests/tools.py b/tests/tools.py index bf8d2c40..82e528fe 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -168,8 +168,8 @@ def buildTestProject(theObject, projPath): theProject.clearProject() theProject.setProjectPath(projPath, newProject=True) theProject.data.setName("New Project") - theProject.setBookTitle("New Novel") - theProject.setBookAuthors("Jane Doe") + theProject.data.setTitle("New Novel") + theProject.data.setAuthors("Jane Doe") # Creating a minimal project with a few root folders and a # single chapter folder with a single file.