From 522acc479b374c60c0ef4f63d1ec65a218fbd276 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 31 Oct 2022 17:01:50 +0100 Subject: [PATCH] Move status and importance to data class --- novelwriter/core/item.py | 12 ++-- novelwriter/core/project.py | 41 +++++------ novelwriter/core/projectdata.py | 12 ++++ novelwriter/core/projectxml.py | 51 +++++-------- novelwriter/core/status.py | 17 +++-- novelwriter/dialogs/projsettings.py | 4 +- novelwriter/gui/projtree.py | 4 +- tests/test_core/test_core_project.py | 80 ++++++++++----------- tests/test_dialogs/test_dlg_projsettings.py | 4 +- 9 files changed, 110 insertions(+), 115 deletions(-) diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py index 66cb7fbe..e71d0643 100644 --- a/novelwriter/core/item.py +++ b/novelwriter/core/item.py @@ -271,11 +271,11 @@ class NWItem: the current item based on its class. """ if self.isNovelLike(): - stName = self.theProject.statusItems.name(self._status) - stIcon = self.theProject.statusItems.icon(self._status) if incIcon else None + stName = self.theProject.data.itemStatus.name(self._status) + stIcon = self.theProject.data.itemStatus.icon(self._status) if incIcon else None else: - stName = self.theProject.importItems.name(self._import) - stIcon = self.theProject.importItems.icon(self._import) if incIcon else None + stName = self.theProject.data.itemImport.name(self._import) + stIcon = self.theProject.data.itemImport.icon(self._import) if incIcon else None return stName, stIcon ## @@ -447,14 +447,14 @@ class NWItem: """Set the item status by looking it up in the valid status items of the current project. """ - self._status = self.theProject.statusItems.check(value) + self._status = self.theProject.data.itemStatus.check(value) return def setImport(self, value): """Set the item importance by looking it up in the valid import items of the current project. """ - self._import = self.theProject.importItems.check(value) + self._import = self.theProject.data.itemImport.check(value) return def setActive(self, state): diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 9d005bf4..a7741a13 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -45,7 +45,6 @@ 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, XMLReadState @@ -92,8 +91,6 @@ class NWProject: self.autoReplace = {} # Text to auto-replace on exports self.titleFormat = {} # The formatting of titles for exports self.spellCheck = False # Controls the spellcheck-as-you-type feature - self.statusItems = None # Novel file progress status values - self.importItems = None # Note file importance values # Internal Mapping self.tr = partial(QCoreApplication.translate, "NWProject") @@ -268,17 +265,15 @@ class NWProject: "scene": "* * *", "section": "", } - self.spellCheck = False - self.statusItems = NWStatus(NWStatus.STATUS) - self.statusItems.write(None, self.tr("New"), (100, 100, 100)) - self.statusItems.write(None, self.tr("Note"), (200, 50, 0)) - self.statusItems.write(None, self.tr("Draft"), (200, 150, 0)) - self.statusItems.write(None, self.tr("Finished"), (50, 200, 0)) - self.importItems = NWStatus(NWStatus.IMPORT) - self.importItems.write(None, self.tr("New"), (100, 100, 100)) - self.importItems.write(None, self.tr("Minor"), (200, 50, 0)) - self.importItems.write(None, self.tr("Major"), (200, 150, 0)) - self.importItems.write(None, self.tr("Main"), (50, 200, 0)) + self.spellCheck = False + self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100)) + self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0)) + self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0)) + self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0)) + self._data.itemImport.write(None, self.tr("New"), (100, 100, 100)) + self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0)) + self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0)) + self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0)) return @@ -548,8 +543,6 @@ class NWProject: self.spellCheck = self._data.spellCheck self.projSpell = self._data.spellLang - self.statusItems.unpack(xmlSettings.get("status", {})) - self.importItems.unpack(xmlSettings.get("import", {})) self.autoReplace = xmlSettings.get("autoReplace", {}) self.titleFormat.update(xmlSettings.get("titleFormat", {})) @@ -665,9 +658,9 @@ class NWProject: # Save Status/Importance self.countStatus() xStatus = etree.SubElement(xSettings, "status") - self.statusItems.packXML(xStatus) + self._data.itemStatus.packXML(xStatus) xStatus = etree.SubElement(xSettings, "importance") - self.importItems.packXML(xStatus) + self._data.itemImport.packXML(xStatus) # Save Tree Content logger.debug("Writing project content") @@ -970,12 +963,12 @@ class NWProject: def setStatusColours(self, newCols, delCols): """Update the list of novel file status flags. """ - return self._setStatusImport(newCols, delCols, self.statusItems) + return self._setStatusImport(newCols, delCols, self._data.itemStatus) def setImportColours(self, newCols, delCols): """Update the list of note file importance flags. """ - return self._setStatusImport(newCols, delCols, self.importItems) + return self._setStatusImport(newCols, delCols, self._data.itemImport) def setAutoReplace(self, autoReplace): """Update the auto-replace dictionary. @@ -1096,13 +1089,13 @@ class NWProject: project tree. The counts themselves are kept in the NWStatus objects. This is essentially a refresh. """ - self.statusItems.resetCounts() - self.importItems.resetCounts() + self._data.itemStatus.resetCounts() + self._data.itemImport.resetCounts() for nwItem in self._projTree: if nwItem.isNovelLike(): - self.statusItems.increment(nwItem.itemStatus) + self._data.itemStatus.increment(nwItem.itemStatus) else: - self.importItems.increment(nwItem.itemImport) + self._data.itemImport.increment(nwItem.itemImport) return def localLookup(self, theWord): diff --git a/novelwriter/core/projectdata.py b/novelwriter/core/projectdata.py index b22c74c9..10988104 100644 --- a/novelwriter/core/projectdata.py +++ b/novelwriter/core/projectdata.py @@ -28,6 +28,7 @@ import logging from novelwriter.common import ( checkBool, checkInt, checkStringNone, simplified ) +from novelwriter.core.status import NWStatus logger = logging.getLogger(__name__) @@ -53,6 +54,9 @@ class NWProjectData: self._lastCount = {} self._currCount = {} + self._status = NWStatus(NWStatus.STATUS) + self._import = NWStatus(NWStatus.IMPORT) + # Internal self._changed = False @@ -102,6 +106,14 @@ class NWProjectData: def spellLang(self): return self._spellLang + @property + def itemStatus(self): + return self._status + + @property + def itemImport(self): + return self._import + @property def changed(self): return self._changed diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py index 8760f6b1..eb8506b2 100644 --- a/novelwriter/core/projectxml.py +++ b/novelwriter/core/projectxml.py @@ -31,7 +31,7 @@ from enum import Enum from lxml import etree from novelwriter.common import ( - checkBool, checkInt, checkStringNone, minmax, simplified, checkString + checkBool, checkInt, checkStringNone, simplified, checkString ) logger = logging.getLogger(__name__) @@ -192,6 +192,7 @@ class ProjectXMLReader: if self._version >= 0x0104: status &= self._parseProjectContent(xSection) else: + self._genLegacyImportStatysMap(projData) status &= self._parseProjectContentLegacy(xSection) else: logger.warning("Ignored in xml", xSection.tag) @@ -264,9 +265,9 @@ class ProjectXMLReader: elif xItem.tag == "notesWordCount": projData.setLastCount(xItem.text, "notes") elif xItem.tag == "status": - data["status"] = self._parseStatusImport(xItem, "status") + self._parseStatusImport(xItem, projData.itemStatus) elif xItem.tag in ("import", "importance"): - data["import"] = self._parseStatusImport(xItem, "import") + self._parseStatusImport(xItem, projData.itemImport) elif xItem.tag == "autoReplace": if self._version >= 0x0102: for xEntry in xItem: @@ -375,9 +376,9 @@ class ProjectXMLReader: # Status was split into separate status/import with a key in 1.4 if item.get("class", "") in ("NOVEL", "ARCHIVE"): - item["status"] = self._getLegacyUnportStatus(tmpStatus, "status") + item["status"] = self._statusMap.get(tmpStatus, None) else: - item["import"] = self._getLegacyUnportStatus(tmpStatus, "import") + item["import"] = self._importMap.get(tmpStatus, None) # A number of layouts were removed in 1.3 if item.get("layout", "") in depLayout: @@ -396,39 +397,25 @@ class ProjectXMLReader: return True - def _parseStatusImport(self, xItem, type): + def _parseStatusImport(self, xItem, sObject): """Parse a status or importance entry. """ - data = self._statusData.get(type, {}) for xEntry in xItem: if xEntry.tag == "entry": - key = xEntry.attrib.get("key", f"{type[0]}{len(data):06x}") - data[key] = { - "label": xEntry.text, - "count": checkInt(xEntry.attrib.get("count", 0), 0), - "colour": ( - minmax(checkInt(xEntry.attrib.get("red", 0), 0), 0, 255), - minmax(checkInt(xEntry.attrib.get("green", 0), 0), 0, 255), - minmax(checkInt(xEntry.attrib.get("blue", 0), 0), 0, 255), - ), - } - self._statusData[type] = data + key = xEntry.attrib.get("key", None) + red = checkInt(xEntry.attrib.get("red", 0), 0) + green = checkInt(xEntry.attrib.get("green", 0), 0) + blue = checkInt(xEntry.attrib.get("blue", 0), 0) + count = checkInt(xEntry.attrib.get("count", 0), 0) + sObject.write(key, xEntry.text, (red, green, blue), count) + return - return data - - def _getLegacyUnportStatus(self, label, type): - """Look up the label in defined status or importance values. - This is needed for file formats prior to 1.4 where the status - was saved as the label, not the key. + def _genLegacyImportStatysMap(self, projData): + """Generate a map of legacy import/status values. """ - if not self._statusMap.get(type): - lookup = {} - for key, entry in self._statusData.get(type, {}).items(): - lookup[entry.get("label", "")] = key - self._statusMap[type] = lookup - print(lookup) - - return self._statusMap.get(type, {}).get(label, None) + self._statusMap = {entry["name"]: key for key, entry in projData.itemStatus.items()} + self._importMap = {entry["name"]: key for key, entry in projData.itemImport.items()} + return # END Class ProjectXMLReader diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index 1d19a08e..63268a4c 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -33,7 +33,7 @@ from lxml import etree from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor from PyQt5.QtCore import QRectF, Qt -from novelwriter.common import simplified +from novelwriter.common import minmax, simplified logger = logging.getLogger(__name__) @@ -57,7 +57,7 @@ class NWStatus: self._iconPath = QPainterPath() self._iconPath.addRoundedRect(QRectF(pA, pA, pB, pB), pR, pR) - self._defaultIcon = self._createIcon([100, 100, 100]) + self._defaultIcon = self._createIcon(100, 100, 100) if self._type == self.STATUS: self._prefix = "s" @@ -79,14 +79,17 @@ class NWStatus: if len(col) != 3: col = (100, 100, 100) + cR = minmax(col[0], 0, 255) + cG = minmax(col[1], 0, 255) + cB = minmax(col[2], 0, 255) name = simplified(name) if count is None: - count = self._store[key]["count"] if key in self._store else 0 + count = self._store.get(key, {}).get("count", 0) self._store[key] = { "name": name, - "icon": self._createIcon(col), - "cols": col, + "icon": self._createIcon(cR, cG, cB), + "cols": (cR, cG, cB), "count": count, } @@ -261,7 +264,7 @@ class NWStatus: return False return True - def _createIcon(self, col): + def _createIcon(self, red, green, blue): """Generate an icon for a status label. """ pixmap = QPixmap(self._iPX, self._iPX) @@ -269,7 +272,7 @@ class NWStatus: painter = QPainter(pixmap) painter.setRenderHint(QPainter.Antialiasing) - painter.fillPath(self._iconPath, QColor(*col)) + painter.fillPath(self._iconPath, QColor(red, green, blue)) painter.end() return QIcon(pixmap) diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py index a9ee7188..18e905a0 100644 --- a/novelwriter/dialogs/projsettings.py +++ b/novelwriter/dialogs/projsettings.py @@ -288,11 +288,11 @@ class GuiProjectEditStatus(QWidget): self.mainTheme = projGui.mainGui.mainTheme if isStatus: - self.theStatus = self.theProject.statusItems + self.theStatus = self.theProject.data.itemStatus pageLabel = self.tr("Novel File Status Levels") colSetting = "statusColW" else: - self.theStatus = self.theProject.importItems + self.theStatus = self.theProject.data.itemImport pageLabel = self.tr("Note File Importance Levels") colSetting = "importColW" diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 02e1352a..b41e7ed7 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -1208,7 +1208,7 @@ class GuiProjectTree(QTreeWidget): checkMark = f" ({nwUnicode.U_CHECK})" if tItem.isNovelLike(): mStatus = ctxMenu.addMenu(self.tr("Set Status to ...")) - for n, (key, entry) in enumerate(self.theProject.statusItems.items()): + for n, (key, entry) in enumerate(self.theProject.data.itemStatus.items()): entryName = entry["name"] + (checkMark if tItem.itemStatus == key else "") aStatus = mStatus.addAction(entry["icon"], entryName) aStatus.triggered.connect( @@ -1221,7 +1221,7 @@ class GuiProjectTree(QTreeWidget): ) else: mImport = ctxMenu.addMenu(self.tr("Set Importance to ...")) - for n, (key, entry) in enumerate(self.theProject.importItems.items()): + for n, (key, entry) in enumerate(self.theProject.data.itemImport.items()): entryName = entry["name"] + (checkMark if tItem.itemImport == key else "") aImport = mImport.addAction(entry["icon"], entryName) aImport.triggered.connect( diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py index aafa1e52..23598f8b 100644 --- a/tests/test_core/test_core_project.py +++ b/tests/test_core/test_core_project.py @@ -783,24 +783,24 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd): assert theProject.setStatusColours([], []) is False assert theProject.setStatusColours(newList, []) is True - assert theProject.statusItems.name(statusKeys[0]) == "New" - assert theProject.statusItems.name(statusKeys[1]) == "Draft" - assert theProject.statusItems.name(statusKeys[2]) == "Note" - assert theProject.statusItems.name(statusKeys[3]) == "Edited" - assert theProject.statusItems.cols(statusKeys[0]) == (1, 1, 1) - assert theProject.statusItems.cols(statusKeys[1]) == (2, 2, 2) - assert theProject.statusItems.cols(statusKeys[2]) == (3, 3, 3) - assert theProject.statusItems.cols(statusKeys[3]) == (4, 4, 4) + assert theProject.data.itemStatus.name(statusKeys[0]) == "New" + assert theProject.data.itemStatus.name(statusKeys[1]) == "Draft" + assert theProject.data.itemStatus.name(statusKeys[2]) == "Note" + assert theProject.data.itemStatus.name(statusKeys[3]) == "Edited" + assert theProject.data.itemStatus.cols(statusKeys[0]) == (1, 1, 1) + assert theProject.data.itemStatus.cols(statusKeys[1]) == (2, 2, 2) + assert theProject.data.itemStatus.cols(statusKeys[2]) == (3, 3, 3) + assert theProject.data.itemStatus.cols(statusKeys[3]) == (4, 4, 4) # Check the new entry - lastKey = theProject.statusItems.check("s000018") + lastKey = theProject.data.itemStatus.check("s000018") assert lastKey == "s000018" - assert theProject.statusItems.name(lastKey) == "Finished" - assert theProject.statusItems.cols(lastKey) == (5, 5, 5) + assert theProject.data.itemStatus.name(lastKey) == "Finished" + assert theProject.data.itemStatus.cols(lastKey) == (5, 5, 5) # Delete last entry assert theProject.setStatusColours([], [lastKey]) is True - assert theProject.statusItems.name(lastKey) == "New" + assert theProject.data.itemStatus.name(lastKey) == "New" # Change Importance # ================= @@ -820,52 +820,52 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd): assert theProject.setImportColours([], []) is False assert theProject.setImportColours(newList, []) is True - assert theProject.importItems.name(importKeys[0]) == "New" - assert theProject.importItems.name(importKeys[1]) == "Minor" - assert theProject.importItems.name(importKeys[2]) == "Major" - assert theProject.importItems.name(importKeys[3]) == "Min" - assert theProject.importItems.cols(importKeys[0]) == (1, 1, 1) - assert theProject.importItems.cols(importKeys[1]) == (2, 2, 2) - assert theProject.importItems.cols(importKeys[2]) == (3, 3, 3) - assert theProject.importItems.cols(importKeys[3]) == (4, 4, 4) + assert theProject.data.itemImport.name(importKeys[0]) == "New" + assert theProject.data.itemImport.name(importKeys[1]) == "Minor" + assert theProject.data.itemImport.name(importKeys[2]) == "Major" + assert theProject.data.itemImport.name(importKeys[3]) == "Min" + assert theProject.data.itemImport.cols(importKeys[0]) == (1, 1, 1) + assert theProject.data.itemImport.cols(importKeys[1]) == (2, 2, 2) + assert theProject.data.itemImport.cols(importKeys[2]) == (3, 3, 3) + assert theProject.data.itemImport.cols(importKeys[3]) == (4, 4, 4) # Check the new entry - lastKey = theProject.importItems.check("i00001a") + lastKey = theProject.data.itemImport.check("i00001a") assert lastKey == "i00001a" - assert theProject.importItems.name(lastKey) == "Max" - assert theProject.importItems.cols(lastKey) == (5, 5, 5) + assert theProject.data.itemImport.name(lastKey) == "Max" + assert theProject.data.itemImport.cols(lastKey) == (5, 5, 5) # Delete last entry assert theProject.setImportColours([], [lastKey]) is True - assert theProject.importItems.name(lastKey) == "New" + assert theProject.data.itemImport.name(lastKey) == "New" # Delete Status/Import # ==================== - theProject.statusItems.resetCounts() - for key in list(theProject.statusItems.keys()): - assert theProject.statusItems.remove(key) is True + theProject.data.itemStatus.resetCounts() + for key in list(theProject.data.itemStatus.keys()): + assert theProject.data.itemStatus.remove(key) is True - theProject.importItems.resetCounts() - for key in list(theProject.importItems.keys()): - assert theProject.importItems.remove(key) is True + theProject.data.itemImport.resetCounts() + for key in list(theProject.data.itemImport.keys()): + assert theProject.data.itemImport.remove(key) is True - assert len(theProject.statusItems) == 0 - assert len(theProject.importItems) == 0 + assert len(theProject.data.itemStatus) == 0 + assert len(theProject.data.itemImport) == 0 assert theProject.saveProject() is True assert theProject.closeProject() is True # This should restore the default status/import labels assert theProject.openProject(fncDir) is True assert theProject.saveProject() is True - assert theProject.statusItems.name("s000023") == "New" - assert theProject.statusItems.name("s000024") == "Note" - assert theProject.statusItems.name("s000025") == "Draft" - assert theProject.statusItems.name("s000026") == "Finished" - assert theProject.importItems.name("i000027") == "New" - assert theProject.importItems.name("i000028") == "Minor" - assert theProject.importItems.name("i000029") == "Major" - assert theProject.importItems.name("i00002a") == "Main" + assert theProject.data.itemStatus.name("s000023") == "New" + assert theProject.data.itemStatus.name("s000024") == "Note" + assert theProject.data.itemStatus.name("s000025") == "Draft" + assert theProject.data.itemStatus.name("s000026") == "Finished" + assert theProject.data.itemImport.name("i000027") == "New" + assert theProject.data.itemImport.name("i000028") == "Minor" + assert theProject.data.itemImport.name("i000029") == "Major" + assert theProject.data.itemImport.name("i00002a") == "Main" # END Test testCoreProject_StatusImport diff --git a/tests/test_dialogs/test_dlg_projsettings.py b/tests/test_dialogs/test_dlg_projsettings.py index 71c90ba8..08121c6b 100644 --- a/tests/test_dialogs/test_dlg_projsettings.py +++ b/tests/test_dialogs/test_dlg_projsettings.py @@ -330,13 +330,13 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncDir, fncProj, # Check Project projSettings._doSave() - statusItems = dict(theProject.statusItems.items()) + statusItems = dict(theProject.data.itemStatus.items()) assert statusItems[C.sNew]["name"] == "New" assert statusItems[C.sDraft]["name"] == "Draft" assert statusItems[C.sFinished]["name"] == "Finished" assert statusItems["s000013"]["name"] == "Final" - importItems = dict(theProject.importItems.items()) + importItems = dict(theProject.data.itemImport.items()) assert importItems[C.iNew]["name"] == "New" assert importItems[C.iMajor]["name"] == "Major" assert importItems[C.iMain]["name"] == "Main"