Save both status and importance flags (#1030)

* Put the status icons in the status object itself
* Allow saving both status and importance values
* Update current tests
* Improve test coverage
* Update sample project file
This commit is contained in:
Veronica Berglyd Olsen
2022-04-03 22:43:11 +02:00
committed by GitHub
parent a04b44d890
commit 23fd6b815f
29 changed files with 481 additions and 418 deletions
+40 -7
View File
@@ -50,6 +50,7 @@ class NWItem():
self._class = nwItemClass.NO_CLASS
self._layout = nwItemLayout.NO_LAYOUT
self._status = None
self._import = None
self._expanded = False
self._exported = True
@@ -104,6 +105,10 @@ class NWItem():
def itemStatus(self):
return self._status
@property
def itemImport(self):
return self._import
@property
def isExpanded(self):
return self._expanded
@@ -159,12 +164,13 @@ class NWItem():
nameAttrib = {}
nameAttrib["status"] = str(self._status)
nameAttrib["import"] = str(self._import)
if self._type == nwItemType.FILE:
nameAttrib["exported"] = str(self._exported)
xPack = etree.SubElement(xParent, "item", attrib=itemAttrib)
self._subPack(xPack, "meta", attrib=metaAttrib)
self._subPack(xPack, "name", text=str(self._name), attrib=nameAttrib)
self._subPack(xPack, "meta", attrib=metaAttrib)
self._subPack(xPack, "name", text=str(self._name), attrib=nameAttrib)
return
@@ -197,11 +203,12 @@ class NWItem():
elif xValue.tag == "name":
self.setName(xValue.text)
self.setStatus(xValue.attrib.get("status", None))
self.setImport(xValue.attrib.get("import", None))
self.setExported(xValue.attrib.get("exported", True))
# Legacy Format (1.3 and earlier)
elif xValue.tag == "status":
self.setStatus(xValue.text)
self.setImportStatus(xValue.text)
elif xValue.tag == "type":
self.setType(xValue.text)
elif xValue.tag == "class":
@@ -268,6 +275,28 @@ class NWItem():
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
def getImportStatus(self):
"""Return the relevant importance or status label and icon for
the current item based on its class.
"""
if self._class in nwLists.CLS_NOVEL:
stName = self.theProject.statusItems.checkEntry(self._status)
stIcon = self.theProject.statusItems.getIcon(stName)
else:
stName = self.theProject.importItems.checkEntry(self._import)
stIcon = self.theProject.importItems.getIcon(stName)
return stName, stIcon
def setImportStatus(self, theLabel):
"""Update the importance or status value based on class. This is
a wrapper setter for setStatus and setImport.
"""
if self._class in nwLists.CLS_NOVEL:
self.setStatus(theLabel)
else:
self.setImport(theLabel)
return
##
# Set Item Values
##
@@ -354,10 +383,14 @@ class NWItem():
"""Set the item status by looking it up in the valid status
items of the current project.
"""
if self._class in nwLists.CLS_NOVEL:
self._status = self.theProject.statusItems.checkEntry(theStatus)
else:
self._status = self.theProject.importItems.checkEntry(theStatus)
self._status = self.theProject.statusItems.checkEntry(theStatus)
return
def setImport(self, theImport):
"""Set the item importance by looking it up in the valid import
items of the current project.
"""
self._import = self.theProject.importItems.checkEntry(theImport)
return
def setExpanded(self, expState):
+7 -7
View File
@@ -46,7 +46,7 @@ from novelwriter.common import (
checkString, checkBool, checkInt, isHandle, formatTimeStamp,
makeFileNameSafe, hexToInt
)
from novelwriter.constants import trConst, nwFiles, nwLabels
from novelwriter.constants import nwLists, trConst, nwFiles, nwLabels
logger = logging.getLogger(__name__)
@@ -1071,7 +1071,7 @@ class NWProject():
"""
replaceMap = self.statusItems.setNewEntries(newCols)
for nwItem in self.projTree:
if nwItem.itemClass == nwItemClass.NOVEL:
if nwItem.itemClass in nwLists.CLS_NOVEL:
if nwItem.itemStatus in replaceMap:
nwItem.setStatus(replaceMap[nwItem.itemStatus])
self.setProjectChanged(True)
@@ -1083,9 +1083,9 @@ class NWProject():
"""
replaceMap = self.importItems.setNewEntries(newCols)
for nwItem in self.projTree:
if nwItem.itemClass != nwItemClass.NOVEL:
if nwItem.itemStatus in replaceMap:
nwItem.setStatus(replaceMap[nwItem.itemStatus])
if nwItem.itemClass not in nwLists.CLS_NOVEL:
if nwItem.itemImport in replaceMap:
nwItem.setImport(replaceMap[nwItem.itemImport])
self.setProjectChanged(True)
return True
@@ -1206,10 +1206,10 @@ class NWProject():
self.statusItems.resetCounts()
self.importItems.resetCounts()
for nwItem in self.projTree:
if nwItem.itemClass == nwItemClass.NOVEL:
if nwItem.itemClass in nwLists.CLS_NOVEL:
self.statusItems.countEntry(nwItem.itemStatus)
else:
self.importItems.countEntry(nwItem.itemStatus)
self.importItems.countEntry(nwItem.itemImport)
return
def localLookup(self, theWord):
+46 -30
View File
@@ -24,9 +24,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
import novelwriter
from lxml import etree
from PyQt5.QtGui import QIcon, QPixmap, QColor
from novelwriter.common import checkInt
logger = logging.getLogger(__name__)
@@ -39,9 +42,11 @@ class NWStatus():
self._theLabels = []
self._theColours = []
self._theCounts = []
self._theIcons = []
self._theMap = {}
self._theLength = 0
self._theIndex = 0
self._iconSize = novelwriter.CONFIG.pxInt(32)
return
@@ -50,38 +55,35 @@ class NWStatus():
a duplicate.
"""
theLabel = theLabel.strip()
if self.lookupEntry(theLabel) is None:
if self._getIndex(theLabel) is None:
theIcon = QPixmap(self._iconSize, self._iconSize)
theIcon.fill(QColor(*theColours))
self._theIcons.append(QIcon(theIcon))
self._theLabels.append(theLabel)
self._theColours.append(theColours)
self._theCounts.append(0)
self._theMap[theLabel] = self._theLength
self._theLength += 1
return True
def lookupEntry(self, theLabel):
"""Look up a status entry in the object lists, and return it if
it exists.
"""
if theLabel is None:
return None
theLabel = theLabel.strip()
if theLabel in self._theMap.keys():
return self._theMap[theLabel]
return None
return True
def checkEntry(self, theStatus):
"""Check if a status value is valid, and returns the safe
reference to be used internally.
"""
if isinstance(theStatus, str):
theStatus = theStatus.strip()
if self.lookupEntry(theStatus) is not None:
return theStatus
theStatus = checkInt(theStatus, 0, False)
if theStatus >= 0 and theStatus < self._theLength:
return self._theLabels[theStatus]
if self._getIndex(theStatus) is not None:
return theStatus.strip()
return self._theLabels[0]
def getIcon(self, theLabel):
"""Return the icon for the given status item.
"""
theIndex = self._getIndex(theLabel)
if theIndex is not None:
return self._theIcons[theIndex]
return QIcon()
def setNewEntries(self, newList):
"""Update the list of entries after they have been modified by
the GUI tool.
@@ -92,6 +94,7 @@ class NWStatus():
self._theLabels = []
self._theColours = []
self._theCounts = []
self._theIcons = []
self._theMap = {}
self._theLength = 0
self._theIndex = 0
@@ -113,7 +116,7 @@ class NWStatus():
"""Increment the counter for a given label. This should be used
together with resetCounts in a loop over project items.
"""
theIndex = self.lookupEntry(theLabel)
theIndex = self._getIndex(theLabel)
if theIndex is not None:
self._theCounts[theIndex] += 1
return
@@ -124,9 +127,9 @@ class NWStatus():
"""
for n in range(self._theLength):
xSub = etree.SubElement(xParent, "entry", attrib={
"blue": str(self._theColours[n][2]),
"green": str(self._theColours[n][1]),
"red": str(self._theColours[n][0]),
"green": str(self._theColours[n][1]),
"blue": str(self._theColours[n][2]),
})
xSub.text = self._theLabels[n]
return True
@@ -145,18 +148,31 @@ class NWStatus():
theColours.append((cR, cG, cB))
if len(theLabels) > 0:
self._theLabels = []
self._theLabels = []
self._theColours = []
self._theCounts = []
self._theMap = {}
self._theLength = 0
self._theIndex = 0
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
##
# Internal Functions
##
def _getIndex(self, theLabel):
"""Look up a status entry in the object lists, and return it if
it exists.
"""
if theLabel is None:
return None
return self._theMap.get(theLabel.strip(), None)
##
# Iterator Bits
##
@@ -165,8 +181,8 @@ class NWStatus():
"""Return an entry by its index.
"""
if n >= 0 and n < self._theLength:
return self._theLabels[n], self._theColours[n], self._theCounts[n]
return None, None, None
return self._theLabels[n], self._theColours[n], self._theCounts[n], self._theIcons[n]
return None, None, None, QIcon()
def __iter__(self):
"""Initialise the iterator.
@@ -178,9 +194,9 @@ class NWStatus():
"""Return the next entry for the iterator.
"""
if self._theIndex < self._theLength:
theLabel, theColour, theCount = self.__getitem__(self._theIndex)
theLabel, theColour, theCount, theIcon = self.__getitem__(self._theIndex)
self._theIndex += 1
return theLabel, theColour, theCount
return theLabel, theColour, theCount, theIcon
else:
raise StopIteration
+1
View File
@@ -133,6 +133,7 @@ class GuiDocMerge(QDialog):
nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemClass, srcItem.itemParent)
newItem = self.theProject.projTree[nHandle]
newItem.setStatus(srcItem.itemStatus)
newItem.setImport(srcItem.itemImport)
outDoc = NWDoc(self.theProject, nHandle)
if not outDoc.writeDocument(theText):
+1
View File
@@ -203,6 +203,7 @@ class GuiDocSplit(QDialog):
newItem = self.theProject.projTree[nHandle]
newItem.setLayout(itemLayout)
newItem.setStatus(srcItem.itemStatus)
newItem.setImport(srcItem.itemImport)
logger.verbose(
"Creating new document '%s' with text from line %d to %d",
nHandle, iStart+1, iEnd
+7 -10
View File
@@ -75,15 +75,11 @@ class GuiItemEditor(QDialog):
self.editStatus = QComboBox()
self.editStatus.setMinimumWidth(mVd)
if self.theItem.itemClass in nwLists.CLS_NOVEL:
for sLabel, _, _ in self.theProject.statusItems:
self.editStatus.addItem(
self.theParent.statusIcons[sLabel], sLabel, sLabel
)
for sLabel, _, _, sIcon in self.theProject.statusItems:
self.editStatus.addItem(sIcon, sLabel, sLabel)
else:
for sLabel, _, _ in self.theProject.importItems:
self.editStatus.addItem(
self.theParent.importIcons[sLabel], sLabel, sLabel
)
for sLabel, _, _, sIcon in self.theProject.importItems:
self.editStatus.addItem(sIcon, sLabel, sLabel)
# Item Layout
self.editLayout = QComboBox()
@@ -120,7 +116,8 @@ class GuiItemEditor(QDialog):
self.editName.setText(self.theItem.itemName)
self.editName.selectAll()
statusIdx = self.editStatus.findData(self.theItem.itemStatus)
currStatus, _ = self.theItem.getImportStatus()
statusIdx = self.editStatus.findData(currStatus)
if statusIdx != -1:
self.editStatus.setCurrentIndex(statusIdx)
@@ -180,7 +177,7 @@ class GuiItemEditor(QDialog):
isExported = self.editExport.isChecked()
self.theItem.setName(itemName)
self.theItem.setStatus(itemStatus)
self.theItem.setImportStatus(itemStatus)
self.theItem.setLayout(itemLayout)
self.theItem.setExported(isExported)
+1 -1
View File
@@ -285,7 +285,7 @@ class GuiProjectEditStatus(QWidget):
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
self.listBox.setIndentation(0)
for iName, iCol, nUse in self.theStatus:
for iName, iCol, nUse, _ in self.theStatus:
self._addItem(iName, iCol, iName, nUse)
# List Controls
+3 -10
View File
@@ -50,7 +50,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter.core import NWDoc, NWSpellEnchant, countWords
from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwItemClass
from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert
from novelwriter.common import transferCase
from novelwriter.constants import nwConst, nwKeyWords, nwUnicode
from novelwriter.gui.dochighlight import GuiDocHighlighter
@@ -2940,17 +2940,10 @@ class GuiDocEditFooter(QWidget):
sIcon = QPixmap()
sText = ""
else:
iStatus = self._theItem.itemStatus
if self._theItem.itemClass == nwItemClass.NOVEL:
iStatus = self.theProject.statusItems.checkEntry(iStatus)
theIcon = self.theParent.statusIcons[iStatus]
else:
iStatus = self.theProject.importItems.checkEntry(iStatus)
theIcon = self.theParent.importIcons[iStatus]
theStatus, theIcon = self._theItem.getImportStatus()
sIcon = theIcon.pixmap(self.sPx, self.sPx)
hLevel = self.theParent.theIndex.getHandleHeaderLevel(self._docHandle)
sText = f"{self._theItem.itemStatus} / {self._theItem.describeMe(hLevel)}"
sText = f"{theStatus} / {self._theItem.describeMe(hLevel)}"
self.statusIcon.setPixmap(sIcon)
self.statusText.setText(sText)
+4 -11
View File
@@ -30,7 +30,7 @@ from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QFont, QPixmap
from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel
from novelwriter.enum import nwItemClass, nwItemType
from novelwriter.enum import nwItemType
from novelwriter.constants import trConst, nwLabels
logger = logging.getLogger(__name__)
@@ -249,16 +249,9 @@ class GuiItemDetails(QWidget):
# Status
# ======
itStatus = nwItem.itemStatus
if nwItem.itemClass == nwItemClass.NOVEL:
itStatus = self.theProject.statusItems.checkEntry(itStatus) # Make sure it's valid
flagIcon = self.theParent.statusIcons[itStatus]
else:
itStatus = self.theProject.importItems.checkEntry(itStatus) # Make sure it's valid
flagIcon = self.theParent.importIcons[itStatus]
self.statusIcon.setPixmap(flagIcon.pixmap(iPx, iPx))
self.statusData.setText(nwItem.itemStatus)
theStatus, theIcon = nwItem.getImportStatus()
self.statusIcon.setPixmap(theIcon.pixmap(iPx, iPx))
self.statusData.setText(theStatus)
# Class
# =====
+3 -10
View File
@@ -610,14 +610,7 @@ class GuiProjectTree(QTreeWidget):
else:
expIcon = self.theTheme.getIcon("cross")
iStatus = nwItem.itemStatus
if nwItem.itemClass == nwItemClass.NOVEL:
iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's valid
statIcon = self.theParent.statusIcons[iStatus]
else:
iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's valid
statIcon = self.theParent.importIcons[iStatus]
itempStatus, statusIcon = nwItem.getImportStatus()
hLevel = self.theIndex.getHandleHeaderLevel(tHandle)
itemIcon = self.theTheme.getItemIcon(
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
@@ -626,8 +619,8 @@ class GuiProjectTree(QTreeWidget):
trItem.setIcon(self.C_NAME, itemIcon)
trItem.setText(self.C_NAME, nwItem.itemName)
trItem.setIcon(self.C_EXPORT, expIcon)
trItem.setIcon(self.C_STATUS, statIcon)
trItem.setToolTip(self.C_STATUS, nwItem.itemStatus)
trItem.setIcon(self.C_STATUS, statusIcon)
trItem.setToolTip(self.C_STATUS, itempStatus)
if self.mainConf.emphLabels and nwItem.itemLayout == nwItemLayout.DOCUMENT:
trFont = trItem.font(self.C_NAME)
+1 -30
View File
@@ -31,7 +31,7 @@ from time import time
from datetime import datetime
from PyQt5.QtCore import Qt, QTimer, QSize, QThreadPool, pyqtSlot
from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor
from PyQt5.QtGui import QIcon, QKeySequence, QCursor
from PyQt5.QtWidgets import (
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
QMessageBox, QDialog, QTabWidget, QToolBar, QAction
@@ -129,10 +129,6 @@ class GuiMain(QMainWindow):
self.treeView.novelItemChanged.connect(self._treeNovelItemChanged)
self.treeView.wordCountsChanged.connect(self._updateStatusWordCount)
# Minor GUI Elements
self.statusIcons = []
self.importIcons = []
# Project Tree Tabs
self.projTabs = QTabWidget()
self.projTabs.setTabPosition(QTabWidget.South)
@@ -869,8 +865,6 @@ class GuiMain(QMainWindow):
def rebuildTrees(self):
"""Rebuild the project tree.
"""
self._makeStatusIcons()
self._makeImportIcons()
self.treeView.buildTree()
self.novelView.refreshTree()
return
@@ -1462,29 +1456,6 @@ class GuiMain(QMainWindow):
self.saveDocument()
return
def _makeStatusIcons(self):
"""Generate all the item status icons based on project settings.
"""
self.statusIcons = {}
iPx = self.mainConf.pxInt(32)
for sLabel, sCol, _ in self.theProject.statusItems:
theIcon = QPixmap(iPx, iPx)
theIcon.fill(QColor(*sCol))
self.statusIcons[sLabel] = QIcon(theIcon)
return
def _makeImportIcons(self):
"""Generate all the item importance icons based on project
settings.
"""
self.importIcons = {}
iPx = self.mainConf.pxInt(32)
for sLabel, sCol, _ in self.theProject.importItems:
theIcon = QPixmap(iPx, iPx)
theIcon.fill(QColor(*sCol))
self.importIcons[sLabel] = QIcon(theIcon)
return
def _assembleProjectWizardData(self, newProj):
"""Extract the user choices from the New Project Wizard and
store them in a dictionary.