diff --git a/novelwriter/common.py b/novelwriter/common.py index 30ac0f2e..69f6bc05 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -184,7 +184,7 @@ def checkIntRange(value, first, last, default): return default -def getMinMax(value, minVal, maxVal): +def minmax(value, minVal, maxVal): """Make sure an integer is between min and max value (inclusive). """ return min(maxVal, max(minVal, value)) diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index ee6f65ba..d17cffc3 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -687,6 +687,8 @@ class NWProject(): if len(aKey) > 0: self._packProjectValue(xTitleFmt, aKey, aValue) + # Save Status/Importance + self.countStatus() xStatus = etree.SubElement(xSettings, "status") self.statusItems.packXML(xStatus) xStatus = etree.SubElement(xSettings, "importance") @@ -1018,7 +1020,8 @@ class NWProject(): if self.projSpell != theLang: self.projSpell = theLang self.setProjectChanged(True) - return True + return True + return False def setProjectLang(self, theLang): """Set the project-specific language. @@ -1065,26 +1068,46 @@ class NWProject(): self.setProjectChanged(True) return True - def setStatusColours(self, newCols): + def setStatusColours(self, newCols, delCols): """Update the list of novel file status flags. Also iterate through the project and replace keys that have been renamed. """ - replaceMap = self.statusItems.setNewEntries(newCols) - for nwItem in self.projTree: - if nwItem.itemStatus in replaceMap: - nwItem.setStatus(replaceMap[nwItem.itemStatus]) + if not (newCols or delCols): + return False + + for entry in newCols: + key = entry.get("key", None) + name = entry.get("name", "") + cols = entry.get("cols", (100, 100, 100)) + if name: + self.statusItems.write(key, name, cols) + + for key in delCols: + self.statusItems.remove(key) + self.setProjectChanged(True) + return True - def setImportColours(self, newCols): + def setImportColours(self, newCols, delCols): """Update the list of note file importance flags. Also iterate through the project and replace keys that have been renamed. """ - replaceMap = self.importItems.setNewEntries(newCols) - for nwItem in self.projTree: - if nwItem.itemImport in replaceMap: - nwItem.setImport(replaceMap[nwItem.itemImport]) + if not (newCols or delCols): + return False + + for entry in newCols: + key = entry.get("key", None) + name = entry.get("name", "") + cols = entry.get("cols", (100, 100, 100)) + if name: + self.importItems.write(key, name, cols) + + for key in delCols: + self.importItems.remove(key) + self.setProjectChanged(True) + return True def setAutoReplace(self, autoReplace): diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index 0f2f1aea..4aee93b2 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -4,7 +4,8 @@ novelWriter – Project Item Status Class Data class for the status/importance settings of a project item File History: -Created: 2019-05-19 [0.1.3] +Created: 2019-05-19 [0.1.3] +Rewritten: 2022-04-05 [1.7a0] This file is a part of novelWriter Copyright 2018–2022, Veronica Berglyd Olsen @@ -31,7 +32,7 @@ from lxml import etree from PyQt5.QtGui import QIcon, QPixmap, QColor -from novelwriter.common import checkInt, getMinMax, simplified +from novelwriter.common import checkInt, minmax, simplified logger = logging.getLogger(__name__) @@ -52,7 +53,7 @@ class NWStatus(): return - def write(self, key, name, cols): + def write(self, key, name, cols, count=None): """Add or update a status entry. If the key is invalid, a new key is generated. """ @@ -67,7 +68,8 @@ class NWStatus(): pixmap.fill(QColor(*cols)) name = simplified(name) - count = self._store[key]["count"] if key in self._store else 0 + if count is None: + count = self._store[key]["count"] if key in self._store else 0 self._store[key] = { "name": name, @@ -82,6 +84,20 @@ class NWStatus(): return key + def remove(self, key): + """Remove an entry in the list, but not if the count is larger + than 0. + """ + if key not in self._store: + return False + if self._store[key]["count"] > 0: + return False + + del self._reverse[self._store[key]["name"]] + del self._store[key] + + return True + def check(self, value): """Check the key against the stored status names. """ @@ -134,12 +150,6 @@ class NWStatus(): else: return self._defaultIcon - def setNewEntries(self, newList): - """Update the list of entries after they have been modified by - the GUI tool. - """ - return {} - def resetCounts(self): """Clear the counts of references to the status entries. """ @@ -161,6 +171,7 @@ class NWStatus(): for key, data in self._store.items(): xSub = etree.SubElement(xParent, "entry", attrib={ "key": key, + "count": str(data["count"]), "red": str(data["cols"][0]), "green": str(data["cols"][1]), "blue": str(data["cols"][2]), @@ -177,12 +188,13 @@ class NWStatus(): self._default = None for xChild in xParent: - name = xChild.text.strip() - key = xChild.attrib.get("key", None) - cR = getMinMax(checkInt(xChild.attrib.get("red", 100), 100), 0, 255) - cG = getMinMax(checkInt(xChild.attrib.get("green", 100), 100), 0, 255) - cB = getMinMax(checkInt(xChild.attrib.get("blue", 100), 100), 0, 255) - self.write(key, name, (cR, cG, cB)) + key = xChild.attrib.get("key", None) + name = xChild.text.strip() + count = max(checkInt(xChild.attrib.get("count", 0), 0), 0) + red = minmax(checkInt(xChild.attrib.get("red", 100), 100), 0, 255) + green = minmax(checkInt(xChild.attrib.get("green", 100), 100), 0, 255) + blue = minmax(checkInt(xChild.attrib.get("blue", 100), 100), 0, 255) + self.write(key, name, (red, green, blue), count) return True diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py index f6e4b611..260f8b10 100644 --- a/novelwriter/dialogs/projsettings.py +++ b/novelwriter/dialogs/projsettings.py @@ -35,6 +35,7 @@ from PyQt5.QtWidgets import ( ) from novelwriter.enum import nwAlert +from novelwriter.common import simplified from novelwriter.gui.custom import QSwitch, PagedDialog, QConfigLayout logger = logging.getLogger(__name__) @@ -81,6 +82,9 @@ class GuiProjectSettings(PagedDialog): self.buttonBox.rejected.connect(self._doClose) self.addControls(self.buttonBox) + # Flags + self.spellChanged = False + logger.debug("GuiProjectSettings initialisation complete") return @@ -103,16 +107,18 @@ class GuiProjectSettings(PagedDialog): self.theProject.setProjectName(projName) self.theProject.setBookTitle(bookTitle) self.theProject.setBookAuthors(bookAuthors) - self.theProject.setSpellLang(spellLang) self.theProject.setProjBackup(doBackup) + # Remember this as updating spell dictionary can be expensive + self.spellChanged = self.theProject.setSpellLang(spellLang) + if self.tabStatus.colChanged: - statusCol = self.tabStatus.getNewList() - self.theProject.setStatusColours(statusCol) + newList, delList = self.tabStatus.getNewList() + self.theProject.setStatusColours(newList, delList) if self.tabImport.colChanged: - importCol = self.tabImport.getNewList() - self.theProject.setImportColours(importCol) + newList, delList = self.tabImport.getNewList() + self.theProject.setImportColours(newList, delList) if self.tabStatus.colChanged or self.tabImport.colChanged: self.theParent.rebuildTrees() @@ -245,6 +251,10 @@ class GuiProjectEditStatus(QWidget): COL_LABEL = 0 COL_USAGE = 1 + KEY_ROLE = Qt.UserRole + COL_ROLE = Qt.UserRole + 1 + NUM_ROLE = Qt.UserRole + 2 + def __init__(self, theParent, theProject, isStatus): QWidget.__init__(self, theParent) @@ -267,10 +277,9 @@ class GuiProjectEditStatus(QWidget): self.optState.getInt("GuiProjectSettings", colSetting, 130) ) - self.colData = [] - self.colCounts = [] + self.colDeleted = [] self.colChanged = False - self.selColour = None + self.selColour = QColor(100, 100, 100) self.iPx = self.theTheme.baseIconSize @@ -285,8 +294,8 @@ class GuiProjectEditStatus(QWidget): self.listBox.setColumnWidth(self.COL_LABEL, wCol0) self.listBox.setIndentation(0) - for iName, iCol, nUse, _ in self.theStatus: - self._addItem(iName, iCol, iName, nUse) + for key, data in self.theStatus.items(): + self._addItem(key, data["name"], data["cols"], data["count"]) # List Controls # ============= @@ -349,12 +358,15 @@ class GuiProjectEditStatus(QWidget): if self.colChanged: newList = [] for n in range(self.listBox.topLevelItemCount()): - nItem = self.listBox.topLevelItem(n) - nIdx = nItem.data(self.COL_LABEL, Qt.UserRole) - newList.append(self.colData[nIdx]) - return newList + item = self.listBox.topLevelItem(n) + newList.append({ + "key": item.data(self.COL_LABEL, self.KEY_ROLE), + "name": item.text(self.COL_LABEL), + "cols": item.data(self.COL_LABEL, self.COL_ROLE), + }) + return newList, self.colDeleted - return None + return [], [] ## # User Actions @@ -369,16 +381,16 @@ class GuiProjectEditStatus(QWidget): ) if newCol.isValid(): self.selColour = newCol - colPixmap = QPixmap(self.iPx, self.iPx) - colPixmap.fill(newCol) - self.colButton.setIcon(QIcon(colPixmap)) - self.colButton.setIconSize(colPixmap.rect().size()) + pixmap = QPixmap(self.iPx, self.iPx) + pixmap.fill(newCol) + self.colButton.setIcon(QIcon(pixmap)) + self.colButton.setIconSize(pixmap.rect().size()) return def _newItem(self): """Create a new status item. """ - newItem = self._addItem(self.tr("New Item"), (0, 0, 0), None, 0) + newItem = self._addItem(None, self.tr("New Item"), (0, 0, 0), 0) newItem.setBackground(self.COL_LABEL, QBrush(QColor(0, 255, 0, 70))) newItem.setBackground(self.COL_USAGE, QBrush(QColor(0, 255, 0, 70))) self.colChanged = True @@ -390,14 +402,14 @@ class GuiProjectEditStatus(QWidget): selItem = self._getSelectedItem() if selItem is not None: iRow = self.listBox.indexOfTopLevelItem(selItem) - selIdx = selItem.data(self.COL_LABEL, Qt.UserRole) - if self.colCounts[selIdx] == 0: - self.listBox.takeTopLevelItem(iRow) - self.colChanged = True - else: + if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0: self.theParent.makeAlert(self.tr( "Cannot delete a status item that is in use." ), nwAlert.ERROR) + else: + self.listBox.takeTopLevelItem(iRow) + self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE)) + self.colChanged = True return def _saveItem(self): @@ -405,36 +417,33 @@ class GuiProjectEditStatus(QWidget): """ selItem = self._getSelectedItem() if selItem is not None: - selIdx = selItem.data(self.COL_LABEL, Qt.UserRole) - self.colData[selIdx] = ( - self.editName.text().strip(), - self.selColour.red(), - self.selColour.green(), - self.selColour.blue(), - self.colData[selIdx][4] - ) - selItem.setText(self.COL_LABEL, self.colData[selIdx][0]) - selItem.setText(self.COL_USAGE, self._usageString(self.colCounts[selIdx])) + selItem.setText(self.COL_LABEL, simplified(self.editName.text())) selItem.setIcon(self.COL_LABEL, self.colButton.icon()) + selItem.setData(self.COL_LABEL, self.COL_ROLE, ( + self.selColour.red(), self.selColour.green(), self.selColour.blue() + )) self.editName.setEnabled(False) self.colChanged = True return - def _addItem(self, iName, iCol, oName, nUse): + def _addItem(self, key, name, cols, count): """Add a status item to the list. """ - newIcon = QPixmap(self.iPx, self.iPx) - newIcon.fill(QColor(*iCol)) - newItem = QTreeWidgetItem() - newItem.setText(self.COL_LABEL, iName) - newItem.setText(self.COL_USAGE, self._usageString(nUse)) - newItem.setIcon(self.COL_LABEL, QIcon(newIcon)) - newItem.setData(self.COL_LABEL, Qt.UserRole, len(self.colData)) - self.listBox.addTopLevelItem(newItem) - self.colData.append((iName, iCol[0], iCol[1], iCol[2], oName)) - self.colCounts.append(nUse) - return newItem + pixmap = QPixmap(self.iPx, self.iPx) + pixmap.fill(QColor(*cols)) + + item = QTreeWidgetItem() + item.setText(self.COL_LABEL, name) + item.setIcon(self.COL_LABEL, QIcon(pixmap)) + item.setData(self.COL_LABEL, self.KEY_ROLE, key) + item.setData(self.COL_LABEL, self.COL_ROLE, cols) + item.setData(self.COL_LABEL, self.NUM_ROLE, count) + item.setText(self.COL_USAGE, self._usageString(count)) + + self.listBox.addTopLevelItem(item) + + return item def _selectedItem(self): """Extract the info of a selected item and populate the settings @@ -442,13 +451,14 @@ class GuiProjectEditStatus(QWidget): """ selItem = self._getSelectedItem() if selItem is not None: - selIdx = selItem.data(self.COL_LABEL, Qt.UserRole) - selVal = self.colData[selIdx] - self.selColour = QColor(selVal[1], selVal[2], selVal[3]) - newIcon = QPixmap(self.iPx, self.iPx) - newIcon.fill(self.selColour) - self.editName.setText(selVal[0]) - self.colButton.setIcon(QIcon(newIcon)) + cols = selItem.data(self.COL_LABEL, self.COL_ROLE) + name = selItem.text(self.COL_LABEL) + + pixmap = QPixmap(self.iPx, self.iPx) + pixmap.fill(QColor(*cols)) + self.selColour = QColor(*cols) + self.editName.setText(name) + self.colButton.setIcon(QIcon(pixmap)) self.editName.setEnabled(True) self.editName.selectAll() self.editName.setFocus() diff --git a/novelwriter/gui/itemdetails.py b/novelwriter/gui/itemdetails.py index 21df9b80..73082fa5 100644 --- a/novelwriter/gui/itemdetails.py +++ b/novelwriter/gui/itemdetails.py @@ -214,6 +214,11 @@ class GuiItemDetails(QWidget): return + def refreshDetails(self): + """Reload the content of the details panel. + """ + self.updateViewBox(self._itemHandle) + def updateViewBox(self, tHandle): """Populate the details box from a given handle. """ diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 9db27659..2ffffe45 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -1002,7 +1002,9 @@ class GuiMain(QMainWindow): if dlgProj.result() == QDialog.Accepted: logger.debug("Applying new project settings") - self.docEditor.setDictionaries() + if dlgProj.spellChanged: + self.docEditor.setDictionaries() + self.treeMeta.refreshDetails() self._updateWindowTitle(self.theProject.projName) return True