Rewrite most of the NWStatus class

This commit is contained in:
Veronica Berglyd Olsen
2022-04-05 22:08:14 +02:00
parent 7298e1f438
commit 40e7055f16
3 changed files with 162 additions and 127 deletions
+6 -6
View File
@@ -280,11 +280,11 @@ class NWItem():
the current item based on its class. the current item based on its class.
""" """
if self._class in nwLists.CLS_NOVEL: if self._class in nwLists.CLS_NOVEL:
stName = self.theProject.statusItems.checkEntry(self._status) stName = self.theProject.statusItems.name(self._status)
stIcon = self.theProject.statusItems.getIcon(stName) stIcon = self.theProject.statusItems.icon(self._status)
else: else:
stName = self.theProject.importItems.checkEntry(self._import) stName = self.theProject.importItems.name(self._import)
stIcon = self.theProject.importItems.getIcon(stName) stIcon = self.theProject.importItems.icon(self._import)
return stName, stIcon return stName, stIcon
def setImportStatus(self, theLabel): def setImportStatus(self, theLabel):
@@ -383,14 +383,14 @@ class NWItem():
"""Set the item status by looking it up in the valid status """Set the item status by looking it up in the valid status
items of the current project. items of the current project.
""" """
self._status = self.theProject.statusItems.checkEntry(theStatus) self._status = self.theProject.statusItems.check(theStatus)
return return
def setImport(self, theImport): def setImport(self, theImport):
"""Set the item importance by looking it up in the valid import """Set the item importance by looking it up in the valid import
items of the current project. items of the current project.
""" """
self._import = self.theProject.importItems.checkEntry(theImport) self._import = self.theProject.importItems.check(theImport)
return return
def setExpanded(self, expState): def setExpanded(self, expState):
+12 -12
View File
@@ -218,16 +218,16 @@ class NWProject():
} }
self.spellCheck = False self.spellCheck = False
self.autoOutline = True self.autoOutline = True
self.statusItems = NWStatus() self.statusItems = NWStatus("s")
self.statusItems.addEntry(self.tr("New"), (100, 100, 100)) self.statusItems.write(None, self.tr("New"), (100, 100, 100))
self.statusItems.addEntry(self.tr("Note"), (200, 50, 0)) self.statusItems.write(None, self.tr("Note"), (200, 50, 0))
self.statusItems.addEntry(self.tr("Draft"), (200, 150, 0)) self.statusItems.write(None, self.tr("Draft"), (200, 150, 0))
self.statusItems.addEntry(self.tr("Finished"), (50, 200, 0)) self.statusItems.write(None, self.tr("Finished"), (50, 200, 0))
self.importItems = NWStatus() self.importItems = NWStatus("i")
self.importItems.addEntry(self.tr("New"), (100, 100, 100)) self.importItems.write(None, self.tr("New"), (100, 100, 100))
self.importItems.addEntry(self.tr("Minor"), (200, 50, 0)) self.importItems.write(None, self.tr("Minor"), (200, 50, 0))
self.importItems.addEntry(self.tr("Major"), (200, 150, 0)) self.importItems.write(None, self.tr("Major"), (200, 150, 0))
self.importItems.addEntry(self.tr("Main"), (50, 200, 0)) self.importItems.write(None, self.tr("Main"), (50, 200, 0))
self.lastEdited = None self.lastEdited = None
self.lastViewed = None self.lastViewed = None
self.lastWCount = 0 self.lastWCount = 0
@@ -1205,9 +1205,9 @@ class NWProject():
self.importItems.resetCounts() self.importItems.resetCounts()
for nwItem in self.projTree: for nwItem in self.projTree:
if nwItem.itemClass in nwLists.CLS_NOVEL: if nwItem.itemClass in nwLists.CLS_NOVEL:
self.statusItems.countEntry(nwItem.itemStatus) self.statusItems.increment(nwItem.itemStatus)
else: else:
self.importItems.countEntry(nwItem.itemImport) self.importItems.increment(nwItem.itemImport)
return return
def localLookup(self, theWord): def localLookup(self, theWord):
+144 -109
View File
@@ -23,6 +23,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import random
import logging import logging
import novelwriter import novelwriter
@@ -30,134 +31,158 @@ from lxml import etree
from PyQt5.QtGui import QIcon, QPixmap, QColor from PyQt5.QtGui import QIcon, QPixmap, QColor
from novelwriter.common import checkInt from novelwriter.common import checkInt, getMinMax, simplified
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class NWStatus(): class NWStatus():
def __init__(self): def __init__(self, type):
self._type = str(type)
self._store = {}
self._reverse = {}
self._default = None
self._theLabels = []
self._theColours = []
self._theCounts = []
self._theIcons = []
self._theMap = {}
self._theLength = 0
self._theIndex = 0
self._iconSize = novelwriter.CONFIG.pxInt(32) self._iconSize = novelwriter.CONFIG.pxInt(32)
pixmap = QPixmap(self._iconSize, self._iconSize)
pixmap.fill(QColor(100, 100, 100))
self._defaultIcon = QIcon(pixmap)
return return
def addEntry(self, theLabel, theColours): def write(self, key, name, cols):
"""Add a status entry to the status object, but ensure it isn't """Add or update a status entry. If the key is invalid, a new
a duplicate. key is generated.
""" """
theLabel = theLabel.strip() if not self._isKey(key):
if self._getIndex(theLabel) is None: key = self._newKey()
theIcon = QPixmap(self._iconSize, self._iconSize) if not isinstance(cols, tuple):
theIcon.fill(QColor(*theColours)) cols = (100, 100, 100)
self._theIcons.append(QIcon(theIcon)) if len(cols) != 3:
self._theLabels.append(theLabel) cols = (100, 100, 100)
self._theColours.append(theColours)
self._theCounts.append(0)
self._theMap[theLabel] = self._theLength
self._theLength += 1
return True pixmap = QPixmap(self._iconSize, self._iconSize)
pixmap.fill(QColor(*cols))
def checkEntry(self, theStatus): name = simplified(name)
"""Check if a status value is valid, and returns the safe count = self._store[key]["count"] if key in self._store else 0
reference to be used internally.
self._store[key] = {
"name": name,
"icon": QIcon(pixmap),
"cols": cols,
"count": count,
}
self._reverse[name] = key
if self._default is None:
self._default = key
return key
def check(self, value):
"""Check the key against the stored status names.
""" """
if isinstance(theStatus, str): if self._isKey(value) and value in self._store:
if self._getIndex(theStatus) is not None: return value
return theStatus.strip() elif value in self._reverse:
return self._theLabels[0] return self._reverse[value]
elif self._default is not None:
return self._default
else:
return ""
def getIcon(self, theLabel): def name(self, key):
"""Return the icon for the given status item. """Return the name associated with a given key.
""" """
theIndex = self._getIndex(theLabel) if key in self._store:
if theIndex is not None: return self._store[key]["name"]
return self._theIcons[theIndex] elif self._default is not None:
return QIcon() return self._store[self._default]["name"]
else:
return ""
def cols(self, key):
"""Return the colours associated with a given key.
"""
if key in self._store:
return self._store[key]["cols"]
elif self._default is not None:
return self._store[self._default]["cols"]
else:
return (100, 100, 100)
def count(self, key):
"""Return the count associated with a given key.
"""
if key in self._store:
return self._store[key]["count"]
elif self._default is not None:
return self._store[self._default]["count"]
else:
return 0
def icon(self, key):
"""Return the icon associated with a given key.
"""
if key in self._store:
return self._store[key]["icon"]
elif self._default is not None:
return self._store[self._default]["icon"]
else:
return self._defaultIcon
def setNewEntries(self, newList): def setNewEntries(self, newList):
"""Update the list of entries after they have been modified by """Update the list of entries after they have been modified by
the GUI tool. the GUI tool.
""" """
replaceMap = {} return {}
if newList is not None:
self._theLabels = []
self._theColours = []
self._theCounts = []
self._theIcons = []
self._theMap = {}
self._theLength = 0
self._theIndex = 0
for nName, nR, nG, nB, oName in newList:
self.addEntry(nName, (nR, nG, nB))
if nName != oName and oName is not None:
replaceMap[oName] = nName
return replaceMap
def resetCounts(self): def resetCounts(self):
"""Clear the counts of references to the status entries. """Clear the counts of references to the status entries.
""" """
self._theCounts = [0]*self._theLength for key in self._store:
self._store[key]["count"] = 0
return return
def countEntry(self, theLabel): def increment(self, key):
"""Increment the counter for a given label. This should be used """Increment the counter for a given entry.
together with resetCounts in a loop over project items.
""" """
theIndex = self._getIndex(theLabel) if key in self._store:
if theIndex is not None: self._store[key]["count"] += 1
self._theCounts[theIndex] += 1
return return
def packXML(self, xParent): def packXML(self, xParent):
"""Pack the status entries into an XML object for saving to the """Pack the status entries into an XML object for saving to the
main project file. main project file.
""" """
for n in range(self._theLength): for key, data in self._store.items():
xSub = etree.SubElement(xParent, "entry", attrib={ xSub = etree.SubElement(xParent, "entry", attrib={
"red": str(self._theColours[n][0]), "key": key,
"green": str(self._theColours[n][1]), "red": str(data["cols"][0]),
"blue": str(self._theColours[n][2]), "green": str(data["cols"][1]),
"blue": str(data["cols"][2]),
}) })
xSub.text = self._theLabels[n] xSub.text = data["name"]
return True return True
def unpackXML(self, xParent): def unpackXML(self, xParent):
"""Unpack an XML tree and set the class values. """Unpack an XML tree and set the class values.
""" """
theLabels = [] self._store = {}
theColours = [] self._reverse = {}
self._default = None
for xChild in xParent: for xChild in xParent:
theLabels.append(xChild.text) name = xChild.text.strip()
cR = checkInt(xChild.attrib.get("red", 0), 0, False) key = xChild.attrib.get("key", None)
cG = checkInt(xChild.attrib.get("green", 0), 0, False) cR = getMinMax(checkInt(xChild.attrib.get("red", 100), 100), 0, 255)
cB = checkInt(xChild.attrib.get("blue", 0), 0, False) cG = getMinMax(checkInt(xChild.attrib.get("green", 100), 100), 0, 255)
theColours.append((cR, cG, cB)) cB = getMinMax(checkInt(xChild.attrib.get("blue", 100), 100), 0, 255)
self.write(key, name, (cR, cG, cB))
if len(theLabels) > 0:
self._theLabels = []
self._theColours = []
self._theCounts = []
self._theIcons = []
self._theMap = {}
self._theLength = 0
self._theIndex = 0
for n in range(len(theLabels)):
self.addEntry(theLabels[n], theColours[n])
return True return True
@@ -165,39 +190,49 @@ class NWStatus():
# Internal Functions # Internal Functions
## ##
def _getIndex(self, theLabel): def _newKey(self):
"""Look up a status entry in the object lists, and return it if """Generate a new key for a status flag. This method is
it exists. recursive, but should only fail if there is an issue with the
random number generator or the user has added a lot of status
flags. The Python recursion limit is given the job to handle
the extreme case and will cause an app crash.
""" """
if theLabel is None: key = f"{self._type}{random.randint(0, 0xffffff):06x}"
return None if key in self._store:
return self._theMap.get(theLabel.strip(), None) key = self._newKey()
return key
def _isKey(self, key):
"""Check if a string is a key or not.
"""
if not isinstance(key, str):
return False
if len(key) != 7:
return False
if key[0] != self._type:
return False
for c in key[1:]:
if c not in "0123456789abcdef":
return False
return True
## ##
# Iterator Bits # Iterator Bits
## ##
def __getitem__(self, n): def __getitem__(self, key):
"""Return an entry by its index. return self._store[key]
"""
if n >= 0 and n < self._theLength:
return self._theLabels[n], self._theColours[n], self._theCounts[n], self._theIcons[n]
return None, None, None, QIcon()
def __iter__(self): def __iter__(self):
"""Initialise the iterator. return iter(self._store)
"""
self._theIndex = 0
return self
def __next__(self): def keys(self):
"""Return the next entry for the iterator. return self._store.keys()
"""
if self._theIndex < self._theLength: def items(self):
theLabel, theColour, theCount, theIcon = self.__getitem__(self._theIndex) return self._store.items()
self._theIndex += 1
return theLabel, theColour, theCount, theIcon def values(self):
else: return self._store.values()
raise StopIteration
# END Class NWStatus # END Class NWStatus