Move project name to project data class

This commit is contained in:
Veronica Berglyd Olsen
2022-10-30 23:18:18 +01:00
parent ea80fd3c71
commit e887ec6991
13 changed files with 174 additions and 73 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ class NWIndex:
return
def __repr__(self):
return f"<NWIndex project='{self.theProject.projName}'>"
return f"<NWIndex project='{self.theProject.data.name}'>"
##
# Properties
+29 -35
View File
@@ -38,9 +38,10 @@ from PyQt5.QtCore import QCoreApplication
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.error import logException
from novelwriter.common import (
checkString, checkStringNone, isHandle, formatTimeStamp,
makeFileNameSafe, hexToInt, minmax, simplified
checkString, checkStringNone, isHandle, formatTimeStamp, makeFileNameSafe,
hexToInt, minmax, simplified
)
from novelwriter.constants import trConst, nwFiles, nwLabels
from novelwriter.core.tree import NWTree
from novelwriter.core.item import NWItem
from novelwriter.core.index import NWIndex
@@ -48,7 +49,7 @@ from novelwriter.core.status import NWStatus
from novelwriter.core.options import OptionState
from novelwriter.core.document import NWDoc
from novelwriter.core.projectxml import ProjectXMLReader, XMLReadState
from novelwriter.constants import trConst, nwFiles, nwLabels
from novelwriter.core.projectdata import NWProjectData
logger = logging.getLogger(__name__)
@@ -63,7 +64,8 @@ class NWProject:
self.mainConf = novelwriter.CONFIG
self.mainGui = mainGui
self._data = {}
self._data = NWProjectData()
self._raw = {}
# Core Elements
self._optState = OptionState(self) # Project-specific GUI options
@@ -91,7 +93,6 @@ class NWProject:
self.projFiles = [] # A list of all files in the content folder on load
# Project Meta
self.projName = "" # Project name
self.bookTitle = "" # The final title; should only be used for exports
self.bookAuthors = [] # A list of book authors
@@ -125,6 +126,10 @@ class NWProject:
# Properties
##
@property
def data(self):
return self._data
@property
def index(self):
return self._projIndex
@@ -263,7 +268,6 @@ class NWProject:
self.projSpell = None
self.projLang = None
self.projFiles = []
self.projName = ""
self.bookTitle = ""
self.bookAuthors = []
self.autoReplace = {}
@@ -328,14 +332,14 @@ class NWProject:
if not self.setProjectPath(projPath, newProject=True):
return False
self.setProjectName(projName)
self.data.setName(projName)
self.setBookTitle(projTitle)
self.setBookAuthors(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.projName)
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())
@@ -480,8 +484,9 @@ class NWProject:
# Open The Project XML File
# =========================
self._data = NWProjectData()
xmlReader = ProjectXMLReader(fileName)
xmlParsed = xmlReader.read()
xmlParsed = xmlReader.read(self._data)
xmlData = xmlReader.data
print(json.dumps(xmlData, indent=2))
@@ -510,7 +515,7 @@ class NWProject:
self.clearProject()
return False
self._data = xmlData
self._raw = xmlData
logger.debug("XML root is '%s'", nwxRoot)
logger.debug("File version is '%s'", xmlVersion)
@@ -552,16 +557,13 @@ class NWProject:
# Extract Data
# ============
xmlProject = xmlData.get("project", {})
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
self.projName = xmlProject.get("name", "")
self.bookTitle = xmlProject.get("title", "")
self.bookAuthors = xmlProject.get("authors", [])
self.saveCount = xmlProject.get("saveCount", 0)
self.autoCount = xmlProject.get("autoCount", 0)
self.editTime = xmlProject.get("editTime", 0)
logger.info("Project Name: '%s'", self.projName)
logger.info("Project Name: '%s'", self._data.name)
logger.info("Project Title: '%s'", self.bookTitle)
xmlSettings = xmlData.get("settings", {})
@@ -601,7 +603,7 @@ class NWProject:
self._deprecatedFiles()
# Update recent projects
self.mainConf.updateRecentCache(self.projPath, self.projName, self.lastWCount, time())
self.mainConf.updateRecentCache(self.projPath, self._data.name, self.lastWCount, time())
self.mainConf.saveRecentCache()
# Check the project tree consistency
@@ -621,7 +623,7 @@ class NWProject:
self._writeLockFile()
self.setProjectChanged(False)
self.mainGui.setStatus(self.tr("Opened Project: {0}").format(self.projName))
self.mainGui.setStatus(self.tr("Opened Project: {0}").format(self._data.name))
return True
@@ -662,7 +664,7 @@ class NWProject:
# Save Project Meta
xProject = etree.SubElement(nwXML, "project")
self._packProjectValue(xProject, "name", self.projName)
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))
@@ -734,11 +736,11 @@ class NWProject:
self._optState.saveSettings()
# Update recent projects
self.mainConf.updateRecentCache(self.projPath, self.projName, self.currWCount, saveTime)
self.mainConf.updateRecentCache(self.projPath, self._data.name, self.currWCount, saveTime)
self.mainConf.saveRecentCache()
self._writeLockFile()
self.mainGui.setStatus(self.tr("Saved Project: {0}").format(self.projName))
self.mainGui.setStatus(self.tr("Saved Project: {0}").format(self._data.name))
self.setProjectChanged(False)
return True
@@ -800,14 +802,14 @@ class NWProject:
), nwAlert.ERROR)
return False
if not self.projName:
if not self._data.name:
self.mainGui.makeAlert(self.tr(
"Cannot backup project because no project name is set. "
"Please set a Working Title in Project Settings."
), nwAlert.ERROR)
return False
cleanName = makeFileNameSafe(self.projName)
cleanName = makeFileNameSafe(self._data.name)
baseDir = os.path.abspath(os.path.join(self.mainConf.backupPath, cleanName))
if not os.path.isdir(baseDir):
try:
@@ -953,14 +955,6 @@ class NWProject:
return True
def setProjectName(self, projName):
"""Set the project name, This is the the name used for backup
files etc.
"""
self.projName = simplified(projName)
self.setProjectChanged(True)
return True
def setBookTitle(self, bookTitle):
"""Set the book title, that is, the title to include in exports.
"""
@@ -998,7 +992,7 @@ class NWProject:
), nwAlert.WARN)
return False
if self.projName == "":
if self._data.name == "":
self.mainGui.makeAlert(self.tr(
"You must set a valid project name in Project Settings to "
"use the automatic project backup feature."
+112
View File
@@ -0,0 +1,112 @@
"""
novelWriter Project Data Class
================================
Class for holding the project settings
File History:
Created: 2022-10-30 [2.0rc1]
This file is a part of novelWriter
Copyright 20182022, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
from novelwriter.common import checkInt, simplified
logger = logging.getLogger(__name__)
class NWProjectData:
def __init__(self):
# Project Meta
self._name = ""
self._title = ""
self._authors = []
self._saveCount = 0
self._autoCount = 0
self._editTime = 0
# Internal
self._changed = False
return
##
# Properties
##
@property
def name(self):
return self._name
@property
def title(self):
return self._title
@property
def autors(self):
return self._authors
@property
def saveCount(self):
return self._saveCount
@property
def autoCount(self):
return self._autoCount
@property
def editTime(self):
return self._editTime
##
# Setters
##
def setName(self, value):
self._name = simplified(str(value))
self._changed = True
return
def setTitle(self, value):
self._title = simplified(str(value))
self._changed = True
return
def addAuthor(self, value):
self._authors.append(simplified(str(value)))
self._changed = True
return
def setSaveCount(self, value):
self._saveCount = checkInt(value, 0)
self._changed = True
return
def setAutoCount(self, value):
self._autoCount = checkInt(value, 0)
self._changed = True
return
def setEditTime(self, value):
self._editTime = checkInt(value, 0)
self._changed = True
return
# END Class NWProjectData
+11 -16
View File
@@ -4,7 +4,8 @@ novelWriter Project XML Read/Write
Classes for reading and writing the project XML file
File History:
Created: 2022-09-28 [1.7.b1]
Created: 2022-09-28 [2.0rc1] ProjectXMLReader
Created: 2022-09-28 [2.0rc1] XMLReadState
This file is a part of novelWriter
Copyright 20182022, Veronica Berglyd Olsen
@@ -90,7 +91,7 @@ class ProjectXMLReader:
# Methods
##
def read(self):
def read(self, projData):
"""Read and parse the project XML file.
"""
self._data = {}
@@ -154,7 +155,7 @@ class ProjectXMLReader:
status = True
for xSection in xRoot:
if xSection.tag == "project":
status &= self._parseProjectMeta(xSection)
status &= self._parseProjectMeta(xSection, projData)
elif xSection.tag == "settings":
status &= self._parseProjectSettings(xSection)
elif xSection.tag == "content":
@@ -180,31 +181,25 @@ class ProjectXMLReader:
# Internal Functions
##
def _parseProjectMeta(self, xSection):
def _parseProjectMeta(self, xSection, projData):
"""Parse the project section of the XML file.
"""
logger.debug("Parsing xml <root/project>")
data = {}
authors = []
for xItem in xSection:
if xItem.tag == "name":
data["name"] = simplified(checkString(xItem.text, ""))
projData.setName(xItem.text)
elif xItem.tag == "title":
data["title"] = simplified(checkString(xItem.text, ""))
projData.setTitle(xItem.text)
elif xItem.tag == "author":
authors.append(simplified(checkString(xItem.text, "")))
projData.addAuthor(xItem.text)
elif xItem.tag == "saveCount":
data["saveCount"] = checkInt(xItem.text, 0)
projData.setSaveCount(xItem.text)
elif xItem.tag == "autoCount":
data["autoCount"] = checkInt(xItem.text, 0)
projData.setAutoCount(xItem.text)
elif xItem.tag == "editTime":
data["editTime"] = checkInt(xItem.text, 0)
projData.setEditTime(xItem.text)
else:
logger.warning("Ignored <root/project/%s> in xml", xItem.tag)
data["authors"] = authors
self._data["project"] = data
return True
def _parseProjectSettings(self, xSection):
+1 -1
View File
@@ -315,7 +315,7 @@ class ToHtml(Tokenizer):
"</body>\n"
"</html>\n"
).format(
projTitle=self.theProject.projName,
projTitle=self.theProject.data.name,
htmlStyle="\n".join(theStyle),
bodyText=bodyText,
)
+1 -1
View File
@@ -166,7 +166,7 @@ class GuiProjectDetailsMain(QWidget):
self.bookTitle.setWordWrap(True)
self.projName = QLabel(
self.tr("Working Title: {0}").format(self.theProject.projName)
self.tr("Working Title: {0}").format(self.theProject.data.name)
)
workFont = self.projName.font()
workFont.setPointSizeF(0.8*fPt)
+2 -2
View File
@@ -114,7 +114,7 @@ class GuiProjectSettings(PagedDialog):
spellLang = self.tabMain.spellLang.currentData()
doBackup = not self.tabMain.doBackup.isChecked()
self.theProject.setProjectName(projName)
self.theProject.data.setName(projName)
self.theProject.setBookTitle(bookTitle)
self.theProject.setBookAuthors(bookAuthors)
self.theProject.setProjBackup(doBackup)
@@ -209,7 +209,7 @@ class GuiProjectEditMain(QWidget):
self.editName = QLineEdit()
self.editName.setMaxLength(200)
self.editName.setMaximumWidth(xW)
self.editName.setText(self.theProject.projName)
self.editName.setText(self.theProject.data.name)
self.mainForm.addRow(
self.tr("Project name"),
self.editName,
+3 -3
View File
@@ -383,7 +383,7 @@ class GuiMain(QMainWindow):
self.mainStatus.setDocumentStatus(nwState.NONE)
self.mainStatus.setStatus(self.tr("New project created ..."))
self._updateWindowTitle(self.theProject.projName)
self._updateWindowTitle(self.theProject.data.name)
else:
self.theProject.clearProject()
@@ -521,7 +521,7 @@ class GuiMain(QMainWindow):
self.theProject.index.loadIndex()
# Update GUI
self._updateWindowTitle(self.theProject.projName)
self._updateWindowTitle(self.theProject.data.name)
self.rebuildTrees()
self.docEditor.setDictionaries()
self.docEditor.toggleSpellCheck(self.theProject.spellCheck)
@@ -960,7 +960,7 @@ class GuiMain(QMainWindow):
if dlgProj.spellChanged:
self.docEditor.setDictionaries()
self.itemDetails.refreshDetails()
self._updateWindowTitle(self.theProject.projName)
self._updateWindowTitle(self.theProject.data.name)
return True
+2 -2
View File
@@ -888,7 +888,7 @@ class GuiBuildNovel(QDialog):
# Generate File Name
# ==================
cleanName = makeFileNameSafe(self.theProject.projName)
cleanName = makeFileNameSafe(self.theProject.data.name)
fileName = "%s.%s" % (cleanName, fileExt)
saveDir = self.mainConf.lastPath
if not os.path.isdir(saveDir):
@@ -972,7 +972,7 @@ class GuiBuildNovel(QDialog):
elif theFmt == self.FMT_JSON_H or theFmt == self.FMT_JSON_M:
jsonData = {
"meta": {
"workingTitle": self.theProject.projName,
"workingTitle": self.theProject.data.name,
"novelTitle": self.theProject.bookTitle,
"authors": self.theProject.bookAuthors,
"buildTime": self.buildTime,
+8 -8
View File
@@ -202,7 +202,7 @@ def testCoreProject_NewSampleA(fncDir, tmpConf, mockGUI, tmpDir):
assert theProject.newProject(projData) is True
assert theProject.openProject(fncDir) is True
assert theProject.projName == "Sample Project"
assert theProject.data.name == "Sample Project"
assert theProject.saveProject() is True
assert theProject.closeProject() is True
os.unlink(dstSample)
@@ -236,7 +236,7 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, mockGUI, tmpDir):
monkeypatch.setattr(nwFiles, "PROJ_FILE", "nwProject.nwx")
assert theProject.newProject(projData) is True
assert theProject.openProject(fncDir) is True
assert theProject.projName == "Sample Project"
assert theProject.data.name == "Sample Project"
assert theProject.saveProject() is True
assert theProject.closeProject() is True
@@ -895,8 +895,8 @@ def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir, mockRnd):
assert theProject.setProjectPath(fncDir)
# Project Name
assert theProject.setProjectName(" A Name ")
assert theProject.projName == "A Name"
assert theProject.data.setName(" A Name ")
assert theProject.data.name == "A Name"
# Project Title
assert theProject.setBookTitle(" A Title ")
@@ -944,9 +944,9 @@ def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir, mockRnd):
theProject.mainConf.backupPath = tmpDir
assert theProject.setProjBackup(True)
assert theProject.setProjectName("")
assert theProject.data.setName("")
assert not theProject.setProjBackup(True)
assert theProject.setProjectName("A Name")
assert theProject.data.setName("A Name")
assert theProject.setProjBackup(True)
# Spell check
@@ -1327,12 +1327,12 @@ def testCoreProject_Backup(monkeypatch, mockGUI, nwMinimal, tmpDir):
# Missing project name
theProject.mainConf.backupPath = tmpDir
theProject.projName = ""
theProject.data.name = ""
assert theProject.zipIt(doNotify=False) is False
# Non-existent folder
theProject.mainConf.backupPath = os.path.join(tmpDir, "nonexistent")
theProject.projName = "Test Minimal"
theProject.data.name = "Test Minimal"
assert theProject.zipIt(doNotify=False) is False
# Same folder as project (causes infinite loop in zipping)
+1 -1
View File
@@ -136,7 +136,7 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd
assert projSettings.spellChanged is False
projSettings._doSave()
assert theProject.projName == "Project Name"
assert theProject.data.name == "Project Name"
assert theProject.bookTitle == "Project Title"
assert theProject.bookAuthors == ["Jane Doe", "John Doh"]
+2 -2
View File
@@ -172,7 +172,7 @@ 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.projName == ""
assert nwGUI.theProject.data.name == ""
assert nwGUI.theProject.bookTitle == ""
assert len(nwGUI.theProject.bookAuthors) == 0
assert not nwGUI.theProject.spellCheck
@@ -194,7 +194,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
assert nwGUI.theProject.tree.trashRoot() is None
assert nwGUI.theProject.projPath == fncProj
assert nwGUI.theProject.projMeta == os.path.join(fncProj, "meta")
assert nwGUI.theProject.projName == "New Project"
assert nwGUI.theProject.data.name == "New Project"
assert nwGUI.theProject.bookTitle == "New Novel"
assert len(nwGUI.theProject.bookAuthors) == 1
assert nwGUI.theProject.spellCheck is False
+1 -1
View File
@@ -167,7 +167,7 @@ def buildTestProject(theObject, projPath):
theProject.clearProject()
theProject.setProjectPath(projPath, newProject=True)
theProject.setProjectName("New Project")
theProject.data.setName("New Project")
theProject.setBookTitle("New Novel")
theProject.setBookAuthors("Jane Doe")