Move project data class to project file

This commit is contained in:
Veronica Berglyd Olsen
2022-10-31 21:25:40 +01:00
parent a760aff826
commit 13b9e5a434
6 changed files with 353 additions and 326 deletions
+314 -16
View File
@@ -4,7 +4,8 @@ novelWriter Project Wrapper
Data class for novelWriter projects
File History:
Created: 2018-09-29 [0.0.1]
Created: 2018-09-29 [0.0.1] NWProject
Created: 2022-10-30 [2.0rc1] NWProjectData
This file is a part of novelWriter
Copyright 20182022, Veronica Berglyd Olsen
@@ -32,38 +33,40 @@ import novelwriter
from time import time
from functools import partial
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtCore import QCoreApplication, QObject, pyqtSignal
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.error import logException
from novelwriter.common import (
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
from novelwriter.core.status import NWStatus
from novelwriter.core.options import OptionState
from novelwriter.core.document import NWDoc
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.core.projectdata import NWProjectData
from novelwriter.common import (
checkBool, checkInt, checkString, checkStringNone, formatTimeStamp, hexToInt, isHandle,
makeFileNameSafe, minmax, simplified,
)
logger = logging.getLogger(__name__)
class NWProject:
class NWProject(QObject):
FILE_VERSION = "1.4" # The current project file format version
projectStatusChanged = pyqtSignal(bool)
def __init__(self, mainGui):
super().__init__(parent=mainGui)
# Internal
self.mainConf = novelwriter.CONFIG
self.mainGui = mainGui
# Project Data
self._data = NWProjectData()
self._data = NWProjectData(self)
# Core Elements
self._optState = OptionState(self) # Project-specific GUI options
@@ -240,7 +243,7 @@ class NWProject:
# Project Tree
self._projTree.clear()
self._data = NWProjectData()
self._data = NWProjectData(self)
# Project Settings
self.projPath = None
@@ -446,7 +449,7 @@ class NWProject:
# Open The Project XML File
# =========================
self._data = NWProjectData()
self._data = NWProjectData(self)
xmlReader = ProjectXMLReader(fileName)
xmlParsed = xmlReader.read(self._data)
@@ -857,13 +860,10 @@ class NWProject:
information to the GUI statusbar.
"""
self._projChanged = bValue
self.mainGui.mainStatus.doUpdateProjectStatus(bValue)
self.projectStatusChanged.emit(self._projChanged)
if bValue is True:
# If we've changed the project at all, this should be True
self._projAltered = True
else:
# If we're resetting the status, also reset for data class
self._data.resetProjectChanged()
return self._projChanged
##
@@ -1310,3 +1310,301 @@ class NWProject:
return True
# END Class NWProject
class NWProjectData:
def __init__(self, theProject):
self.theProject = theProject
# Project Meta
self._name = ""
self._title = ""
self._authors = []
self._saveCount = 0
self._autoCount = 0
self._editTime = 0
# Project Settings
self._doBackup = True
self._language = None
self._spellCheck = False
self._spellLang = None
self._lastHandle = {
"editor": "",
"viewer": "",
"novelTree": "",
"outline": "",
}
self._lastCount = {}
self._currCount = {}
self._autoReplace = {}
self._titleFormat = {
"title": "%title%",
"chapter": "%title%",
"unnumbered": "%title%",
"scene": "* * *",
"section": "",
}
self._status = NWStatus(NWStatus.STATUS)
self._import = NWStatus(NWStatus.IMPORT)
return
##
# Properties
##
@property
def name(self):
return self._name
@property
def title(self):
return self._title
@property
def authors(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
@property
def doBackup(self):
return self._doBackup
@property
def language(self):
return self._language
@property
def spellCheck(self):
return self._spellCheck
@property
def spellLang(self):
return self._spellLang
@property
def lastHandle(self):
return self._lastHandle
@property
def autoReplace(self):
return self._autoReplace
@property
def titleFormat(self):
return self._titleFormat
@property
def itemStatus(self):
return self._status
@property
def itemImport(self):
return self._import
##
# Methods
##
def addAuthor(self, value):
"""Add an author to the authors list.
"""
self._authors.append(simplified(str(value)))
self.theProject.setProjectChanged(True)
return
def incSaveCount(self):
"""Increment the save count by one.
"""
self._saveCount += 1
self.theProject.setProjectChanged(True)
return
def incAutoCount(self):
"""Increment the auto save count by one.
"""
self._autoCount += 1
self.theProject.setProjectChanged(True)
return
##
# Getters
##
def getLastHandle(self, component):
"""Retrieve the last used handle for a given component.
"""
return self._lastHandle.get(component, None)
def getLastCount(self, type):
"""Retrieve the last word count for a given type.
"""
return self._lastCount.get(type, 0)
def getCurrCount(self, type):
"""Retrieve the current word count for a given type.
"""
return self._currCount.get(type, 0)
def getTitleFormat(self, kind):
"""Retrieve the title format string for a given kind of header.
"""
return self._titleFormat.get(kind, "%title%")
##
# Setters
##
def setName(self, value):
"""Set a new project name.
"""
if value != self._name:
self._name = simplified(str(value))
self.theProject.setProjectChanged(True)
return
def setTitle(self, value):
"""Set a new novel title.
"""
if value != self._title:
self._title = simplified(str(value))
self.theProject.setProjectChanged(True)
return
def setAuthors(self, value):
"""Set the list of authors from either a string with one author
per line, or a list of authors.
"""
self._authors = []
self.theProject.setProjectChanged(True)
if isinstance(value, str):
for author in value.splitlines():
author = simplified(author)
if author:
self._authors.append(author)
self.theProject.setProjectChanged(True)
elif isinstance(value, list):
self._authors = value
return
def setSaveCount(self, value):
"""Set the save count from last session.
"""
self._saveCount = checkInt(value, 0)
self.theProject.setProjectChanged(True)
return
def setAutoCount(self, value):
"""Set the auto save count from last session.
"""
self._autoCount = checkInt(value, 0)
self.theProject.setProjectChanged(True)
return
def setEditTime(self, value):
"""Set tyje edit time from last session.
"""
self._editTime = checkInt(value, 0)
self.theProject.setProjectChanged(True)
return
def setDoBackup(self, value):
"""Set the do write backup flag.
"""
if value != self._doBackup:
self._doBackup = checkBool(value, False)
self.theProject.setProjectChanged(True)
return
def setLanguage(self, value):
"""Set the project language.
"""
if value != self._language:
self._language = checkStringNone(value, None)
self.theProject.setProjectChanged(True)
return
def setSpellCheck(self, value):
"""Set the spell check flag.
"""
if value != self._spellCheck:
self._spellCheck = checkBool(value, False)
self.theProject.setProjectChanged(True)
return
def setSpellLang(self, value):
"""Set the spell check language.
"""
if value != self._spellLang:
self._spellLang = checkStringNone(value, None)
self.theProject.setProjectChanged(True)
return
def setLastHandle(self, value, component=None):
"""Set a last used handle into the handle registry. If component
is None, the value is assumed to be the whole dictionary of
values.
"""
if isinstance(component, str):
self._lastHandle[component] = checkString(value, "")
self.theProject.setProjectChanged(True)
elif isinstance(value, dict):
for key, entry in value.items():
if key in self._lastHandle:
self._lastHandle[key] = checkString(entry, "")
self.theProject.setProjectChanged(True)
return
def setLastCount(self, value, type):
"""Set the word counts from last session.
"""
self._lastCount[type] = checkInt(value, 0)
self.theProject.setProjectChanged(True)
return
def setCurrCount(self, value, type):
"""Set the current word counts.
"""
if value != self._currCount.get(type, 0):
self._currCount[type] = checkInt(value, 0)
self.theProject.setProjectChanged(True)
return
def setAutoReplace(self, value):
"""Set the auto-replace dictionary.
"""
if isinstance(value, dict):
self._autoReplace = {}
for key, entry in value.items():
if isinstance(entry, str):
self._autoReplace[key] = simplified(entry)
self.theProject.setProjectChanged(True)
return
def setTitleFormat(self, value):
"""Set the title formats.
"""
if isinstance(value, dict):
for key, entry in value.items():
if key in self._titleFormat and isinstance(entry, str):
self._titleFormat[key] = simplified(entry)
self.theProject.setProjectChanged(True)
return
# END Class NWProjectData
-271
View File
@@ -1,271 +0,0 @@
"""
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 (
checkBool, checkInt, checkStringNone, simplified
)
from novelwriter.core.status import NWStatus
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
# Project Settings
self._doBackup = True
self._language = None
self._spellCheck = False
self._spellLang = None
self._lastHandle = {}
self._lastCount = {}
self._currCount = {}
self._autoReplace = {}
self._titleFormat = {
"title": "%title%",
"chapter": "%title%",
"unnumbered": "%title%",
"scene": "* * *",
"section": "",
}
self._status = NWStatus(NWStatus.STATUS)
self._import = NWStatus(NWStatus.IMPORT)
# Internal
self._changed = False
return
##
# Properties
##
@property
def name(self):
return self._name
@property
def title(self):
return self._title
@property
def authors(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
@property
def doBackup(self):
return self._doBackup
@property
def language(self):
return self._language
@property
def spellCheck(self):
return self._spellCheck
@property
def spellLang(self):
return self._spellLang
@property
def autoReplace(self):
return self._autoReplace
@property
def titleFormat(self):
return self._titleFormat
@property
def itemStatus(self):
return self._status
@property
def itemImport(self):
return self._import
@property
def changed(self):
return self._changed
##
# 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
def resetProjectChanged(self):
self._changed = False
##
# Getters
##
def getLastHandle(self, component):
return self._lastHandle.get(component, None)
def getLastCount(self, type):
return self._lastCount.get(type, 0)
def getCurrCount(self, type):
return self._currCount.get(type, 0)
def getTitleFormat(self, kind):
return self._titleFormat.get(kind, "%title%")
##
# 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 setAuthors(self, value):
self._authors = []
self._changed = True
if isinstance(value, str):
for author in value.splitlines():
author = simplified(author)
if author:
self._authors.append(author)
self._changed = True
elif isinstance(value, list):
self._authors = value
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
def setDoBackup(self, value):
self._doBackup = checkBool(value, False)
self._changed = True
return
def setLanguage(self, value):
self._language = checkStringNone(value, None)
self._changed = True
return
def setSpellCheck(self, value):
self._spellCheck = checkBool(value, False)
self._changed = True
return
def setSpellLang(self, value):
self._spellLang = checkStringNone(value, None)
self._changed = True
return
def setLastHandle(self, value, component):
self._lastHandle[component] = checkStringNone(value, None)
self._changed = True
return
def setLastCount(self, value, type):
self._lastCount[type] = checkInt(value, 0)
self._changed = True
return
def setCurrCount(self, value, type):
if value != self._currCount.get(type, 0):
self._currCount[type] = checkInt(value, 0)
self._changed = True
return
def setAutoReplace(self, value):
if isinstance(value, dict):
self._autoReplace = {}
for key, entry in value.items():
if isinstance(entry, str):
self._autoReplace[key] = simplified(entry)
self._changed = True
return
def setTitleFormat(self, value):
if isinstance(value, dict):
for key, entry in value.items():
if key in self._titleFormat and isinstance(entry, str):
self._titleFormat[key] = simplified(entry)
self._changed = True
return
# END Class NWProjectData
+23 -27
View File
@@ -266,14 +266,10 @@ class ProjectXMLReader:
projData.setSpellCheck(xItem.text)
elif xItem.tag == "spellLang":
projData.setSpellLang(xItem.text)
elif xItem.tag == "lastEdited":
elif xItem.tag == "lastEdited": # Discontinued in 1.4
projData.setLastHandle(xItem.text, "editor")
elif xItem.tag == "lastViewed":
elif xItem.tag == "lastViewed": # Discontinued in 1.4
projData.setLastHandle(xItem.text, "viewer")
elif xItem.tag == "lastNovel":
projData.setLastHandle(xItem.text, "noveltree")
elif xItem.tag == "lastOutline":
projData.setLastHandle(xItem.text, "outline")
elif xItem.tag == "lastWordCount":
projData.setLastCount(xItem.text, "total")
elif xItem.tag == "novelWordCount":
@@ -284,9 +280,11 @@ class ProjectXMLReader:
self._parseStatusImport(xItem, projData.itemStatus)
elif xItem.tag in ("import", "importance"):
self._parseStatusImport(xItem, projData.itemImport)
elif xItem.tag == "lastHandle":
projData.setLastHandle(self._parseDictKeyText(xItem, "component"))
elif xItem.tag == "autoReplace":
if self._version >= 0x0102:
projData.setAutoReplace(self._parseDictKeyText(xItem))
projData.setAutoReplace(self._parseDictKeyText(xItem, "key"))
else: # Pre 1.2 format
projData.setAutoReplace(self._parseDictTagText(xItem))
elif xItem.tag == "titleFormat":
@@ -304,13 +302,13 @@ class ProjectXMLReader:
for xItem in xSection:
if xItem.tag == "item":
item = {}
item["handle"] = xItem.attrib.get("handle", None)
item["parent"] = xItem.attrib.get("parent", None)
item["root"] = xItem.attrib.get("root", None)
item["handle"] = checkStringNone(xItem.attrib.get("handle", None), None)
item["parent"] = checkStringNone(xItem.attrib.get("parent", None), None)
item["root"] = checkStringNone(xItem.attrib.get("root", None), None)
item["order"] = checkInt(xItem.attrib.get("order", 0), 0)
item["type"] = checkString(xItem.attrib.get("type", ""), "")
item["class"] = checkString(xItem.attrib.get("class", ""), "")
item["layout"] = checkString(xItem.attrib.get("layout", ""), "")
item["type"] = checkString(xItem.attrib.get("type", "NO_TYPE"), "NO_TYPE")
item["class"] = checkString(xItem.attrib.get("class", "NO_CLASS"), "NO_CLASS")
item["layout"] = checkString(xItem.attrib.get("layout", "NO_LAYOUT"), "NO_LAYOUT")
for xVal in xItem:
if xVal.tag == "meta":
item["expanded"] = checkBool(xVal.attrib.get("expanded", False), False)
@@ -339,7 +337,7 @@ class ProjectXMLReader:
return True
def _parseProjectContentLegacy(self, xSection):
"""Parse the content section of the XML file for older version.
"""Parse the content section of the XML file for older versions.
"""
logger.debug("Parsing xml <root/content> (legacy format)")
depLayout = ("TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE")
@@ -347,8 +345,8 @@ class ProjectXMLReader:
for xItem in xSection:
item = {}
if xItem.tag == "item":
item["handle"] = xItem.attrib.get("handle", None)
item["parent"] = xItem.attrib.get("parent", None)
item["handle"] = checkStringNone(xItem.attrib.get("handle", None), None)
item["parent"] = checkStringNone(xItem.attrib.get("parent", None), None)
item["root"] = None # Value was added in 1.4
item["order"] = checkInt(xItem.attrib.get("order", 0), 0)
item["heading"] = "H0" # Value was added in 1.4
@@ -421,14 +419,14 @@ class ProjectXMLReader:
self._importMap = {entry["name"]: key for key, entry in projData.itemImport.items()}
return
def _parseDictKeyText(self, xItem):
def _parseDictKeyText(self, xItem, keyName):
"""Parse a dictionary stored with key as an attribute and the
value as the text porperty.
"""
result = {}
for xEntry in xItem:
if xEntry.tag == "entry" and "key" in xEntry.attrib:
result[xEntry.attrib["key"]] = checkString(xEntry.text, "")
if xEntry.tag == "entry" and keyName in xEntry.attrib:
result[xEntry.attrib[keyName]] = checkString(xEntry.text, "")
return result
def _parseDictTagText(self, xItem):
@@ -473,14 +471,11 @@ class ProjectXMLWriter:
self._packSingleValue(xSettings, "language", projData.language)
self._packSingleValue(xSettings, "spellCheck", projData.spellCheck)
self._packSingleValue(xSettings, "spellLang", projData.spellLang)
self._packSingleValue(xSettings, "lastEdited", projData.getLastHandle("editor"))
self._packSingleValue(xSettings, "lastViewed", projData.getLastHandle("viewer"))
self._packSingleValue(xSettings, "lastNovel", projData.getLastHandle("noveltree"))
self._packSingleValue(xSettings, "lastOutline", projData.getLastHandle("outline"))
self._packSingleValue(xSettings, "lastWordCount", projData.getCurrCount("total"))
self._packSingleValue(xSettings, "novelWordCount", projData.getCurrCount("novel"))
self._packSingleValue(xSettings, "notesWordCount", projData.getCurrCount("notes"))
self._packDictKeyValue(xSettings, "autoReplace", projData.autoReplace)
self._packDictKeyValue(xSettings, "lastHandle", projData.lastHandle, "component")
self._packDictKeyValue(xSettings, "autoReplace", projData.autoReplace, "key")
self._packDictTagValue(xSettings, "titleFormat", projData.titleFormat)
# Save Status/Importance
@@ -549,14 +544,15 @@ class ProjectXMLWriter:
self._packSingleValue(xParent, name, value, allowNone=allowNone)
return
def _packDictKeyValue(self, xParent, name, data):
def _packDictKeyValue(self, xParent, name, data, keyName):
"""Pack the entries of a dictionary into an xml element.
"""
xItem = etree.SubElement(xParent, name)
for key, value in data.items():
if len(key) > 0:
xEntry = etree.SubElement(xItem, "entry", attrib={"key": key})
xEntry.text = value
xEntry = etree.SubElement(xItem, "entry", attrib={keyName: key})
if value:
xEntry.text = value
return
def _packDictTagValue(self, xParent, name, data):
+5 -5
View File
@@ -109,7 +109,7 @@ class GuiNovelView(QWidget):
def refreshTree(self):
"""Refresh the current tree.
"""
self.novelTree.refreshTree(rootHandle=self.theProject.data.getLastHandle("noveltree"))
self.novelTree.refreshTree(rootHandle=self.theProject.data.getLastHandle("novelTree"))
return
def clearProject(self):
@@ -122,7 +122,7 @@ class GuiNovelView(QWidget):
def openProjectTasks(self):
"""Run open project tasks.
"""
lastNovel = self.theProject.data.getLastHandle("noveltree")
lastNovel = self.theProject.data.getLastHandle("novelTree")
if lastNovel not in self.theProject.tree:
lastNovel = self.theProject.tree.findRoot(nwItemClass.NOVEL)
@@ -319,7 +319,7 @@ class GuiNovelToolBar(QWidget):
def _refreshNovelTree(self):
"""Rebuild the current tree.
"""
rootHandle = self.theProject.data.getLastHandle("noveltree")
rootHandle = self.theProject.data.getLastHandle("novelTree")
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
return
@@ -485,7 +485,7 @@ class GuiNovelTree(QTreeWidget):
titleKey = selItem[0].data(self.C_TITLE, self.D_KEY)
self._populateTree(rootHandle)
self.theProject.data.setLastHandle(rootHandle, "noveltree")
self.theProject.data.setLastHandle(rootHandle, "novelTree")
if titleKey is not None and titleKey in self._treeMap:
self._treeMap[titleKey].setSelected(True)
@@ -523,7 +523,7 @@ class GuiNovelTree(QTreeWidget):
self._lastCol = colType
self.setColumnHidden(self.C_EXTRA, colType == NovelTreeColumn.HIDDEN)
if doRefresh:
lastNovel = self.theProject.data.getLastHandle("noveltree")
lastNovel = self.theProject.data.getLastHandle("novelTree")
self.refreshTree(rootHandle=lastNovel, overRide=True)
return
+2
View File
@@ -197,6 +197,8 @@ class GuiMain(QMainWindow):
# Connect Signals
# ===============
self.theProject.projectStatusChanged.connect(self.mainStatus.doUpdateProjectStatus)
self.viewsBar.viewChangeRequested.connect(self._changeView)
self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
+9 -7
View File
@@ -1,26 +1,28 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-31 18:11:11">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-31 21:24:31">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>1387</saveCount>
<saveCount>1403</saveCount>
<autoCount>236</autoCount>
<editTime>69358</editTime>
<editTime>69454</editTime>
</project>
<settings>
<doBackup>False</doBackup>
<language>en_GB</language>
<spellCheck>True</spellCheck>
<spellLang>None</spellLang>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed>
<lastNovel>7031beac91f75</lastNovel>
<lastOutline>7031beac91f75</lastOutline>
<lastWordCount>1363</lastWordCount>
<novelWordCount>954</novelWordCount>
<notesWordCount>409</notesWordCount>
<lastHandle>
<entry component="editor">636b6aa9b697b</entry>
<entry component="viewer">636b6aa9b697b</entry>
<entry component="novelTree">7031beac91f75</entry>
<entry component="outline">7031beac91f75</entry>
</lastHandle>
<autoReplace>
<entry key="A">B</entry>
<entry key="B">E</entry>