From f17302439e4143e534cdc2b96d97aa9c2619e97d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:38:43 +0200 Subject: [PATCH] Simplify further how status labels are added and updated --- novelwriter/core/coretools.py | 2 +- novelwriter/core/project.py | 64 +++++++------------ novelwriter/core/projectxml.py | 2 +- novelwriter/core/status.py | 87 ++++++++------------------ novelwriter/dialogs/projectsettings.py | 56 +++++++---------- 5 files changed, 69 insertions(+), 142 deletions(-) diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py index b8fc5ba1..925db901 100644 --- a/novelwriter/core/coretools.py +++ b/novelwriter/core/coretools.py @@ -104,7 +104,7 @@ class DocMerger: docText = self._project.storage.getDocumentText(srcHandle).rstrip("\n") if addComment: docInfo = srcItem.describeMe() - docSt, _ = srcItem.getImportStatus(incIcon=False) + docSt, _ = srcItem.getImportStatus() cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n" docText = cmtLine + docText diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 2a9e156a..08a3dd11 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -26,34 +26,32 @@ from __future__ import annotations import json import logging +from collections.abc import Iterable from enum import Enum +from functools import partial +from pathlib import Path from time import time from typing import TYPE_CHECKING -from pathlib import Path -from functools import partial -from collections.abc import Iterable from PyQt5.QtCore import QCoreApplication from novelwriter import CONFIG, SHARED, __version__, __hexversion__ -from novelwriter.core.status import StatusEntry -from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout -from novelwriter.error import logException -from novelwriter.constants import trConst, nwLabels -from novelwriter.core.tree import NWTree -from novelwriter.core.index import NWIndex -from novelwriter.core.options import OptionState -from novelwriter.core.storage import NWStorage, NWStorageOpen -from novelwriter.core.sessions import NWSessionLog -from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState -from novelwriter.core.projectdata import NWProjectData from novelwriter.common import ( checkStringNone, formatInt, formatTimeStamp, getFileSize, hexToInt, makeFileNameSafe, minmax ) +from novelwriter.constants import trConst, nwLabels +from novelwriter.core.index import NWIndex +from novelwriter.core.options import OptionState +from novelwriter.core.projectdata import NWProjectData +from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState +from novelwriter.core.sessions import NWSessionLog +from novelwriter.core.storage import NWStorage, NWStorageOpen +from novelwriter.core.tree import NWTree +from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout +from novelwriter.error import logException if TYPE_CHECKING: # pragma: no cover from novelwriter.core.item import NWItem - from novelwriter.core.status import NWStatus logger = logging.getLogger(__name__) @@ -462,14 +460,14 @@ class NWProject: def setDefaultStatusImport(self) -> None: """Set the default status and importance values.""" - self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100), "SQUARE") - self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0), "SQUARE") - self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0), "SQUARE") - self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0), "SQUARE") - self._data.itemImport.write(None, self.tr("New"), (100, 100, 100), "SQUARE") - self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0), "SQUARE") - self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0), "SQUARE") - self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0), "SQUARE") + self._data.itemStatus.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0) + self._data.itemStatus.add(None, self.tr("Note"), (200, 50, 0), "SQUARE", 0) + self._data.itemStatus.add(None, self.tr("Draft"), (200, 150, 0), "SQUARE", 0) + self._data.itemStatus.add(None, self.tr("Finished"), (50, 200, 0), "SQUARE", 0) + self._data.itemImport.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0) + self._data.itemImport.add(None, self.tr("Minor"), (200, 50, 0), "SQUARE", 0) + self._data.itemImport.add(None, self.tr("Major"), (200, 150, 0), "SQUARE", 0) + self._data.itemImport.add(None, self.tr("Main"), (50, 200, 0), "SQUARE", 0) return def setProjectLang(self, language: str | None) -> None: @@ -492,14 +490,6 @@ class NWProject: self.setProjectChanged(True) return - def setStatus(self, update: list[tuple[str | None, StatusEntry]], remove: list[str]) -> None: - """Update the list of novel file status flags.""" - return self._setStatusImport(update, remove, self._data.itemStatus) - - def setImport(self, update: list[tuple[str | None, StatusEntry]], remove: list[str]) -> None: - """Update the list of note file importance flags.""" - return self._setStatusImport(update, remove, self._data.itemImport) - def setProjectChanged(self, status: bool) -> bool: """Toggle the project changed flag, and propagate the information to the GUI statusbar. @@ -585,18 +575,6 @@ class NWProject: # Internal Functions ## - def _setStatusImport(self, update: list[tuple[str | None, StatusEntry]], - remove: list[str], target: NWStatus) -> None: - """Update the list of novel file status or importance flags, and - delete those that have been requested deleted. - """ - if update or remove: - order = [target.write(k, e.name, e.color, e.shape) for k, e in update] - for key in remove: - target.remove(key) - target.reorder(order) - return - def _loadProjectLocalisation(self) -> bool: """Load the language data for the current project language.""" if self._data.language is None or CONFIG._nwLangPath is None: diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py index eedcde10..ac28ed4a 100644 --- a/novelwriter/core/projectxml.py +++ b/novelwriter/core/projectxml.py @@ -439,7 +439,7 @@ class ProjectXMLReader: blue = checkInt(xEntry.attrib.get("blue", 0), 0) count = checkInt(xEntry.attrib.get("count", 0), 0) shape = xEntry.attrib.get("shape", "") - sObject.write(key, xEntry.text or "", (red, green, blue), shape, count) + sObject.add(key, xEntry.text or "", (red, green, blue), shape, count) return def _parseDictKeyText(self, xItem: ET.Element) -> dict: diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index ebf3d662..df5414d9 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -35,7 +35,7 @@ from typing import TYPE_CHECKING, Literal from PyQt5.QtCore import QPointF, Qt from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor, QPolygonF -from novelwriter import CONFIG +from novelwriter import SHARED from novelwriter.common import simplified from novelwriter.enum import nwStatusShape from novelwriter.types import QtPaintAnitAlias, QtTransparent @@ -77,7 +77,7 @@ class NWStatus: self._store: dict[str, StatusEntry] = {} self._default = None - self._iPX = CONFIG.pxInt(24) + self._iPx = SHARED.theme.baseIconHeight if self._type == self.STATUS: self._prefix = "s" @@ -103,62 +103,42 @@ class NWStatus: # Methods ## - def write(self, key: str | None, name: str, color: tuple[int, int, int] | QColor, - shape: nwStatusShape | str, count: int = 0) -> str: + def add(self, key: str | None, name: str, color: tuple[int, int, int], + shape: str, count: int) -> str: """Add or update a status entry. If the key is invalid, a new key is generated. """ - if not self._isKey(key): - key = self._newKey() - - if isinstance(color, QColor): - qColor = color - elif isinstance(color, tuple) and len(color) == 3: + if isinstance(color, tuple) and len(color) == 3: qColor = QColor(*color) else: qColor = QColor(100, 100, 100) - if not isinstance(shape, nwStatusShape): - try: - shape = nwStatusShape[shape] - except KeyError: - shape = nwStatusShape.SQUARE + try: + iShape = nwStatusShape[shape] + except KeyError: + iShape = nwStatusShape.SQUARE + key = self._checkKey(key) name = simplified(name) - icon = self.createIcon(self._iPX, qColor, shape) - - if key and key in self._store: - entry = self._store[key] - entry.name = name - entry.color = qColor - entry.shape = shape - entry.icon = icon - entry.count = count - else: - self._store[key] = StatusEntry(name, qColor, shape, icon, count) + icon = self.createIcon(self._iPx, qColor, iShape) + self._store[key] = StatusEntry(name, qColor, iShape, icon, count) if self._default is None: self._default = key return key - def remove(self, key: str) -> bool: - """Remove an entry in the list, except if the count > 0.""" - if key not in self._store: - return False - if self._store[key].count > 0: - return False + def update(self, update: list[tuple[str | None, StatusEntry]]) -> None: + """Update the list of statuses, and from removed list.""" + self._store.clear() + for key, entry in update: + self._store[self._checkKey(key)] = entry - del self._store[key] + # Check if we need a new default + if self._default not in self._store: + self._default = next(iter(self._store)) if self._store else None - keys = list(self._store.keys()) - if key == self._default: - if len(keys) > 0: - self._default = keys[0] - else: - self._default = None - - return True + return def check(self, value: str) -> str: """Check the key against the stored status names.""" @@ -168,27 +148,6 @@ class NWStatus: return self._default return "" - def reorder(self, order: list[str]) -> bool: - """Reorder the items according to list.""" - if len(order) != len(self._store): - logger.error("Length mismatch between new and old order") - return False - - if order == list(self._store.keys()): - return False - - store = {} - for key in order: - if key in self._store: - store[key] = self._store[key] - else: - logger.error("Unknown key '%s' in order", key) - return False - - self._store = store - - return True - def resetCounts(self) -> None: """Clear the counts of references to the status entries.""" for key in self._store: @@ -264,6 +223,10 @@ class NWStatus: return False return True + def _checkKey(self, key: str | None) -> str: + """Check key is valid, and if not, generate one.""" + return key if self._isKey(key) else self._newKey() + # END Class NWStatus diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index ff3663fe..e4543864 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -181,19 +181,16 @@ class GuiProjectSettings(QDialog): rebuildTrees = False - if self.statusPage.wasChanged: - update, remove = self.statusPage.getNewList() - project.setStatus(update, remove) + if self.statusPage.changed: + project.data.itemStatus.update(self.statusPage.getNewList()) rebuildTrees = True - if self.importPage.wasChanged: - update, remove = self.importPage.getNewList() - project.setImport(update, remove) + if self.importPage.changed: + project.data.itemImport.update(self.importPage.getNewList()) rebuildTrees = True - if self.replacePage.wasChanged: - update = self.replacePage.getNewList() - project.data.setAutoReplace(update) + if self.replacePage.changed: + project.data.setAutoReplace(self.replacePage.getNewList()) self.newProjectSettingsReady.emit(rebuildTrees) QApplication.processEvents() @@ -327,7 +324,6 @@ class _StatusPage(NFixedPage): ) self._changed = False - self._colDeleted: list[str] = [] self._selColour = QColor(100, 100, 100) self._iPx = SHARED.theme.baseIconHeight @@ -415,7 +411,7 @@ class _StatusPage(NFixedPage): return @property - def wasChanged(self) -> bool: + def changed(self) -> bool: """The user changed these settings.""" return self._changed @@ -423,7 +419,7 @@ class _StatusPage(NFixedPage): # Methods ## - def getNewList(self) -> tuple[list[tuple[str | None, StatusEntry]], list[str]]: + def getNewList(self) -> list[tuple[str | None, StatusEntry]]: """Return list of entries.""" if self._changed: update = [] @@ -432,8 +428,8 @@ class _StatusPage(NFixedPage): key = item.data(self.C_DATA, self.D_KEY) entry = item.data(self.C_DATA, self.D_ENTRY) update.append((key, entry)) - return update, self._colDeleted - return [], [] + return update + return [] def columnWidth(self) -> int: """Return the size of the header column.""" @@ -478,7 +474,6 @@ class _StatusPage(NFixedPage): SHARED.error(self.tr("Cannot delete a status item that is in use.")) else: self.listBox.takeTopLevelItem(iRow) - self._colDeleted.append(item.data(self.C_DATA, self.D_KEY)) self._changed = True return @@ -543,24 +538,15 @@ class _StatusPage(NFixedPage): def _moveItem(self, step: int) -> None: """Move and item up or down step.""" - selItem = self._getSelectedItem() - if selItem is None: - return - - tIndex = self.listBox.indexOfTopLevelItem(selItem) - nChild = self.listBox.topLevelItemCount() - nIndex = tIndex + step - if nIndex < 0 or nIndex >= nChild: - return - - cItem = self.listBox.takeTopLevelItem(tIndex) - self.listBox.insertTopLevelItem(nIndex, cItem) - self.listBox.clearSelection() - - if cItem is not None: - cItem.setSelected(True) - self._changed = True - + if item := self._getSelectedItem(): + tIdx = self.listBox.indexOfTopLevelItem(item) + nItm = self.listBox.topLevelItemCount() + nIdx = tIdx + step + if (0 <= nIdx < nItm) and (cItem := self.listBox.takeTopLevelItem(tIdx)): + self.listBox.insertTopLevelItem(nIdx, cItem) + self.listBox.clearSelection() + cItem.setSelected(True) + self._changed = True return def _getSelectedItem(self) -> QTreeWidgetItem | None: @@ -665,7 +651,7 @@ class _ReplacePage(NFixedPage): return @property - def wasChanged(self) -> bool: + def changed(self) -> bool: """The user changed these settings.""" return self._changed @@ -673,7 +659,7 @@ class _ReplacePage(NFixedPage): # Methods ## - def getNewList(self) -> dict: + def getNewList(self) -> dict[str, str]: """Extract the list from the widget.""" new = {} for n in range(self.listBox.topLevelItemCount()):