Rewrite most of the NWStatus class
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user