From 9c9408d78a868c94c2d599dbe5c118e4a4a19f54 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Tue, 5 Apr 2022 11:22:41 +0200
Subject: [PATCH 01/17] Changing status or importance flags should still update
hidden flags
---
novelwriter/core/project.py | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index ff2c2774..04e5242e 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -1071,9 +1071,8 @@ class NWProject():
"""
replaceMap = self.statusItems.setNewEntries(newCols)
for nwItem in self.projTree:
- if nwItem.itemClass in nwLists.CLS_NOVEL:
- if nwItem.itemStatus in replaceMap:
- nwItem.setStatus(replaceMap[nwItem.itemStatus])
+ if nwItem.itemStatus in replaceMap:
+ nwItem.setStatus(replaceMap[nwItem.itemStatus])
self.setProjectChanged(True)
return True
@@ -1083,9 +1082,8 @@ class NWProject():
"""
replaceMap = self.importItems.setNewEntries(newCols)
for nwItem in self.projTree:
- if nwItem.itemClass not in nwLists.CLS_NOVEL:
- if nwItem.itemImport in replaceMap:
- nwItem.setImport(replaceMap[nwItem.itemImport])
+ if nwItem.itemImport in replaceMap:
+ nwItem.setImport(replaceMap[nwItem.itemImport])
self.setProjectChanged(True)
return True
From 7298e1f438b3110a81d2f31e4d92a6be827ab910 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 5 Apr 2022 22:07:20 +0200
Subject: [PATCH 02/17] Add two new utility functions to the common module
---
novelwriter/common.py | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/novelwriter/common.py b/novelwriter/common.py
index 8e17a7ee..30ac0f2e 100644
--- a/novelwriter/common.py
+++ b/novelwriter/common.py
@@ -184,6 +184,12 @@ def checkIntRange(value, first, last, default):
return default
+def getMinMax(value, minVal, maxVal):
+ """Make sure an integer is between min and max value (inclusive).
+ """
+ return min(maxVal, max(minVal, value))
+
+
def checkIntTuple(value, valid, default):
"""Check that an int is an element of a tuple. If it isn't, return
the default value.
@@ -245,6 +251,13 @@ def formatTime(tS):
# String Functions
# =============================================================================================== #
+def simplified(string):
+ """Take a string an strip leading and trailing whitespaces, and
+ replace all occurences of (multiple) whitespaces with a 0x20 space.
+ """
+ return " ".join(str(string).strip().split())
+
+
def splitVersionNumber(value):
"""Split a version string on the form aa.bb.cc into major, minor
and patch, and computes an integer value aabbcc.
From 40e7055f167c5f5f9442636e52a73c31be358807 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 5 Apr 2022 22:08:14 +0200
Subject: [PATCH 03/17] Rewrite most of the NWStatus class
---
novelwriter/core/item.py | 12 +-
novelwriter/core/project.py | 24 ++--
novelwriter/core/status.py | 253 ++++++++++++++++++++----------------
3 files changed, 162 insertions(+), 127 deletions(-)
diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py
index 993b183c..d56d60f2 100644
--- a/novelwriter/core/item.py
+++ b/novelwriter/core/item.py
@@ -280,11 +280,11 @@ class NWItem():
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)
+ stName = self.theProject.statusItems.name(self._status)
+ stIcon = self.theProject.statusItems.icon(self._status)
else:
- stName = self.theProject.importItems.checkEntry(self._import)
- stIcon = self.theProject.importItems.getIcon(stName)
+ stName = self.theProject.importItems.name(self._import)
+ stIcon = self.theProject.importItems.icon(self._import)
return stName, stIcon
def setImportStatus(self, theLabel):
@@ -383,14 +383,14 @@ class NWItem():
"""Set the item status by looking it up in the valid status
items of the current project.
"""
- self._status = self.theProject.statusItems.checkEntry(theStatus)
+ self._status = self.theProject.statusItems.check(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)
+ self._import = self.theProject.importItems.check(theImport)
return
def setExpanded(self, expState):
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index 04e5242e..ee6f65ba 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -218,16 +218,16 @@ class NWProject():
}
self.spellCheck = False
self.autoOutline = True
- self.statusItems = NWStatus()
- self.statusItems.addEntry(self.tr("New"), (100, 100, 100))
- self.statusItems.addEntry(self.tr("Note"), (200, 50, 0))
- self.statusItems.addEntry(self.tr("Draft"), (200, 150, 0))
- self.statusItems.addEntry(self.tr("Finished"), (50, 200, 0))
- self.importItems = NWStatus()
- self.importItems.addEntry(self.tr("New"), (100, 100, 100))
- self.importItems.addEntry(self.tr("Minor"), (200, 50, 0))
- self.importItems.addEntry(self.tr("Major"), (200, 150, 0))
- self.importItems.addEntry(self.tr("Main"), (50, 200, 0))
+ self.statusItems = NWStatus("s")
+ self.statusItems.write(None, self.tr("New"), (100, 100, 100))
+ self.statusItems.write(None, self.tr("Note"), (200, 50, 0))
+ self.statusItems.write(None, self.tr("Draft"), (200, 150, 0))
+ self.statusItems.write(None, self.tr("Finished"), (50, 200, 0))
+ self.importItems = NWStatus("i")
+ self.importItems.write(None, self.tr("New"), (100, 100, 100))
+ self.importItems.write(None, self.tr("Minor"), (200, 50, 0))
+ self.importItems.write(None, self.tr("Major"), (200, 150, 0))
+ self.importItems.write(None, self.tr("Main"), (50, 200, 0))
self.lastEdited = None
self.lastViewed = None
self.lastWCount = 0
@@ -1205,9 +1205,9 @@ class NWProject():
self.importItems.resetCounts()
for nwItem in self.projTree:
if nwItem.itemClass in nwLists.CLS_NOVEL:
- self.statusItems.countEntry(nwItem.itemStatus)
+ self.statusItems.increment(nwItem.itemStatus)
else:
- self.importItems.countEntry(nwItem.itemImport)
+ self.importItems.increment(nwItem.itemImport)
return
def localLookup(self, theWord):
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index 2ca8ed53..0f2f1aea 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -23,6 +23,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import random
import logging
import novelwriter
@@ -30,134 +31,158 @@ from lxml import etree
from PyQt5.QtGui import QIcon, QPixmap, QColor
-from novelwriter.common import checkInt
+from novelwriter.common import checkInt, getMinMax, simplified
logger = logging.getLogger(__name__)
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)
+ pixmap = QPixmap(self._iconSize, self._iconSize)
+ pixmap.fill(QColor(100, 100, 100))
+ self._defaultIcon = QIcon(pixmap)
return
- def addEntry(self, theLabel, theColours):
- """Add a status entry to the status object, but ensure it isn't
- a duplicate.
+ def write(self, key, name, cols):
+ """Add or update a status entry. If the key is invalid, a new
+ key is generated.
"""
- theLabel = theLabel.strip()
- 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
+ if not self._isKey(key):
+ key = self._newKey()
+ if not isinstance(cols, tuple):
+ cols = (100, 100, 100)
+ if len(cols) != 3:
+ cols = (100, 100, 100)
- return True
+ pixmap = QPixmap(self._iconSize, self._iconSize)
+ pixmap.fill(QColor(*cols))
- def checkEntry(self, theStatus):
- """Check if a status value is valid, and returns the safe
- reference to be used internally.
+ name = simplified(name)
+ count = self._store[key]["count"] if key in self._store else 0
+
+ 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._getIndex(theStatus) is not None:
- return theStatus.strip()
- return self._theLabels[0]
+ if self._isKey(value) and value in self._store:
+ return value
+ elif value in self._reverse:
+ return self._reverse[value]
+ elif self._default is not None:
+ return self._default
+ else:
+ return ""
- def getIcon(self, theLabel):
- """Return the icon for the given status item.
+ def name(self, key):
+ """Return the name associated with a given key.
"""
- theIndex = self._getIndex(theLabel)
- if theIndex is not None:
- return self._theIcons[theIndex]
- return QIcon()
+ if key in self._store:
+ return self._store[key]["name"]
+ elif self._default is not None:
+ 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):
"""Update the list of entries after they have been modified by
the GUI tool.
"""
- replaceMap = {}
-
- 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
+ return {}
def resetCounts(self):
"""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
- def countEntry(self, theLabel):
- """Increment the counter for a given label. This should be used
- together with resetCounts in a loop over project items.
+ def increment(self, key):
+ """Increment the counter for a given entry.
"""
- theIndex = self._getIndex(theLabel)
- if theIndex is not None:
- self._theCounts[theIndex] += 1
+ if key in self._store:
+ self._store[key]["count"] += 1
return
def packXML(self, xParent):
"""Pack the status entries into an XML object for saving to the
main project file.
"""
- for n in range(self._theLength):
+ for key, data in self._store.items():
xSub = etree.SubElement(xParent, "entry", attrib={
- "red": str(self._theColours[n][0]),
- "green": str(self._theColours[n][1]),
- "blue": str(self._theColours[n][2]),
+ "key": key,
+ "red": str(data["cols"][0]),
+ "green": str(data["cols"][1]),
+ "blue": str(data["cols"][2]),
})
- xSub.text = self._theLabels[n]
+ xSub.text = data["name"]
+
return True
def unpackXML(self, xParent):
"""Unpack an XML tree and set the class values.
"""
- theLabels = []
- theColours = []
+ self._store = {}
+ self._reverse = {}
+ self._default = None
for xChild in xParent:
- theLabels.append(xChild.text)
- cR = checkInt(xChild.attrib.get("red", 0), 0, False)
- cG = checkInt(xChild.attrib.get("green", 0), 0, False)
- cB = checkInt(xChild.attrib.get("blue", 0), 0, False)
- theColours.append((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])
+ 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))
return True
@@ -165,39 +190,49 @@ class NWStatus():
# Internal Functions
##
- def _getIndex(self, theLabel):
- """Look up a status entry in the object lists, and return it if
- it exists.
+ def _newKey(self):
+ """Generate a new key for a status flag. This method is
+ 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:
- return None
- return self._theMap.get(theLabel.strip(), None)
+ key = f"{self._type}{random.randint(0, 0xffffff):06x}"
+ if key in self._store:
+ 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
##
- def __getitem__(self, n):
- """Return an entry by its index.
- """
- 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 __getitem__(self, key):
+ return self._store[key]
def __iter__(self):
- """Initialise the iterator.
- """
- self._theIndex = 0
- return self
+ return iter(self._store)
- def __next__(self):
- """Return the next entry for the iterator.
- """
- if self._theIndex < self._theLength:
- theLabel, theColour, theCount, theIcon = self.__getitem__(self._theIndex)
- self._theIndex += 1
- return theLabel, theColour, theCount, theIcon
- else:
- raise StopIteration
+ def keys(self):
+ return self._store.keys()
+
+ def items(self):
+ return self._store.items()
+
+ def values(self):
+ return self._store.values()
# END Class NWStatus
From ddd8b6081647d3ca29776b29710127cbede18ef0 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 5 Apr 2022 23:13:39 +0200
Subject: [PATCH 04/17] Complete the needed GUI class changes for the new
NWStatus class
---
novelwriter/common.py | 2 +-
novelwriter/core/project.py | 45 ++++++++---
novelwriter/core/status.py | 44 +++++++----
novelwriter/dialogs/projsettings.py | 118 +++++++++++++++-------------
novelwriter/gui/itemdetails.py | 5 ++
novelwriter/guimain.py | 4 +-
6 files changed, 135 insertions(+), 83 deletions(-)
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
From ac904ee9fc36935578ed3ec1f58235d5e04061e8 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 5 Apr 2022 23:15:18 +0200
Subject: [PATCH 05/17] Update sample project file
---
sample/nwProject.nwx | 76 ++++++++++++++++++++++----------------------
1 file changed, 38 insertions(+), 38 deletions(-)
diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx
index 68265de5..002cea43 100644
--- a/sample/nwProject.nwx
+++ b/sample/nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Sample Project
Sample Project
@@ -7,7 +7,7 @@
Jay Doh
1303
199
- 65005
+ 65049
False
@@ -33,121 +33,121 @@
- New
- Notes
- Started
- 1st Draft
- 2nd Draft
- 3rd Draft
- Finished
+ New
+ Notes
+ Started
+ 1st Draft
+ 2nd Draft
+ 3rd Draft
+ Finished
- None
- Minor
- Major
- Main
+ None
+ Minor
+ Major
+ Main
-
- Novel
+ Novel
-
- Title Page
+ Title Page
-
- Page
+ Page
-
- Part One
+ Part One
-
- A Folder
+ A Folder
-
- Chapter One
+ Chapter One
-
- Making a Scene
+ Making a Scene
-
- Another Scene
+ Another Scene
-
- Interlude
+ Interlude
-
- A Note on Structure
+ A Note on Structure
-
- Chapter Two
+ Chapter Two
-
- We Found John!
+ We Found John!
-
- Characters
+ Characters
-
- Main Characters
+ Main Characters
-
- John Smith
+ John Smith
-
- Jane Smith
+ Jane Smith
-
- Locations
+ Locations
-
- Earth
+ Earth
-
- Space
+ Space
-
- Mars
+ Mars
-
- Archive
+ Archive
-
- Scenes
+ Scenes
-
- Old File
+ Old File
-
- Trash
+ Trash
-
- Delete Me!
+ Delete Me!
From e98e942f198b957b27121371ba8d7eba0856be4f Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 16 Apr 2022 14:59:25 +0200
Subject: [PATCH 06/17] Replace key generator in the NWStatus class
---
novelwriter/core/project.py | 5 +++--
novelwriter/core/status.py | 26 ++++++++++++++++++--------
2 files changed, 21 insertions(+), 10 deletions(-)
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index d17cffc3..b00b73cf 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -218,12 +218,12 @@ class NWProject():
}
self.spellCheck = False
self.autoOutline = True
- self.statusItems = NWStatus("s")
+ self.statusItems = NWStatus(NWStatus.STATUS)
self.statusItems.write(None, self.tr("New"), (100, 100, 100))
self.statusItems.write(None, self.tr("Note"), (200, 50, 0))
self.statusItems.write(None, self.tr("Draft"), (200, 150, 0))
self.statusItems.write(None, self.tr("Finished"), (50, 200, 0))
- self.importItems = NWStatus("i")
+ self.importItems = NWStatus(NWStatus.IMPORT)
self.importItems.write(None, self.tr("New"), (100, 100, 100))
self.importItems.write(None, self.tr("Minor"), (200, 50, 0))
self.importItems.write(None, self.tr("Major"), (200, 150, 0))
@@ -267,6 +267,7 @@ class NWProject():
logger.error("No project path set for the new project")
return False
+ self.clearProject()
if not self.setProjectPath(projPath, newProject=True):
return False
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index 4aee93b2..a3792764 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -39,9 +39,12 @@ logger = logging.getLogger(__name__)
class NWStatus():
+ STATUS = 1
+ IMPORT = 2
+
def __init__(self, type):
- self._type = str(type)
+ self._type = type
self._store = {}
self._reverse = {}
self._default = None
@@ -51,6 +54,13 @@ class NWStatus():
pixmap.fill(QColor(100, 100, 100))
self._defaultIcon = QIcon(pixmap)
+ if self._type == self.STATUS:
+ self._prefix = "s"
+ elif self._type == self.IMPORT:
+ self._prefix = "i"
+ else:
+ raise Exception("This is a bug!")
+
return
def write(self, key, name, cols, count=None):
@@ -209,21 +219,21 @@ class NWStatus():
flags. The Python recursion limit is given the job to handle
the extreme case and will cause an app crash.
"""
- key = f"{self._type}{random.randint(0, 0xffffff):06x}"
+ key = f"{self._prefix}{random.getrandbits(24):06x}"
if key in self._store:
key = self._newKey()
return key
- def _isKey(self, key):
- """Check if a string is a key or not.
+ def _isKey(self, value):
+ """Check if a value is a key or not.
"""
- if not isinstance(key, str):
+ if not isinstance(value, str):
return False
- if len(key) != 7:
+ if len(value) != 7:
return False
- if key[0] != self._type:
+ if value[0] != self._prefix:
return False
- for c in key[1:]:
+ for c in value[1:]:
if c not in "0123456789abcdef":
return False
return True
From cf7e1da96aad29d52ccafc7eb6e5f19bda57a22a Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 16 Apr 2022 14:59:59 +0200
Subject: [PATCH 07/17] Fix display text issues in varuious GUI elements
---
novelwriter/dialogs/itemeditor.py | 30 ++++++++++++++++-------------
novelwriter/dialogs/projsettings.py | 4 ++--
novelwriter/gui/outlinedetails.py | 4 +++-
3 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/novelwriter/dialogs/itemeditor.py b/novelwriter/dialogs/itemeditor.py
index a39d93f4..84a0db14 100644
--- a/novelwriter/dialogs/itemeditor.py
+++ b/novelwriter/dialogs/itemeditor.py
@@ -75,11 +75,20 @@ class GuiItemEditor(QDialog):
self.editStatus = QComboBox()
self.editStatus.setMinimumWidth(mVd)
if self.theItem.itemClass in nwLists.CLS_NOVEL:
- for sLabel, _, _, sIcon in self.theProject.statusItems:
- self.editStatus.addItem(sIcon, sLabel, sLabel)
+ for key, entry in self.theProject.statusItems.items():
+ self.editStatus.addItem(entry["icon"], entry["name"], key)
+
+ index = self.editStatus.findData(self.theItem.itemStatus)
+ if index != -1:
+ self.editStatus.setCurrentIndex(index)
+
else:
- for sLabel, _, _, sIcon in self.theProject.importItems:
- self.editStatus.addItem(sIcon, sLabel, sLabel)
+ for key, entry in self.theProject.importItems.items():
+ self.editStatus.addItem(entry["icon"], entry["name"], key)
+
+ index = self.editStatus.findData(self.theItem.itemImport)
+ if index != -1:
+ self.editStatus.setCurrentIndex(index)
# Item Layout
self.editLayout = QComboBox()
@@ -97,6 +106,10 @@ class GuiItemEditor(QDialog):
if itemLayout in validLayouts:
self.editLayout.addItem(trConst(nwLabels.LAYOUT_NAME[itemLayout]), itemLayout)
+ index = self.editLayout.findData(self.theItem.itemLayout)
+ if index != -1:
+ self.editLayout.setCurrentIndex(index)
+
# Export Switch
self.textExport = QLabel(self.tr("Include when building project"))
self.editExport = QSwitch()
@@ -116,15 +129,6 @@ class GuiItemEditor(QDialog):
self.editName.setText(self.theItem.itemName)
self.editName.selectAll()
- currStatus, _ = self.theItem.getImportStatus()
- statusIdx = self.editStatus.findData(currStatus)
- if statusIdx != -1:
- self.editStatus.setCurrentIndex(statusIdx)
-
- layoutIdx = self.editLayout.findData(self.theItem.itemLayout)
- if layoutIdx != -1:
- self.editLayout.setCurrentIndex(layoutIdx)
-
##
# Assemble
##
diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py
index 260f8b10..9599cc92 100644
--- a/novelwriter/dialogs/projsettings.py
+++ b/novelwriter/dialogs/projsettings.py
@@ -294,8 +294,8 @@ class GuiProjectEditStatus(QWidget):
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
self.listBox.setIndentation(0)
- for key, data in self.theStatus.items():
- self._addItem(key, data["name"], data["cols"], data["count"])
+ for key, entry in self.theStatus.items():
+ self._addItem(key, entry["name"], entry["cols"], entry["count"])
# List Controls
# =============
diff --git a/novelwriter/gui/outlinedetails.py b/novelwriter/gui/outlinedetails.py
index cc97cdf5..b45c298f 100644
--- a/novelwriter/gui/outlinedetails.py
+++ b/novelwriter/gui/outlinedetails.py
@@ -291,8 +291,10 @@ class GuiOutlineDetails(QScrollArea):
self.titleLabel.setText("%s" % self.tr("Title"))
self.titleValue.setText(novIdx["title"])
+ itemStatus, _ = nwItem.getImportStatus()
+
self.fileValue.setText(nwItem.itemName)
- self.itemValue.setText(nwItem.itemStatus)
+ self.itemValue.setText(itemStatus)
cC = checkInt(novIdx["cCount"], 0)
wC = checkInt(novIdx["wCount"], 0)
From fedb064b1f6b2ec62414aca9da7b7eda10c03805 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 16 Apr 2022 15:01:55 +0200
Subject: [PATCH 08/17] Fix other tests
---
tests/conftest.py | 19 +++-
tests/lipsum/nwProject.nwx | 64 +++++------
tests/minimal/nwProject.nwx | 38 +++----
.../coreProject_NewCustomA_nwProject.nwx | 64 +++++------
.../coreProject_NewCustomB_nwProject.nwx | 46 ++++----
.../coreProject_NewFile_nwProject.nwx | 38 +++----
.../coreProject_NewMinimal_nwProject.nwx | 34 +++---
.../coreProject_NewRoot_nwProject.nwx | 42 +++----
.../guiEditor_Main_Final_nwProject.nwx | 40 +++----
.../guiEditor_Main_Initial_nwProject.nwx | 34 +++---
.../guiProjSettings_Dialog_nwProject.nwx | 34 +++---
tests/test_base/test_base_common.py | 27 ++++-
tests/test_core/test_core_item.py | 77 ++++++-------
tests/test_core/test_core_project.py | 107 ++++++++++--------
tests/test_dialogs/test_dlg_itemeditor.py | 36 ++++--
tests/test_dialogs/test_dlg_projload.py | 3 +-
tests/test_dialogs/test_dlg_projsettings.py | 39 +++++--
tests/test_dialogs/test_dlg_wordlist.py | 1 +
tests/test_gui/test_gui_doceditor.py | 1 +
tests/test_gui/test_gui_guimain.py | 4 +-
tests/test_gui/test_gui_theme.py | 1 +
21 files changed, 412 insertions(+), 337 deletions(-)
diff --git a/tests/conftest.py b/tests/conftest.py
index 4d8d7183..8551724b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -24,6 +24,8 @@ import sys
import pytest
import shutil
+from dataclasses import dataclass
+
from mock import MockGuiMain
from tools import cleanProject
@@ -249,9 +251,24 @@ def nwOldProj(tmpDir):
##
-# Useful Fixtures
+# Data Fixtures
##
+@dataclass
+class TestConst:
+
+ statusKeys = ["sa3b179", "s1c8031", "s06671a", "sbdd640"]
+ importKeys = ["i466852", "i3eb13b", "i392456", "i23b8c1"]
+
+
+@pytest.fixture(scope="session")
+def constData():
+ """A named tuple of known contstant values. For those that depend on
+ the random number generator, they assume the seed is 42.
+ """
+ return TestConst()
+
+
@pytest.fixture(scope="session")
def ipsumText():
"""Return five paragraphs of Lorem Ipsum text.
diff --git a/tests/lipsum/nwProject.nwx b/tests/lipsum/nwProject.nwx
index 55a582ad..8ffb64fc 100644
--- a/tests/lipsum/nwProject.nwx
+++ b/tests/lipsum/nwProject.nwx
@@ -1,12 +1,12 @@
-
+
Lorem Ipsum
Lorem Ipsum
lipsum.com
- 23
+ 24
24
- 1854
+ 1856
False
@@ -31,102 +31,102 @@
- New
- Note
- Draft
- Finished
+ New
+ Note
+ Draft
+ Finished
- New
- Minor
- Major
- Main
+ New
+ Minor
+ Major
+ Main
-
- Novel
+ Novel
-
- Lorem Ipsum
+ Lorem Ipsum
-
- Front Matter
+ Front Matter
-
- Prologue
+ Prologue
-
- Act One
+ Act One
-
- Chapter One
+ Chapter One
-
- Chapter One
+ Chapter One
-
- Scene One
+ Scene One
-
- Scene Two
+ Scene Two
-
- Interlude
+ Interlude
-
- Chapter Two
+ Chapter Two
-
- Chapter Two
+ Chapter Two
-
- Scene Three
+ Scene Three
-
- Scene Four
+ Scene Four
-
- Scene Five
+ Scene Five
-
- Characters
+ Characters
-
- Mr. Nobody
+ Mr. Nobody
-
- Plot
+ Plot
-
- Main
+ Main
-
- World
+ World
-
- Ancient Europe
+ Ancient Europe
diff --git a/tests/minimal/nwProject.nwx b/tests/minimal/nwProject.nwx
index 6f45815d..9824e2bb 100644
--- a/tests/minimal/nwProject.nwx
+++ b/tests/minimal/nwProject.nwx
@@ -1,13 +1,13 @@
-
+
Test Minimal
Minimal
Jane Doe
John Doh
- 14
+ 15
2
- 135
+ 146
True
@@ -29,50 +29,50 @@
- New
- Note
- Draft
- Finished
+ New
+ Note
+ Draft
+ Finished
- New
- Minor
- Major
- Main
+ New
+ Minor
+ Major
+ Main
-
- Novel
+ Novel
-
- Title Page
+ Title Page
-
- New Chapter
+ New Chapter
-
- New Chapter
+ New Chapter
-
- New Scene
+ New Scene
-
- Plot
+ Plot
-
- Characters
+ Characters
-
- World
+ World
diff --git a/tests/reference/coreProject_NewCustomA_nwProject.nwx b/tests/reference/coreProject_NewCustomA_nwProject.nwx
index 10398c21..65d56307 100644
--- a/tests/reference/coreProject_NewCustomA_nwProject.nwx
+++ b/tests/reference/coreProject_NewCustomA_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Test Custom
Test Novel
@@ -29,110 +29,110 @@
- New
- Note
- Draft
- Finished
+ New
+ Note
+ Draft
+ Finished
- New
- Minor
- Major
- Main
+ New
+ Minor
+ Major
+ Main
-
- Novel
+ Novel
-
- Plot
+ Plot
-
- Characters
+ Characters
-
- Locations
+ Locations
-
- Timeline
+ Timeline
-
- Objects
+ Objects
-
- Entities
+ Entities
-
- Title Page
+ Title Page
-
- Chapter 1
+ Chapter 1
-
- Chapter 1
+ Chapter 1
-
- Scene 1.1
+ Scene 1.1
-
- Scene 1.2
+ Scene 1.2
-
- Scene 1.3
+ Scene 1.3
-
- Chapter 2
+ Chapter 2
-
- Chapter 2
+ Chapter 2
-
- Scene 2.1
+ Scene 2.1
-
- Scene 2.2
+ Scene 2.2
-
- Scene 2.3
+ Scene 2.3
-
- Chapter 3
+ Chapter 3
-
- Chapter 3
+ Chapter 3
-
- Scene 3.1
+ Scene 3.1
-
- Scene 3.2
+ Scene 3.2
-
- Scene 3.3
+ Scene 3.3
diff --git a/tests/reference/coreProject_NewCustomB_nwProject.nwx b/tests/reference/coreProject_NewCustomB_nwProject.nwx
index 2bb2df31..baab7a17 100644
--- a/tests/reference/coreProject_NewCustomB_nwProject.nwx
+++ b/tests/reference/coreProject_NewCustomB_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Test Custom
Test Novel
@@ -29,74 +29,74 @@
- New
- Note
- Draft
- Finished
+ New
+ Note
+ Draft
+ Finished
- New
- Minor
- Major
- Main
+ New
+ Minor
+ Major
+ Main
-
- Novel
+ Novel
-
- Plot
+ Plot
-
- Characters
+ Characters
-
- Locations
+ Locations
-
- Timeline
+ Timeline
-
- Objects
+ Objects
-
- Entities
+ Entities
-
- Title Page
+ Title Page
-
- Scene 1
+ Scene 1
-
- Scene 2
+ Scene 2
-
- Scene 3
+ Scene 3
-
- Scene 4
+ Scene 4
-
- Scene 5
+ Scene 5
-
- Scene 6
+ Scene 6
diff --git a/tests/reference/coreProject_NewFile_nwProject.nwx b/tests/reference/coreProject_NewFile_nwProject.nwx
index d4ffb126..978f5855 100644
--- a/tests/reference/coreProject_NewFile_nwProject.nwx
+++ b/tests/reference/coreProject_NewFile_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
New Project
@@ -27,58 +27,58 @@
- New
- Note
- Draft
- Finished
+ New
+ Note
+ Draft
+ Finished
- New
- Minor
- Major
- Main
+ New
+ Minor
+ Major
+ Main
-
- Novel
+ Novel
-
- Plot
+ Plot
-
- Characters
+ Characters
-
- World
+ World
-
- Title Page
+ Title Page
-
- New Chapter
+ New Chapter
-
- New Chapter
+ New Chapter
-
- New Scene
+ New Scene
-
- Hello
+ Hello
-
- Jane
+ Jane
diff --git a/tests/reference/coreProject_NewMinimal_nwProject.nwx b/tests/reference/coreProject_NewMinimal_nwProject.nwx
index 20fa4d71..fc2ad735 100644
--- a/tests/reference/coreProject_NewMinimal_nwProject.nwx
+++ b/tests/reference/coreProject_NewMinimal_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
New Project
@@ -27,50 +27,50 @@
- New
- Note
- Draft
- Finished
+ New
+ Note
+ Draft
+ Finished
- New
- Minor
- Major
- Main
+ New
+ Minor
+ Major
+ Main
-
- Novel
+ Novel
-
- Plot
+ Plot
-
- Characters
+ Characters
-
- World
+ World
-
- Title Page
+ Title Page
-
- New Chapter
+ New Chapter
-
- New Chapter
+ New Chapter
-
- New Scene
+ New Scene
diff --git a/tests/reference/coreProject_NewRoot_nwProject.nwx b/tests/reference/coreProject_NewRoot_nwProject.nwx
index 22d51fd7..94fdbeea 100644
--- a/tests/reference/coreProject_NewRoot_nwProject.nwx
+++ b/tests/reference/coreProject_NewRoot_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
New Project
@@ -27,66 +27,66 @@
- New
- Note
- Draft
- Finished
+ New
+ Note
+ Draft
+ Finished
- New
- Minor
- Major
- Main
+ New
+ Minor
+ Major
+ Main
-
- Novel
+ Novel
-
- Plot
+ Plot
-
- Characters
+ Characters
-
- World
+ World
-
- Title Page
+ Title Page
-
- New Chapter
+ New Chapter
-
- New Chapter
+ New Chapter
-
- New Scene
+ New Scene
-
- Timeline
+ Timeline
-
- Object
+ Object
-
- Custom1
+ Custom1
-
- Custom2
+ Custom2
diff --git a/tests/reference/guiEditor_Main_Final_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx
index 685095bb..3432d06b 100644
--- a/tests/reference/guiEditor_Main_Final_nwProject.nwx
+++ b/tests/reference/guiEditor_Main_Final_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
New Project
@@ -27,62 +27,62 @@
- New
- Note
- Draft
- Finished
+ New
+ Note
+ Draft
+ Finished
- New
- Minor
- Major
- Main
+ New
+ Minor
+ Major
+ Main
-
- Novel
+ Novel
-
- Title Page
+ Title Page
-
- New Chapter
+ New Chapter
-
- New Chapter
+ New Chapter
-
- New Scene
+ New Scene
-
- Plot
+ Plot
-
- New File
+ New File
-
- Characters
+ Characters
-
- New File
+ New File
-
- World
+ World
-
- New File
+ New File
-
diff --git a/tests/reference/guiEditor_Main_Initial_nwProject.nwx b/tests/reference/guiEditor_Main_Initial_nwProject.nwx
index a42552cf..e6e80636 100644
--- a/tests/reference/guiEditor_Main_Initial_nwProject.nwx
+++ b/tests/reference/guiEditor_Main_Initial_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
New Project
@@ -27,50 +27,50 @@
- New
- Note
- Draft
- Finished
+ New
+ Note
+ Draft
+ Finished
- New
- Minor
- Major
- Main
+ New
+ Minor
+ Major
+ Main
-
- Novel
+ Novel
-
- Title Page
+ Title Page
-
- New Chapter
+ New Chapter
-
- New Chapter
+ New Chapter
-
- New Scene
+ New Scene
-
- Plot
+ Plot
-
- Characters
+ Characters
-
- World
+ World
diff --git a/tests/reference/guiProjSettings_Dialog_nwProject.nwx b/tests/reference/guiProjSettings_Dialog_nwProject.nwx
index c6652f72..f41ac5c8 100644
--- a/tests/reference/guiProjSettings_Dialog_nwProject.nwx
+++ b/tests/reference/guiProjSettings_Dialog_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Project Name
Project Title
@@ -33,50 +33,50 @@
- New
- Note
- Finished
- Final
+ New
+ Note
+ Finished
+ Final
- New
- Minor
- Major
- Final
+ New
+ Minor
+ Major
+ Final
-
- Novel
+ Novel
-
- Title Page
+ Title Page
-
- New Chapter
+ New Chapter
-
- New Chapter
+ New Chapter
-
- New Scene
+ New Scene
-
- Plot
+ Plot
-
- Characters
+ Characters
-
- World
+ World
diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py
index f5eef8fe..d7ad4119 100644
--- a/tests/test_base/test_base_common.py
+++ b/tests/test_base/test_base_common.py
@@ -31,9 +31,9 @@ from novelwriter.guimain import GuiMain
from novelwriter.common import (
checkString, checkInt, checkFloat, checkBool, checkHandle, isHandle,
isTitleTag, isItemClass, isItemType, isItemLayout, hexToInt, checkIntRange,
- checkIntTuple, formatInt, formatTimeStamp, formatTime, splitVersionNumber,
- transferCase, fuzzyTime, numberToRoman, jsonEncode, readTextFile,
- makeFileNameSafe, sha256sum, getGuiItem, NWConfigParser
+ minmax, checkIntTuple, formatInt, formatTimeStamp, formatTime, simplified,
+ splitVersionNumber, transferCase, fuzzyTime, numberToRoman, jsonEncode,
+ readTextFile, makeFileNameSafe, sha256sum, getGuiItem, NWConfigParser
)
@@ -226,6 +226,16 @@ def testBaseCommon_CheckIntRange():
# END Test testBaseCommon_CheckIntRange
+@pytest.mark.base
+def testBaseCommon_MinMax():
+ """Test the minmax function.
+ """
+ for i in range(-5, 15):
+ assert 0 <= minmax(i, 0, 10) <= 10
+
+# END Test testBaseCommon_MinMax
+
+
@pytest.mark.base
def testBaseCommon_CheckIntTuple():
"""Test the checkIntTuple function.
@@ -270,6 +280,17 @@ def testBaseCommon_FormatTime():
# END Test testBaseCommon_FormatTime
+@pytest.mark.base
+def testBaseCommon_Simplified():
+ """Test the simplified function.
+ """
+ assert simplified("Hello World") == "Hello World"
+ assert simplified(" Hello World ") == "Hello World"
+ assert simplified("\tHello\n\r\tWorld") == "Hello World"
+
+# END Test testBaseCommon_Simplified
+
+
@pytest.mark.base
def testBaseCommon_SplitVersionNumber():
"""Test the splitVersionNumber function.
diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py
index 61c67987..eb1425b3 100644
--- a/tests/test_core/test_core_item.py
+++ b/tests/test_core/test_core_item.py
@@ -20,6 +20,7 @@ along with this program. If not, see .
"""
import pytest
+import random
from lxml import etree
@@ -31,9 +32,10 @@ from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
@pytest.mark.core
-def testCoreItem_Setters(mockGUI):
+def testCoreItem_Setters(mockGUI, constData):
"""Test all the simple setters for the NWItem class.
"""
+ random.seed(42)
theProject = NWProject(mockGUI)
theItem = NWItem(theProject)
@@ -77,50 +79,32 @@ def testCoreItem_Setters(mockGUI):
# Importance
theItem._class = nwItemClass.CHARACTER
- theItem.setImport("Nonsense")
- assert theItem.itemImport == "New"
- theItem.setImport("New")
- assert theItem.itemImport == "New"
- theItem.setImport("Minor")
- assert theItem.itemImport == "Minor"
- theItem.setImport("Major")
- assert theItem.itemImport == "Major"
- theItem.setImport("Main")
- assert theItem.itemImport == "Main"
+ theItem.setImport("Word")
+ assert theItem.itemImport == constData.importKeys[0] # Default
+ for key in constData.importKeys:
+ theItem.setImport(key)
+ assert theItem.itemImport == key
# Status
theItem._class = nwItemClass.NOVEL
- theItem.setStatus("Nonsense")
- assert theItem.itemStatus == "New"
- theItem.setStatus("New")
- assert theItem.itemStatus == "New"
- theItem.setStatus("Note")
- assert theItem.itemStatus == "Note"
- theItem.setStatus("Draft")
- assert theItem.itemStatus == "Draft"
- theItem.setStatus("Finished")
- assert theItem.itemStatus == "Finished"
+ theItem.setStatus("Word")
+ assert theItem.itemStatus == constData.statusKeys[0] # Default
+ for key in constData.statusKeys:
+ theItem.setStatus(key)
+ assert theItem.itemStatus == key
# Status/Importance Wrapper
theItem._class = nwItemClass.CHARACTER
- theItem.setImportStatus("New")
- assert theItem.itemImport == "New"
- theItem.setImportStatus("Minor")
- assert theItem.itemImport == "Minor"
- theItem.setImportStatus("Note")
- assert theItem.itemImport == "New"
- theItem.setImportStatus("Draft")
- assert theItem.itemImport == "New"
+ for key in constData.importKeys:
+ theItem.setImport(key)
+ assert theItem.itemImport == key
+ assert theItem.itemStatus == constData.statusKeys[3] # Should not change
theItem._class = nwItemClass.NOVEL
- theItem.setImportStatus("New")
- assert theItem.itemStatus == "New"
- theItem.setImportStatus("Minor")
- assert theItem.itemStatus == "New"
- theItem.setImportStatus("Note")
- assert theItem.itemStatus == "Note"
- theItem.setImportStatus("Draft")
- assert theItem.itemStatus == "Draft"
+ for key in constData.statusKeys:
+ theItem.setStatus(key)
+ assert theItem.itemImport == constData.importKeys[3] # Should not change
+ assert theItem.itemStatus == key
# Expanded
theItem.setExpanded(8)
@@ -354,9 +338,10 @@ def testCoreItem_LayoutSetter(mockGUI):
@pytest.mark.core
-def testCoreItem_XMLPackUnpack(mockGUI, caplog):
+def testCoreItem_XMLPackUnpack(mockGUI, caplog, constData):
"""Test packing and unpacking XML objects for the NWItem class.
"""
+ random.seed(42)
theProject = NWProject(mockGUI)
nwXML = etree.Element("novelWriterXML")
@@ -370,7 +355,7 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
theItem.setName("A Name")
theItem.setClass("NOVEL")
theItem.setType("FILE")
- theItem.setStatus("Main")
+ theItem.setImport(constData.importKeys[3])
theItem.setLayout("NOTE")
theItem.setExported(False)
theItem.setParaCount(3)
@@ -385,9 +370,9 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
b''
b'- '
- b'A Name
'
+ b'A Name '
b''
- )
+ ) % bytes(constData.importKeys[3], encoding="utf8")
# Unpack
theItem = NWItem(theProject)
@@ -403,6 +388,8 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FILE
assert theItem.itemLayout == nwItemLayout.NOTE
+ assert theItem.itemStatus == constData.statusKeys[0] # Was None, should now be default
+ assert theItem.itemImport == constData.importKeys[3]
# Folder
# ======
@@ -414,7 +401,7 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
theItem.setName("A Name")
theItem.setClass("NOVEL")
theItem.setType("FOLDER")
- theItem.setStatus("Main")
+ theItem.setStatus(constData.statusKeys[1])
theItem.setLayout("NOTE")
theItem.setExpanded(True)
theItem.setExported(False)
@@ -429,10 +416,10 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
b''
b'- A Name'
+ b'class="NOVEL">A Name'
b'
'
b''
- )
+ ) % bytes(constData.statusKeys[1], encoding="utf8")
# Unpack
theItem = NWItem(theProject)
@@ -449,6 +436,8 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FOLDER
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
+ assert theItem.itemStatus == constData.statusKeys[1]
+ assert theItem.itemImport == constData.importKeys[0] # Was None, should now be default
# Errors
# ======
diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py
index 311fca8c..b98b05f4 100644
--- a/tests/test_core/test_core_project.py
+++ b/tests/test_core/test_core_project.py
@@ -19,8 +19,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import pytest
import os
+import pytest
+import random
from shutil import copyfile
from zipfile import ZipFile
@@ -44,6 +45,7 @@ def testCoreProject_NewMinimal(fncDir, outDir, refDir, mockGUI):
testFile = os.path.join(outDir, "coreProject_NewMinimal_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewMinimal_nwProject.nwx")
+ random.seed(42)
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
@@ -112,6 +114,7 @@ def testCoreProject_NewCustomA(fncDir, outDir, refDir, mockGUI):
"numScenes": 3,
"chFolders": True,
}
+ random.seed(42)
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
@@ -154,6 +157,7 @@ def testCoreProject_NewCustomB(fncDir, outDir, refDir, mockGUI):
"numScenes": 6,
"chFolders": True,
}
+ random.seed(42)
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
@@ -262,6 +266,7 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI):
testFile = os.path.join(outDir, "coreProject_NewRoot_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewRoot_nwProject.nwx")
+ random.seed(42)
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
@@ -299,6 +304,7 @@ def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI):
testFile = os.path.join(outDir, "coreProject_NewFile_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewFile_nwProject.nwx")
+ random.seed(42)
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
@@ -770,9 +776,10 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
# Spell language
theProject.projChanged = False
- assert theProject.setSpellLang(None)
assert theProject.projSpell is None
- assert theProject.setSpellLang("None")
+ assert theProject.setSpellLang(None) is False
+ assert theProject.projSpell is None
+ assert theProject.setSpellLang("None") is False # Should be interpreded as None
assert theProject.projSpell is None
assert theProject.setSpellLang("en_GB")
assert theProject.projSpell == "en_GB"
@@ -827,55 +834,55 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
assert theProject.setTreeOrder(oldOrder)
assert theProject.projTree.handles() == oldOrder
- # Change status
- theProject.projTree["a35baf2e93843"].setStatus("Finished")
- theProject.projTree["a6d311a93600a"].setStatus("Draft")
- theProject.projTree["f5ab3e30151e1"].setStatus("Note")
- theProject.projTree["8c659a11cd429"].setStatus("Finished")
- newList = [
- ("New", 1, 1, 1, "New"),
- ("Draft", 2, 2, 2, "Note"), # These are swapped
- ("Note", 3, 3, 3, "Draft"), # These are swapped
- ("Edited", 4, 4, 4, "Finished"), # Renamed
- ("Finished", 5, 5, 5, None), # New, with reused name
- ]
- assert theProject.setStatusColours(newList)
- assert theProject.statusItems._theLabels == [
- "New", "Draft", "Note", "Edited", "Finished"
- ]
- assert theProject.statusItems._theColours == [
- (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)
- ]
- assert theProject.projTree["a35baf2e93843"].itemStatus == "Edited" # Renamed
- assert theProject.projTree["a6d311a93600a"].itemStatus == "Note" # Swapped
- assert theProject.projTree["f5ab3e30151e1"].itemStatus == "Draft" # Swapped
- assert theProject.projTree["8c659a11cd429"].itemStatus == "Edited" # Renamed
+ # # Change status
+ # theProject.projTree["a35baf2e93843"].setStatus("Finished")
+ # theProject.projTree["a6d311a93600a"].setStatus("Draft")
+ # theProject.projTree["f5ab3e30151e1"].setStatus("Note")
+ # theProject.projTree["8c659a11cd429"].setStatus("Finished")
+ # newList = [
+ # ("New", 1, 1, 1, "New"),
+ # ("Draft", 2, 2, 2, "Note"), # These are swapped
+ # ("Note", 3, 3, 3, "Draft"), # These are swapped
+ # ("Edited", 4, 4, 4, "Finished"), # Renamed
+ # ("Finished", 5, 5, 5, None), # New, with reused name
+ # ]
+ # assert theProject.setStatusColours(newList, [])
+ # assert theProject.statusItems._theLabels == [
+ # "New", "Draft", "Note", "Edited", "Finished"
+ # ]
+ # assert theProject.statusItems._theColours == [
+ # (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)
+ # ]
+ # assert theProject.projTree["a35baf2e93843"].itemStatus == "Edited" # Renamed
+ # assert theProject.projTree["a6d311a93600a"].itemStatus == "Note" # Swapped
+ # assert theProject.projTree["f5ab3e30151e1"].itemStatus == "Draft" # Swapped
+ # assert theProject.projTree["8c659a11cd429"].itemStatus == "Edited" # Renamed
- # Change importance
- fHandle = theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "afb3043c7b2b3")
- theProject.projTree[fHandle].setImport("Main")
- newList = [
- ("New", 1, 1, 1, "New"),
- ("Minor", 2, 2, 2, "Minor"),
- ("Major", 3, 3, 3, "Major"),
- ("Min", 4, 4, 4, "Main"),
- ("Max", 5, 5, 5, None),
- ]
- assert theProject.setImportColours(newList)
- assert theProject.importItems._theLabels == [
- "New", "Minor", "Major", "Min", "Max"
- ]
- assert theProject.importItems._theColours == [
- (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)
- ]
- assert theProject.projTree[fHandle].itemImport == "Min"
+ # # Change importance
+ # fHandle = theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "afb3043c7b2b3")
+ # theProject.projTree[fHandle].setImport("Main")
+ # newList = [
+ # ("New", 1, 1, 1, "New"),
+ # ("Minor", 2, 2, 2, "Minor"),
+ # ("Major", 3, 3, 3, "Major"),
+ # ("Min", 4, 4, 4, "Main"),
+ # ("Max", 5, 5, 5, None),
+ # ]
+ # assert theProject.setImportColours(newList)
+ # assert theProject.importItems._theLabels == [
+ # "New", "Minor", "Major", "Min", "Max"
+ # ]
+ # assert theProject.importItems._theColours == [
+ # (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)
+ # ]
+ # assert theProject.projTree[fHandle].itemImport == "Min"
- # Check status counts
- assert theProject.statusItems._theCounts == [0, 0, 0, 0, 0]
- assert theProject.importItems._theCounts == [0, 0, 0, 0, 0]
- theProject.countStatus()
- assert theProject.statusItems._theCounts == [1, 1, 1, 2, 0]
- assert theProject.importItems._theCounts == [3, 0, 0, 1, 0]
+ # # Check status counts
+ # assert theProject.statusItems._theCounts == [0, 0, 0, 0, 0]
+ # assert theProject.importItems._theCounts == [0, 0, 0, 0, 0]
+ # theProject.countStatus()
+ # assert theProject.statusItems._theCounts == [1, 1, 1, 2, 0]
+ # assert theProject.importItems._theCounts == [3, 0, 0, 1, 0]
# Session stats
theProject.currWCount = 200
diff --git a/tests/test_dialogs/test_dlg_itemeditor.py b/tests/test_dialogs/test_dlg_itemeditor.py
index 79038345..10d72549 100644
--- a/tests/test_dialogs/test_dlg_itemeditor.py
+++ b/tests/test_dialogs/test_dlg_itemeditor.py
@@ -20,6 +20,7 @@ along with this program. If not, see .
"""
import pytest
+import random
from tools import getGuiItem
@@ -88,7 +89,7 @@ def testDlgItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncProj):
@pytest.mark.gui
-def testDlgItemEditor_Novel(qtbot, monkeypatch, nwGUI, fncProj):
+def testDlgItemEditor_Novel(qtbot, monkeypatch, nwGUI, fncProj, constData):
"""Test the item editor dialog for a novel document.
"""
# Block message box
@@ -96,9 +97,13 @@ def testDlgItemEditor_Novel(qtbot, monkeypatch, nwGUI, fncProj):
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create Project and Open Document
+ random.seed(42)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj})
- assert nwGUI.openDocument("0e17daca5f3e1")
+ assert nwGUI.theProject.statusItems.name(constData.statusKeys[0]) == "New"
+ assert nwGUI.theProject.statusItems.name(constData.statusKeys[1]) == "Note"
+
+ assert nwGUI.openDocument("0e17daca5f3e1") is True
# Check that an invalid handle is managed
itemEdit = GuiItemEditor(nwGUI, "whatever")
@@ -111,7 +116,7 @@ def testDlgItemEditor_Novel(qtbot, monkeypatch, nwGUI, fncProj):
# Check Existing Settings
assert itemEdit.editName.text() == "New Scene"
- assert itemEdit.editStatus.currentData() == "New"
+ assert itemEdit.editStatus.currentData() == constData.statusKeys[0]
assert itemEdit.editLayout.currentData() == nwItemLayout.DOCUMENT
assert itemEdit.editExport.isChecked() is True
@@ -125,7 +130,7 @@ def testDlgItemEditor_Novel(qtbot, monkeypatch, nwGUI, fncProj):
# Check New Settings
itemEdit._doSave()
assert itemEdit.theItem.itemName == "Great Scene"
- assert itemEdit.theItem.itemStatus == "Note"
+ assert itemEdit.theItem.itemStatus == constData.statusKeys[1]
assert itemEdit.theItem.itemLayout == nwItemLayout.NOTE
assert itemEdit.theItem.isExported is False
@@ -141,7 +146,7 @@ def testDlgItemEditor_Novel(qtbot, monkeypatch, nwGUI, fncProj):
@pytest.mark.gui
-def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj):
+def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj, constData):
"""Test the item editor dialog for a project note.
"""
# Block message box
@@ -149,8 +154,13 @@ def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj):
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create Project and Open Document
+ random.seed(42)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj})
+ assert nwGUI.theProject.statusItems.name(constData.statusKeys[0]) == "New"
+ assert nwGUI.theProject.statusItems.name(constData.statusKeys[1]) == "Note"
+ assert nwGUI.theProject.importItems.name(constData.importKeys[0]) == "New"
+ assert nwGUI.theProject.importItems.name(constData.importKeys[1]) == "Minor"
# Create Note
nwGUI.treeView.clearSelection()
@@ -166,7 +176,7 @@ def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj):
# Check Existing Settings
assert itemEdit.editName.text() == "New File"
- assert itemEdit.editStatus.currentData() == "New"
+ assert itemEdit.editStatus.currentData() == constData.importKeys[0]
assert itemEdit.editLayout.currentData() == nwItemLayout.NOTE
assert itemEdit.editExport.isChecked() is True
@@ -178,8 +188,8 @@ def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj):
# Check New Settings
assert itemEdit.theItem.itemName == "New Character"
- assert itemEdit.theItem.itemStatus == "New"
- assert itemEdit.theItem.itemImport == "Minor"
+ assert itemEdit.theItem.itemStatus == constData.statusKeys[0]
+ assert itemEdit.theItem.itemImport == constData.importKeys[1]
assert itemEdit.theItem.itemLayout == nwItemLayout.NOTE
assert itemEdit.theItem.isExported is False
@@ -191,7 +201,7 @@ def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj):
@pytest.mark.gui
-def testDlgItemEditor_Folder(qtbot, monkeypatch, nwGUI, fncProj):
+def testDlgItemEditor_Folder(qtbot, monkeypatch, nwGUI, fncProj, constData):
"""Test the item editor dialog for a folder.
"""
# Block message box
@@ -199,6 +209,7 @@ def testDlgItemEditor_Folder(qtbot, monkeypatch, nwGUI, fncProj):
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create Project and Open Document
+ random.seed(42)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj})
@@ -206,9 +217,12 @@ def testDlgItemEditor_Folder(qtbot, monkeypatch, nwGUI, fncProj):
itemEdit = GuiItemEditor(nwGUI, "31489056e0916")
itemEdit.show()
+ assert nwGUI.theProject.statusItems.name(constData.statusKeys[0]) == "New"
+ assert nwGUI.theProject.statusItems.name(constData.statusKeys[1]) == "Note"
+
# Check Existing Settings
assert itemEdit.editName.text() == "New Chapter"
- assert itemEdit.editStatus.currentData() == "New"
+ assert itemEdit.editStatus.currentData() == constData.statusKeys[0]
assert itemEdit.editLayout.currentData() == nwItemLayout.NO_LAYOUT
assert itemEdit.editExport.isChecked() is False
@@ -222,7 +236,7 @@ def testDlgItemEditor_Folder(qtbot, monkeypatch, nwGUI, fncProj):
# Check New Settings
itemEdit._doSave()
assert itemEdit.theItem.itemName == "Chapter One"
- assert itemEdit.theItem.itemStatus == "Note"
+ assert itemEdit.theItem.itemStatus == constData.statusKeys[1]
assert itemEdit.theItem.itemLayout == nwItemLayout.NO_LAYOUT
assert itemEdit.theItem.isExported is False
diff --git a/tests/test_dialogs/test_dlg_projload.py b/tests/test_dialogs/test_dlg_projload.py
index 195ca26b..6edb7a0d 100644
--- a/tests/test_dialogs/test_dlg_projload.py
+++ b/tests/test_dialogs/test_dlg_projload.py
@@ -42,7 +42,8 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
"""Test the load project wizard.
"""
# Block message box
- monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
assert nwGUI.openProject(nwMinimal)
assert nwGUI.closeProject()
diff --git a/tests/test_dialogs/test_dlg_projsettings.py b/tests/test_dialogs/test_dlg_projsettings.py
index ec316bea..f2aa8319 100644
--- a/tests/test_dialogs/test_dlg_projsettings.py
+++ b/tests/test_dialogs/test_dlg_projsettings.py
@@ -19,8 +19,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import pytest
import os
+import pytest
+import random
from shutil import copyfile
from tools import cmpFiles, getGuiItem
@@ -39,7 +40,9 @@ stepDelay = 20
@pytest.mark.gui
-def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDir, refDir):
+def testDlgProjSettings_Dialog(
+ qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDir, refDir, constData
+):
"""Test the full project settings dialog.
"""
projFile = os.path.join(fncProj, "nwProject.nwx")
@@ -55,6 +58,7 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDi
assert getGuiItem("GuiProjectSettings") is None
# Create new project
+ random.seed(42)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj})
nwGUI.mainConf.backupPath = fncDir
@@ -111,7 +115,7 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDi
projEdit._tabBox.setCurrentWidget(projEdit.tabStatus)
assert projEdit.tabStatus.colChanged is False
- assert projEdit.tabStatus.getNewList() is None
+ assert projEdit.tabStatus.getNewList() == ([], [])
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4
# Fake drag'n'drop should change changed status
@@ -150,12 +154,29 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDi
qtbot.wait(stepDelay)
assert projEdit.tabStatus.colChanged is True
- assert projEdit.tabStatus.getNewList() == [
- ("New", 100, 100, 100, "New"),
- ("Note", 200, 50, 0, "Note"),
- ("Finished", 50, 200, 0, "Finished"),
- ("Final", 20, 30, 40, None)
- ]
+ assert projEdit.tabStatus.getNewList() == (
+ [
+ {
+ "key": constData.statusKeys[0],
+ "name": "New",
+ "cols": (100, 100, 100)
+ }, {
+ "key": constData.statusKeys[1],
+ "name": "Note",
+ "cols": (200, 50, 0)
+ }, {
+ "key": constData.statusKeys[3],
+ "name": "Finished",
+ "cols": (50, 200, 0)
+ }, {
+ "key": None,
+ "name": "Final",
+ "cols": (20, 30, 40)
+ }
+ ], [
+ constData.statusKeys[2] # Deleted item
+ ]
+ )
# Importance Tab
# ==============
diff --git a/tests/test_dialogs/test_dlg_wordlist.py b/tests/test_dialogs/test_dlg_wordlist.py
index 3ca5a0ee..d9993d4d 100644
--- a/tests/test_dialogs/test_dlg_wordlist.py
+++ b/tests/test_dialogs/test_dlg_wordlist.py
@@ -40,6 +40,7 @@ stepDelay = 20
def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal):
"""test the word list editor.
"""
+ monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiWordList, "exec_", lambda *a: None)
diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py
index 8057ca41..eaa1d2ba 100644
--- a/tests/test_gui/test_gui_doceditor.py
+++ b/tests/test_gui/test_gui_doceditor.py
@@ -43,6 +43,7 @@ def testGuiEditor_Init(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
# Open project
assert nwGUI.openProject(nwMinimal)
diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py
index 34cfaffa..fb36b3e2 100644
--- a/tests/test_gui/test_gui_guimain.py
+++ b/tests/test_gui/test_gui_guimain.py
@@ -19,8 +19,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import pytest
import os
+import random
+import pytest
from shutil import copyfile
from tools import cmpFiles
@@ -139,6 +140,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
# Create new, save, close project
+ random.seed(42)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj})
assert nwGUI.saveProject()
diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py
index 6adac08e..da7fc414 100644
--- a/tests/test_gui/test_gui_theme.py
+++ b/tests/test_gui/test_gui_theme.py
@@ -35,6 +35,7 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
"""Test the theme and icon classes.
"""
# Block message box
+ monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
From d7a8cb1537f70929b360a1b2b06355b19001b4d1 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 16 Apr 2022 15:38:13 +0200
Subject: [PATCH 09/17] Rewrite NWStatus class tests
---
novelwriter/core/status.py | 10 +
tests/test_core/test_core_status.py | 344 +++++++++++++++++++++-------
2 files changed, 268 insertions(+), 86 deletions(-)
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index a3792764..4e9317f2 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -106,6 +106,13 @@ class NWStatus():
del self._reverse[self._store[key]["name"]]
del self._store[key]
+ keys = list(self._store.keys())
+ if key == self._default:
+ if len(keys) > 0:
+ self._default = keys[0]
+ else:
+ self._default = None
+
return True
def check(self, value):
@@ -242,6 +249,9 @@ class NWStatus():
# Iterator Bits
##
+ def __len__(self):
+ return len(self._store)
+
def __getitem__(self, key):
return self._store[key]
diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py
index a7a2d55e..611f8327 100644
--- a/tests/test_core/test_core_status.py
+++ b/tests/test_core/test_core_status.py
@@ -20,6 +20,7 @@ along with this program. If not, see .
"""
import pytest
+import random
from lxml import etree
@@ -29,103 +30,268 @@ from novelwriter.core.status import NWStatus
@pytest.mark.core
-def testCoreStatus_Entries():
- """Test all the simple setters for the NWItem class.
+def testCoreStatus_Internal(constData):
+ """Test all the internal functions of the NWStatus class.
"""
- theStatus = NWStatus()
+ random.seed(42)
+ theStatus = NWStatus(NWStatus.STATUS)
+ theImport = NWStatus(NWStatus.IMPORT)
- # Add entries
- theStatus.addEntry("New", (100, 100, 100))
- theStatus.addEntry("Minor", (200, 50, 0))
- theStatus.addEntry("Major", (200, 150, 0))
- theStatus.addEntry("Main", (50, 200, 0))
+ with pytest.raises(Exception):
+ NWStatus(999)
- assert theStatus._theLabels == ["New", "Minor", "Major", "Main"]
- assert theStatus._theColours == [(100, 100, 100), (200, 50, 0), (200, 150, 0), (50, 200, 0)]
- assert theStatus._theCounts == [0, 0, 0, 0]
- assert theStatus._theMap["New"] == 0
- assert theStatus._theMap["Minor"] == 1
- assert theStatus._theMap["Major"] == 2
- assert theStatus._theMap["Main"] == 3
- assert theStatus._theLength == 4
+ # Generate Key
+ # ============
- # Lookups
- assert theStatus._getIndex(None) is None
- assert theStatus._getIndex("stuff") is None
- assert theStatus._getIndex("Main") == 3
+ assert theStatus._newKey() == constData.statusKeys[0]
+ assert theStatus._newKey() == constData.statusKeys[1]
- # Checks
- assert theStatus.checkEntry(123) == "New"
- assert theStatus.checkEntry("Stuff") == "New"
- assert theStatus.checkEntry("New ") == "New"
- assert theStatus.checkEntry(" Main ") == "Main"
+ # Key collision, should move to key 3
+ theStatus.write(constData.statusKeys[2], "Crash", (0, 0, 0))
+ assert theStatus._newKey() == constData.statusKeys[3]
- # Icons
- assert isinstance(theStatus.getIcon("Stuff"), QIcon)
- assert isinstance(theStatus.getIcon("New"), QIcon)
+ assert theImport._newKey() == constData.importKeys[0]
+ assert theImport._newKey() == constData.importKeys[1]
- # Set new list
- newList = [
- ("New", 1, 1, 1, "New"),
- ("Minor", 2, 2, 2, "Minor"),
- ("Major", 3, 3, 3, "Major"),
- ("Min", 4, 4, 4, "Main"),
- ("Max", 5, 5, 5, None),
- ]
- assert theStatus.setNewEntries(None) == {}
- assert theStatus.setNewEntries(newList) == {"Main": "Min"}
+ # Key collision, should move to key 3
+ theImport.write(constData.importKeys[2], "Crash", (0, 0, 0))
+ assert theImport._newKey() == constData.importKeys[3]
- assert theStatus._theLabels == ["New", "Minor", "Major", "Min", "Max"]
- assert theStatus._theColours == [(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)]
- assert theStatus._theCounts == [0, 0, 0, 0, 0]
- assert theStatus._theMap["New"] == 0
- assert theStatus._theMap["Minor"] == 1
- assert theStatus._theMap["Major"] == 2
- assert theStatus._theMap["Min"] == 3
- assert theStatus._theMap["Max"] == 4
- assert theStatus._theLength == 5
+ # Check Key
+ # =========
- # Add counts
- countTo = [3, 5, 7, 9, 11]
- for i, n in enumerate(countTo):
- for _ in range(n):
- theStatus.countEntry(theStatus._theLabels[i])
- assert theStatus._theCounts == countTo
+ assert theStatus._isKey(None) is False # Not a string
+ assert theStatus._isKey("s00000") is False # Too short
+ assert theStatus._isKey("s000000") is True # Correct length
+ assert theStatus._isKey("s0000000") is False # Too long
+ assert theStatus._isKey("i000000") is False # Wrong type
+ assert theStatus._isKey("q000000") is False # Wrong type
+ assert theStatus._isKey("s12345H") is False # Not a hex value
+ assert theStatus._isKey("s12345F") is False # Not a lower case hex value
+ assert theStatus._isKey("s12345f") is True # Valid hex value
+
+ assert theImport._isKey(None) is False # Not a string
+ assert theImport._isKey("i00000") is False # Too short
+ assert theImport._isKey("i000000") is True # Correct length
+ assert theImport._isKey("i0000000") is False # Too long
+ assert theImport._isKey("s000000") is False # Wrong type
+ assert theImport._isKey("q000000") is False # Wrong type
+ assert theImport._isKey("i12345H") is False # Not a hex value
+ assert theImport._isKey("i12345F") is False # Not a lower case hex value
+ assert theImport._isKey("i12345f") is True # Valid hex value
+
+# END Test testCoreStatus_Internal
+
+
+@pytest.mark.core
+def testCoreStatus_Iterator(constData):
+ """Test the iterator functions of the NWStatus class.
+ """
+ random.seed(42)
+ theStatus = NWStatus(NWStatus.STATUS)
+ theStatus.write(None, "New", (100, 100, 100))
+ theStatus.write(None, "Note", (200, 50, 0))
+ theStatus.write(None, "Draft", (200, 150, 0))
+ theStatus.write(None, "Finished", (50, 200, 0))
+
+ # Direct access
+ entry = theStatus[constData.statusKeys[0]]
+ assert entry["cols"] == (100, 100, 100)
+ assert entry["name"] == "New"
+ assert entry["count"] == 0
+ assert isinstance(entry["icon"], QIcon)
# Iterate
- for i, (sA, sB, sC, sD) in enumerate(theStatus):
- assert sA == theStatus._theLabels[i]
- assert sB == theStatus._theColours[i]
- assert sC == theStatus._theCounts[i]
- assert sD == theStatus._theIcons[i]
+ entries = list(theStatus)
+ assert len(entries) == 4
+ assert len(theStatus) == 4
- sA, sB, sC, sD = theStatus[9]
- assert sA is None
- assert sB is None
- assert sC is None
- assert isinstance(sD, QIcon)
+ # Keys
+ assert list(theStatus.keys()) == constData.statusKeys
+
+ # Items
+ for index, (key, entry) in enumerate(theStatus.items()):
+ assert key == constData.statusKeys[index]
+ assert "cols" in entry
+ assert "name" in entry
+ assert "count" in entry
+ assert "icon" in entry
+
+ # Valuse
+ for entry in theStatus.values():
+ assert "cols" in entry
+ assert "name" in entry
+ assert "count" in entry
+ assert "icon" in entry
+
+# END Test testCoreStatus_Iterator
+
+
+@pytest.mark.core
+def testCoreStatus_Entries(constData):
+ """Test all the simple setters for the NWStatus class.
+ """
+ random.seed(42)
+ theStatus = NWStatus(NWStatus.STATUS)
+
+ # Write
+ # =====
+
+ # Have a key
+ theStatus.write(constData.statusKeys[0], "Entry 1", (200, 100, 50))
+ assert theStatus[constData.statusKeys[0]]["name"] == "Entry 1"
+ assert theStatus[constData.statusKeys[0]]["cols"] == (200, 100, 50)
+
+ # Don't have a key
+ theStatus.write(None, "Entry 2", (210, 110, 60))
+ assert theStatus[constData.statusKeys[1]]["name"] == "Entry 2"
+ assert theStatus[constData.statusKeys[1]]["cols"] == (210, 110, 60)
+
+ # Wrong colour spec
+ theStatus.write(None, "Entry 3", "what?")
+ assert theStatus[constData.statusKeys[2]]["name"] == "Entry 3"
+ assert theStatus[constData.statusKeys[2]]["cols"] == (100, 100, 100)
+
+ # Wrong colour count
+ theStatus.write(None, "Entry 4", (10, 20))
+ assert theStatus[constData.statusKeys[3]]["name"] == "Entry 4"
+ assert theStatus[constData.statusKeys[3]]["cols"] == (100, 100, 100)
+
+ # Check reverse map
+ assert theStatus._reverse == {
+ "Entry 1": constData.statusKeys[0],
+ "Entry 2": constData.statusKeys[1],
+ "Entry 3": constData.statusKeys[2],
+ "Entry 4": constData.statusKeys[3],
+ }
+
+ # Check
+ # =====
+
+ # Normal lookup
+ for key in constData.statusKeys:
+ assert theStatus.check(key) == key
+
+ # Reverse map lookup
+ assert theStatus.check("Entry 1") == constData.statusKeys[0]
+ assert theStatus.check("Entry 2") == constData.statusKeys[1]
+ assert theStatus.check("Entry 3") == constData.statusKeys[2]
+ assert theStatus.check("Entry 4") == constData.statusKeys[3]
+
+ # Non-existing name
+ assert theStatus.check("Entry 5") == constData.statusKeys[0]
+
+ # Name Access
+ # ===========
+
+ assert theStatus.name(constData.statusKeys[0]) == "Entry 1"
+ assert theStatus.name(constData.statusKeys[1]) == "Entry 2"
+ assert theStatus.name(constData.statusKeys[2]) == "Entry 3"
+ assert theStatus.name(constData.statusKeys[3]) == "Entry 4"
+ assert theStatus.name("blablabla") == "Entry 1"
+
+ # Colour Access
+ # =============
+
+ assert theStatus.cols(constData.statusKeys[0]) == (200, 100, 50)
+ assert theStatus.cols(constData.statusKeys[1]) == (210, 110, 60)
+ assert theStatus.cols(constData.statusKeys[2]) == (100, 100, 100)
+ assert theStatus.cols(constData.statusKeys[3]) == (100, 100, 100)
+ assert theStatus.cols("blablabla") == (200, 100, 50)
+
+ # Icon Access
+ # ===========
+
+ assert isinstance(theStatus.icon(constData.statusKeys[0]), QIcon)
+ assert isinstance(theStatus.icon(constData.statusKeys[1]), QIcon)
+ assert isinstance(theStatus.icon(constData.statusKeys[2]), QIcon)
+ assert isinstance(theStatus.icon(constData.statusKeys[3]), QIcon)
+ assert isinstance(theStatus.icon("blablabla"), QIcon)
+
+ # Increment and Count Access
+ # ==========================
+
+ countTo = [3, 5, 7, 9]
+ for i, n in enumerate(countTo):
+ for _ in range(n):
+ theStatus.increment(constData.statusKeys[i])
+
+ assert theStatus.count(constData.statusKeys[0]) == countTo[0]
+ assert theStatus.count(constData.statusKeys[1]) == countTo[1]
+ assert theStatus.count(constData.statusKeys[2]) == countTo[2]
+ assert theStatus.count(constData.statusKeys[3]) == countTo[3]
+ assert theStatus.count("blablabla") == countTo[0]
- # Clear counts
theStatus.resetCounts()
- assert theStatus._theCounts == [0, 0, 0, 0, 0]
+
+ assert theStatus.count(constData.statusKeys[0]) == 0
+ assert theStatus.count(constData.statusKeys[1]) == 0
+ assert theStatus.count(constData.statusKeys[2]) == 0
+ assert theStatus.count(constData.statusKeys[3]) == 0
+
+ # Default
+ # =======
+
+ default = theStatus._default
+ theStatus._default = None
+
+ assert theStatus.check("Entry 5") == ""
+ assert theStatus.name("blablabla") == ""
+ assert theStatus.cols("blablabla") == (100, 100, 100)
+ assert theStatus.count("blablabla") == 0
+ assert isinstance(theStatus.icon("blablabla"), QIcon)
+
+ theStatus._default = default
+
+ # Remove
+ # ======
+
+ # Non-existing entry
+ assert theStatus.remove("blablabla") is False
+
+ # Non-zero entry
+ theStatus.increment(constData.statusKeys[3])
+ assert theStatus.remove(constData.statusKeys[3]) is False
+
+ # Delete last entry
+ theStatus.resetCounts()
+ lastName = theStatus.name(constData.statusKeys[3])
+ assert lastName == "Entry 4"
+ assert theStatus.remove(constData.statusKeys[3]) is True
+ assert theStatus.check(constData.statusKeys[3]) == theStatus._default
+ assert theStatus.check(lastName) == theStatus._default
+
+ # Delete default entry, Entry 2 is new default
+ firstName = theStatus.name(theStatus._default)
+ assert firstName == "Entry 1"
+ assert theStatus.remove(theStatus._default) is True
+ assert theStatus.name(firstName) == "Entry 2"
+
+ # Remove remaining entries
+ assert theStatus.remove(constData.statusKeys[1]) is True
+ assert theStatus.remove(constData.statusKeys[2]) is True
+
+ assert len(theStatus) == 0
+ assert theStatus._default is None
# END Test testCoreStatus_Entries
@pytest.mark.core
-def testCoreStatus_XMLPackUnpack():
- """Test all the simple setters for the NWItem class.
+def testCoreStatus_XMLPackUnpack(constData):
+ """Test all the XML pack/unpack of the NWStatus class.
"""
- theStatus = NWStatus()
- theStatus.addEntry("New", (100, 100, 100))
- theStatus.addEntry("Minor", (200, 50, 0))
- theStatus.addEntry("Major", (200, 150, 0))
- theStatus.addEntry("Main", (50, 200, 0))
+ random.seed(42)
+ theStatus = NWStatus(NWStatus.STATUS)
+ theStatus.write(None, "New", (100, 100, 100))
+ theStatus.write(None, "Note", (200, 50, 0))
+ theStatus.write(None, "Draft", (200, 150, 0))
+ theStatus.write(None, "Finished", (50, 200, 0))
countTo = [3, 5, 7, 9]
for i, n in enumerate(countTo):
for _ in range(n):
- theStatus.countEntry(theStatus._theLabels[i])
+ theStatus.increment(constData.statusKeys[i])
nwXML = etree.Element("novelWriterXML")
@@ -134,23 +300,29 @@ def testCoreStatus_XMLPackUnpack():
theStatus.packXML(xStatus)
assert etree.tostring(xStatus, pretty_print=False, encoding="utf-8") == (
b''
- b'New'
- b'Minor'
- b'Major'
- b'Main'
+ b'New'
+ b'Note'
+ b'Draft'
+ b'Finished'
b''
)
# Unpack
- theStatus = NWStatus()
+ theStatus = NWStatus(NWStatus.STATUS)
assert theStatus.unpackXML(xStatus)
- assert theStatus._theLabels == ["New", "Minor", "Major", "Main"]
- assert theStatus._theColours == [(100, 100, 100), (200, 50, 0), (200, 150, 0), (50, 200, 0)]
- assert theStatus._theCounts == [0, 0, 0, 0]
- assert theStatus._theMap["New"] == 0
- assert theStatus._theMap["Minor"] == 1
- assert theStatus._theMap["Major"] == 2
- assert theStatus._theMap["Main"] == 3
- assert theStatus._theLength == 4
+ assert len(theStatus._store) == 4
+ assert list(theStatus._store.keys()) == constData.statusKeys
+ assert theStatus._store[constData.statusKeys[0]]["name"] == "New"
+ assert theStatus._store[constData.statusKeys[1]]["name"] == "Note"
+ assert theStatus._store[constData.statusKeys[2]]["name"] == "Draft"
+ assert theStatus._store[constData.statusKeys[3]]["name"] == "Finished"
+ assert theStatus._store[constData.statusKeys[0]]["cols"] == (100, 100, 100)
+ assert theStatus._store[constData.statusKeys[1]]["cols"] == (200, 50, 0)
+ assert theStatus._store[constData.statusKeys[2]]["cols"] == (200, 150, 0)
+ assert theStatus._store[constData.statusKeys[3]]["cols"] == (50, 200, 0)
+ assert theStatus._store[constData.statusKeys[0]]["count"] == countTo[0]
+ assert theStatus._store[constData.statusKeys[1]]["count"] == countTo[1]
+ assert theStatus._store[constData.statusKeys[2]]["count"] == countTo[2]
+ assert theStatus._store[constData.statusKeys[3]]["count"] == countTo[3]
# END Test testCoreStatus_XMLPackUnpack
From c5f92edaa845d547b7b121fd1fd017a9eee8aceb Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 16 Apr 2022 16:27:46 +0200
Subject: [PATCH 10/17] Update the NWProject class test for status and import
labels
---
tests/test_core/test_core_project.py | 197 ++++++++++++++++++---------
1 file changed, 131 insertions(+), 66 deletions(-)
diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py
index b98b05f4..d4213442 100644
--- a/tests/test_core/test_core_project.py
+++ b/tests/test_core/test_core_project.py
@@ -686,13 +686,127 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
@pytest.mark.core
-def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
+def testCoreProject_StatusImport(mockGUI, fncDir, constData):
+ """Test the status and importance flag handling.
+ """
+ theProject = NWProject(mockGUI)
+ random.seed(42)
+ theProject.projTree.setSeed(42)
+ assert theProject.newProject({"projPath": fncDir}) is True
+
+ # Change Status
+ # =============
+
+ theProject.projTree["44cb730c42048"].setStatus("Finished")
+ theProject.projTree["71ee45a3c0db9"].setStatus("Draft")
+ theProject.projTree["811786ad1ae74"].setStatus("Note")
+ theProject.projTree["25fc0e7096fc6"].setStatus("Finished")
+
+ assert theProject.projTree["44cb730c42048"].itemStatus == constData.statusKeys[3]
+ assert theProject.projTree["71ee45a3c0db9"].itemStatus == constData.statusKeys[2]
+ assert theProject.projTree["811786ad1ae74"].itemStatus == constData.statusKeys[1]
+ assert theProject.projTree["25fc0e7096fc6"].itemStatus == constData.statusKeys[3]
+
+ newList = [
+ {"key": constData.statusKeys[0], "name": "New", "cols": (1, 1, 1)},
+ {"key": constData.statusKeys[1], "name": "Draft", "cols": (2, 2, 2)}, # These are swapped
+ {"key": constData.statusKeys[2], "name": "Note", "cols": (3, 3, 3)}, # These are swapped
+ {"key": constData.statusKeys[3], "name": "Edited", "cols": (4, 4, 4)}, # Renamed
+ {"key": None, "name": "Finished", "cols": (5, 5, 5)}, # New, reused name
+ ]
+ assert theProject.setStatusColours(None, None) is False
+ assert theProject.setStatusColours([], []) is False
+ assert theProject.setStatusColours(newList, []) is True
+
+ assert theProject.statusItems.name(constData.statusKeys[0]) == "New"
+ assert theProject.statusItems.name(constData.statusKeys[1]) == "Draft"
+ assert theProject.statusItems.name(constData.statusKeys[2]) == "Note"
+ assert theProject.statusItems.name(constData.statusKeys[3]) == "Edited"
+ assert theProject.statusItems.cols(constData.statusKeys[0]) == (1, 1, 1)
+ assert theProject.statusItems.cols(constData.statusKeys[1]) == (2, 2, 2)
+ assert theProject.statusItems.cols(constData.statusKeys[2]) == (3, 3, 3)
+ assert theProject.statusItems.cols(constData.statusKeys[3]) == (4, 4, 4)
+
+ # Check the new entry
+ lastKey = theProject.statusItems.check("Finished")
+ assert lastKey == "sbc8960"
+ assert theProject.statusItems.name(lastKey) == "Finished"
+ assert theProject.statusItems.cols(lastKey) == (5, 5, 5)
+
+ # Delete last entry
+ assert theProject.setStatusColours([], [lastKey]) is True
+ assert theProject.statusItems.name(lastKey) == "New"
+
+ # Change Importance
+ # =================
+
+ fHandle = theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "73475cb40a568")
+ theProject.projTree[fHandle].setImport("Main")
+
+ assert theProject.projTree[fHandle].itemImport == constData.importKeys[3]
+ newList = [
+ {"key": constData.importKeys[0], "name": "New", "cols": (1, 1, 1)},
+ {"key": constData.importKeys[1], "name": "Minor", "cols": (2, 2, 2)},
+ {"key": constData.importKeys[2], "name": "Major", "cols": (3, 3, 3)},
+ {"key": constData.importKeys[3], "name": "Min", "cols": (4, 4, 4)},
+ {"key": None, "name": "Max", "cols": (5, 5, 5)},
+ ]
+ assert theProject.setImportColours(None, None) is False
+ assert theProject.setImportColours([], []) is False
+ assert theProject.setImportColours(newList, []) is True
+
+ assert theProject.importItems.name(constData.importKeys[0]) == "New"
+ assert theProject.importItems.name(constData.importKeys[1]) == "Minor"
+ assert theProject.importItems.name(constData.importKeys[2]) == "Major"
+ assert theProject.importItems.name(constData.importKeys[3]) == "Min"
+ assert theProject.importItems.cols(constData.importKeys[0]) == (1, 1, 1)
+ assert theProject.importItems.cols(constData.importKeys[1]) == (2, 2, 2)
+ assert theProject.importItems.cols(constData.importKeys[2]) == (3, 3, 3)
+ assert theProject.importItems.cols(constData.importKeys[3]) == (4, 4, 4)
+
+ # Check the new entry
+ lastKey = theProject.importItems.check("Max")
+ assert lastKey == "i1a3d1f"
+ assert theProject.importItems.name(lastKey) == "Max"
+ assert theProject.importItems.cols(lastKey) == (5, 5, 5)
+
+ # Delete last entry
+ assert theProject.setImportColours([], [lastKey]) is True
+ assert theProject.importItems.name(lastKey) == "New"
+
+ # Delete Status/Import
+ # ====================
+
+ theProject.statusItems.resetCounts()
+ for key in list(theProject.statusItems.keys()):
+ assert theProject.statusItems.remove(key) is True
+
+ theProject.importItems.resetCounts()
+ for key in list(theProject.importItems.keys()):
+ assert theProject.importItems.remove(key) is True
+
+ assert len(theProject.statusItems) == 0
+ assert len(theProject.importItems) == 0
+ assert theProject.saveProject() is True
+ assert theProject.closeProject() is True
+
+ # This should restore the default status/import labels
+ random.seed(42)
+ assert theProject.openProject(fncDir) is True
+ assert theProject.saveProject() is True
+ assert list(theProject.statusItems.keys()) == constData.statusKeys
+ assert list(theProject.importItems.keys()) == constData.importKeys
+
+# END Test testCoreProject_StatusImport
+
+
+@pytest.mark.core
+def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir):
"""Test other project class methods and functions.
"""
theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42)
- assert theProject.openProject(nwMinimal)
- assert theProject.projPath == nwMinimal
+ assert theProject.newProject({"projPath": fncDir}) is True
# Setting project path
assert theProject.setProjectPath(None)
@@ -703,16 +817,16 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
assert theProject.projPath == os.path.expanduser("~")
# Create a new folder and populate it
- projPath = os.path.join(nwMinimal, "mock1")
+ projPath = os.path.join(fncDir, "mock1")
assert theProject.setProjectPath(projPath, newProject=True)
# Make os.mkdir fail
monkeypatch.setattr("os.mkdir", causeOSError)
- projPath = os.path.join(nwMinimal, "mock2")
+ projPath = os.path.join(fncDir, "mock2")
assert not theProject.setProjectPath(projPath, newProject=True)
# Set back
- assert theProject.setProjectPath(nwMinimal)
+ assert theProject.setProjectPath(fncDir)
# Project Name
assert theProject.setProjectName(" A Name ")
@@ -750,9 +864,10 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
# Trash folder
# Should create on first call, and just returned on later calls
- assert theProject.projTree["73475cb40a568"] is None
- assert theProject.trashFolder() == "73475cb40a568"
- assert theProject.trashFolder() == "73475cb40a568"
+ hTrash = "1a6562590ef19"
+ assert theProject.projTree[hTrash] is None
+ assert theProject.trashFolder() == hTrash
+ assert theProject.trashFolder() == hTrash
# Project backup
assert theProject.doBackup is True
@@ -819,14 +934,14 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
# Change project tree order
oldOrder = [
- "a508bb932959c", "a35baf2e93843", "a6d311a93600a",
- "f5ab3e30151e1", "8c659a11cd429", "7695ce551d265",
- "afb3043c7b2b3", "9d5247ab588e0", "73475cb40a568",
+ "73475cb40a568", "44cb730c42048", "71ee45a3c0db9",
+ "811786ad1ae74", "25fc0e7096fc6", "31489056e0916",
+ "98010bd9270f9", "0e17daca5f3e1", "1a6562590ef19",
]
newOrder = [
- "f5ab3e30151e1", "8c659a11cd429", "7695ce551d265",
- "a508bb932959c", "a35baf2e93843", "a6d311a93600a",
- "afb3043c7b2b3", "9d5247ab588e0",
+ "811786ad1ae74", "25fc0e7096fc6", "31489056e0916",
+ "73475cb40a568", "44cb730c42048", "71ee45a3c0db9",
+ "98010bd9270f9", "0e17daca5f3e1",
]
assert theProject.projTree.handles() == oldOrder
assert theProject.setTreeOrder(newOrder)
@@ -834,56 +949,6 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
assert theProject.setTreeOrder(oldOrder)
assert theProject.projTree.handles() == oldOrder
- # # Change status
- # theProject.projTree["a35baf2e93843"].setStatus("Finished")
- # theProject.projTree["a6d311a93600a"].setStatus("Draft")
- # theProject.projTree["f5ab3e30151e1"].setStatus("Note")
- # theProject.projTree["8c659a11cd429"].setStatus("Finished")
- # newList = [
- # ("New", 1, 1, 1, "New"),
- # ("Draft", 2, 2, 2, "Note"), # These are swapped
- # ("Note", 3, 3, 3, "Draft"), # These are swapped
- # ("Edited", 4, 4, 4, "Finished"), # Renamed
- # ("Finished", 5, 5, 5, None), # New, with reused name
- # ]
- # assert theProject.setStatusColours(newList, [])
- # assert theProject.statusItems._theLabels == [
- # "New", "Draft", "Note", "Edited", "Finished"
- # ]
- # assert theProject.statusItems._theColours == [
- # (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)
- # ]
- # assert theProject.projTree["a35baf2e93843"].itemStatus == "Edited" # Renamed
- # assert theProject.projTree["a6d311a93600a"].itemStatus == "Note" # Swapped
- # assert theProject.projTree["f5ab3e30151e1"].itemStatus == "Draft" # Swapped
- # assert theProject.projTree["8c659a11cd429"].itemStatus == "Edited" # Renamed
-
- # # Change importance
- # fHandle = theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "afb3043c7b2b3")
- # theProject.projTree[fHandle].setImport("Main")
- # newList = [
- # ("New", 1, 1, 1, "New"),
- # ("Minor", 2, 2, 2, "Minor"),
- # ("Major", 3, 3, 3, "Major"),
- # ("Min", 4, 4, 4, "Main"),
- # ("Max", 5, 5, 5, None),
- # ]
- # assert theProject.setImportColours(newList)
- # assert theProject.importItems._theLabels == [
- # "New", "Minor", "Major", "Min", "Max"
- # ]
- # assert theProject.importItems._theColours == [
- # (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)
- # ]
- # assert theProject.projTree[fHandle].itemImport == "Min"
-
- # # Check status counts
- # assert theProject.statusItems._theCounts == [0, 0, 0, 0, 0]
- # assert theProject.importItems._theCounts == [0, 0, 0, 0, 0]
- # theProject.countStatus()
- # assert theProject.statusItems._theCounts == [1, 1, 1, 2, 0]
- # assert theProject.importItems._theCounts == [3, 0, 0, 1, 0]
-
# Session stats
theProject.currWCount = 200
theProject.lastWCount = 100
@@ -897,7 +962,7 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
assert not theProject._appendSessionStats(idleTime=0)
# Write entry
- assert theProject.projMeta == os.path.join(nwMinimal, "meta")
+ assert theProject.projMeta == os.path.join(fncDir, "meta")
statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS)
theProject.projOpened = 1600002000
From 048e5faa1ff6c44be7d85ad57bbd5b807c9de1e4 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 16 Apr 2022 16:44:39 +0200
Subject: [PATCH 11/17] Also simplify item labels, due to XML limitations
---
novelwriter/core/item.py | 4 ++--
tests/test_core/test_core_item.py | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py
index d56d60f2..2672a196 100644
--- a/novelwriter/core/item.py
+++ b/novelwriter/core/item.py
@@ -29,7 +29,7 @@ from lxml import etree
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
from novelwriter.common import (
- checkInt, isHandle, isItemClass, isItemLayout, isItemType
+ checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified
)
from novelwriter.constants import nwLabels, nwLists, trConst
@@ -305,7 +305,7 @@ class NWItem():
"""Set the item name.
"""
if isinstance(theName, str):
- self._name = theName.strip()
+ self._name = simplified(theName)
else:
self._name = ""
return
diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py
index eb1425b3..3adda4de 100644
--- a/tests/test_core/test_core_item.py
+++ b/tests/test_core/test_core_item.py
@@ -44,6 +44,8 @@ def testCoreItem_Setters(mockGUI, constData):
assert theItem.itemName == "A Name"
theItem.setName("\t A Name ")
assert theItem.itemName == "A Name"
+ theItem.setName("\t A\t\u2009\u202f\u2002\u2003\u2028\u2029Name ")
+ assert theItem.itemName == "A Name"
theItem.setName(123)
assert theItem.itemName == ""
From 72c5a9ee92fe866ffafa39893f28d45e7ae508ea Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 16 Apr 2022 16:47:57 +0200
Subject: [PATCH 12/17] Rename some internal variables in the NWItem class
---
novelwriter/core/item.py | 102 +++++++++++++++++++--------------------
1 file changed, 51 insertions(+), 51 deletions(-)
diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py
index 2672a196..f9c1921d 100644
--- a/novelwriter/core/item.py
+++ b/novelwriter/core/item.py
@@ -301,142 +301,142 @@ class NWItem():
# Set Item Values
##
- def setName(self, theName):
+ def setName(self, name):
"""Set the item name.
"""
- if isinstance(theName, str):
- self._name = simplified(theName)
+ if isinstance(name, str):
+ self._name = simplified(name)
else:
self._name = ""
return
- def setHandle(self, theHandle):
+ def setHandle(self, tHandle):
"""Set the item handle, and ensure it is valid.
"""
- if isHandle(theHandle):
- self._handle = theHandle
+ if isHandle(tHandle):
+ self._handle = tHandle
else:
self._handle = None
return
- def setParent(self, theParent):
+ def setParent(self, pHandle):
"""Set the parent handle, and ensure it is valid.
"""
- if theParent is None:
+ if pHandle is None:
self._parent = None
- elif isHandle(theParent):
- self._parent = theParent
+ elif isHandle(pHandle):
+ self._parent = pHandle
else:
self._parent = None
return
- def setOrder(self, theOrder):
+ def setOrder(self, order):
"""Set the item order, and ensure that it is valid. This value
is purely a meta value, and not actually used by novelWriter at
the moment.
"""
- self._order = checkInt(theOrder, 0)
+ self._order = checkInt(order, 0)
return
- def setType(self, theType):
+ def setType(self, itemType):
"""Set the item type from either a proper nwItemType, or set it
from a string representing an nwItemType.
"""
- if isinstance(theType, nwItemType):
- self._type = theType
- elif isItemType(theType):
- self._type = nwItemType[theType]
+ if isinstance(itemType, nwItemType):
+ self._type = itemType
+ elif isItemType(itemType):
+ self._type = nwItemType[itemType]
else:
- logger.error("Unrecognised item type '%s'", theType)
+ logger.error("Unrecognised item type '%s'", itemType)
self._type = nwItemType.NO_TYPE
return
- def setClass(self, theClass):
+ def setClass(self, itemClass):
"""Set the item class from either a proper nwItemClass, or set
it from a string representing an nwItemClass.
"""
- if isinstance(theClass, nwItemClass):
- self._class = theClass
- elif isItemClass(theClass):
- self._class = nwItemClass[theClass]
+ if isinstance(itemClass, nwItemClass):
+ self._class = itemClass
+ elif isItemClass(itemClass):
+ self._class = nwItemClass[itemClass]
else:
- logger.error("Unrecognised item class '%s'", theClass)
+ logger.error("Unrecognised item class '%s'", itemClass)
self._class = nwItemClass.NO_CLASS
return
- def setLayout(self, theLayout):
+ def setLayout(self, itemLayout):
"""Set the item layout from either a proper nwItemLayout, or set
it from a string representing an nwItemLayout.
"""
- if isinstance(theLayout, nwItemLayout):
- self._layout = theLayout
- elif isItemLayout(theLayout):
- self._layout = nwItemLayout[theLayout]
- elif theLayout in nwLists.DEP_LAYOUT:
+ if isinstance(itemLayout, nwItemLayout):
+ self._layout = itemLayout
+ elif isItemLayout(itemLayout):
+ self._layout = nwItemLayout[itemLayout]
+ elif itemLayout in nwLists.DEP_LAYOUT:
self._layout = nwItemLayout.DOCUMENT
else:
- logger.error("Unrecognised item layout '%s'", theLayout)
+ logger.error("Unrecognised item layout '%s'", itemLayout)
self._layout = nwItemLayout.NO_LAYOUT
return
- def setStatus(self, theStatus):
+ def setStatus(self, itemStatus):
"""Set the item status by looking it up in the valid status
items of the current project.
"""
- self._status = self.theProject.statusItems.check(theStatus)
+ self._status = self.theProject.statusItems.check(itemStatus)
return
- def setImport(self, theImport):
+ def setImport(self, itemImport):
"""Set the item importance by looking it up in the valid import
items of the current project.
"""
- self._import = self.theProject.importItems.check(theImport)
+ self._import = self.theProject.importItems.check(itemImport)
return
- def setExpanded(self, expState):
+ def setExpanded(self, state):
"""Set the expanded status of an item in the project tree.
"""
- if isinstance(expState, str):
- self._expanded = (expState == str(True))
+ if isinstance(state, str):
+ self._expanded = (state == str(True))
else:
- self._expanded = (expState is True)
+ self._expanded = (state is True)
return
- def setExported(self, expState):
+ def setExported(self, state):
"""Set the export flag.
"""
- if isinstance(expState, str):
- self._exported = (expState == str(True))
+ if isinstance(state, str):
+ self._exported = (state == str(True))
else:
- self._exported = (expState is True)
+ self._exported = (state is True)
return
##
# Set Document Meta Data
##
- def setCharCount(self, theCount):
+ def setCharCount(self, count):
"""Set the character count, and ensure that it is an integer.
"""
- self._charCount = max(0, checkInt(theCount, 0))
+ self._charCount = max(0, checkInt(count, 0))
return
- def setWordCount(self, theCount):
+ def setWordCount(self, count):
"""Set the word count, and ensure that it is an integer.
"""
- self._wordCount = max(0, checkInt(theCount, 0))
+ self._wordCount = max(0, checkInt(count, 0))
return
- def setParaCount(self, theCount):
+ def setParaCount(self, count):
"""Set the paragraph count, and ensure that it is an integer.
"""
- self._paraCount = max(0, checkInt(theCount, 0))
+ self._paraCount = max(0, checkInt(count, 0))
return
- def setCursorPos(self, thePosition):
+ def setCursorPos(self, position):
"""Set the cursor position, and ensure that it is an integer.
"""
- self._cursorPos = max(0, checkInt(thePosition, 0))
+ self._cursorPos = max(0, checkInt(position, 0))
return
def saveInitialCount(self):
From 2f547033cd9e7f61fa6bf67deb506610fb8a0ea6 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 16 Apr 2022 17:00:08 +0200
Subject: [PATCH 13/17] Simplify all other strings in main project class
---
novelwriter/core/project.py | 30 +++++++++++--------
.../guiProjSettings_Dialog_nwProject.nwx | 4 +--
2 files changed, 20 insertions(+), 14 deletions(-)
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index b00b73cf..a8a9cce3 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -44,7 +44,7 @@ from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.error import logException
from novelwriter.common import (
checkString, checkBool, checkInt, isHandle, formatTimeStamp,
- makeFileNameSafe, hexToInt
+ makeFileNameSafe, hexToInt, simplified
)
from novelwriter.constants import nwLists, trConst, nwFiles, nwLabels
@@ -534,14 +534,16 @@ class NWProject():
if xItem.text is None:
continue
if xItem.tag == "name":
- logger.verbose("Working Title: '%s'", xItem.text)
- self.projName = xItem.text
+ self.projName = checkString(simplified(xItem.text), "")
+ logger.verbose("Working Title: '%s'", self.projName)
elif xItem.tag == "title":
- logger.verbose("Title is '%s'", xItem.text)
- self.bookTitle = xItem.text
+ self.bookTitle = checkString(simplified(xItem.text), "")
+ logger.verbose("Title is '%s'", self.bookTitle)
elif xItem.tag == "author":
- logger.verbose("Author: '%s'", xItem.text)
- self.bookAuthors.append(xItem.text)
+ author = checkString(simplified(xItem.text), "")
+ if author:
+ self.bookAuthors.append(author)
+ logger.verbose("Author: '%s'", author)
elif xItem.tag == "saveCount":
self.saveCount = checkInt(xItem.text, 0)
elif xItem.tag == "autoCount":
@@ -956,14 +958,14 @@ class NWProject():
"""Set the project name (working title), This is the the title
used for backup files etc.
"""
- self.projName = projName.strip()
+ self.projName = simplified(projName)
self.setProjectChanged(True)
return True
def setBookTitle(self, bookTitle):
"""Set the book title, that is, the title to include in exports.
"""
- self.bookTitle = bookTitle.strip()
+ self.bookTitle = simplified(bookTitle)
self.setProjectChanged(True)
return True
@@ -975,7 +977,7 @@ class NWProject():
self.bookAuthors = []
for bookAuthor in bookAuthors.splitlines():
- bookAuthor = bookAuthor.strip()
+ bookAuthor = simplified(bookAuthor)
if bookAuthor == "":
continue
self.bookAuthors.append(bookAuthor)
@@ -1114,7 +1116,9 @@ class NWProject():
def setAutoReplace(self, autoReplace):
"""Update the auto-replace dictionary.
"""
- self.autoReplace = autoReplace
+ self.autoReplace = {}
+ for key, entry in autoReplace.items():
+ self.autoReplace[key] = simplified(entry)
self.setProjectChanged(True)
return True
@@ -1123,7 +1127,9 @@ class NWProject():
"""
for valKey, valEntry in titleFormat.items():
if valKey in self.titleFormat:
- self.titleFormat[valKey] = checkString(valEntry, self.titleFormat[valKey])
+ self.titleFormat[valKey] = checkString(
+ simplified(valEntry), self.titleFormat[valKey]
+ )
return True
def setProjectChanged(self, bValue):
diff --git a/tests/reference/guiProjSettings_Dialog_nwProject.nwx b/tests/reference/guiProjSettings_Dialog_nwProject.nwx
index f41ac5c8..e894d5bf 100644
--- a/tests/reference/guiProjSettings_Dialog_nwProject.nwx
+++ b/tests/reference/guiProjSettings_Dialog_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Project Name
Project Title
@@ -23,7 +23,7 @@
B
D
- With This Stuff
+ With This Stuff
%title%
From 17c56440f22881fa3beaca80c60db7ea39ec6159 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 16 Apr 2022 17:28:28 +0200
Subject: [PATCH 14/17] Automatic indexing of un-indexed files (#1039)
* Downgrade rebuild index dialog from warning to info
* Don't flag index as broken if file is missing, just add it again
* Fix log warning
* Drop the return value in the index checker
---
novelwriter/core/index.py | 18 ++++++++++--------
novelwriter/guimain.py | 2 +-
2 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py
index b05fa952..39bdf0fb 100644
--- a/novelwriter/core/index.py
+++ b/novelwriter/core/index.py
@@ -681,16 +681,18 @@ class NWIndex():
logException()
self._indexBroken = True
- # Check that project files are indexed
- for fHandle in self.theProject.projFiles:
- if fHandle not in self._fileMeta:
- self._indexBroken = True
- break
-
- logger.verbose("Index check completed in %.3f ms", (time() - tStart)*1000)
-
if self._indexBroken:
self.clearIndex()
+ logger.verbose("Index check completed in %.3f ms", (time() - tStart)*1000)
+ return
+
+ # If the index was ok, we check that project files are indexed
+ for fHandle in self.theProject.projFiles:
+ if fHandle not in self._fileMeta:
+ logger.warning("Item '%s' is not in the index", fHandle)
+ self.reIndexHandle(fHandle)
+
+ logger.verbose("Index check completed in %.3f ms", (time() - tStart)*1000)
return
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 2ffffe45..c21fc091 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -545,7 +545,7 @@ class GuiMain(QMainWindow):
if self.theIndex.indexBroken:
self.makeAlert(self.tr(
"The project index is outdated or broken. Rebuilding index."
- ), nwAlert.WARN)
+ ), nwAlert.INFO)
self.rebuildIndex()
# Make sure the changed status is set to false on things opened
From fd2248de171532cc5c84233135ca1497806600ec Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 16 Apr 2022 17:44:54 +0200
Subject: [PATCH 15/17] Add up and down button icons
---
.../assets/icons/typicons_dark/icons.conf | 2 ++
.../icons/typicons_dark/typ_chevron-down.svg | 31 +++++++++++++++++++
.../icons/typicons_dark/typ_chevron-up.svg | 31 +++++++++++++++++++
.../assets/icons/typicons_light/icons.conf | 2 ++
.../icons/typicons_light/typ_chevron-down.svg | 31 +++++++++++++++++++
.../icons/typicons_light/typ_chevron-up.svg | 31 +++++++++++++++++++
novelwriter/gui/theme.py | 2 +-
7 files changed, 129 insertions(+), 1 deletion(-)
create mode 100644 novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg
create mode 100644 novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg
create mode 100644 novelwriter/assets/icons/typicons_light/typ_chevron-down.svg
create mode 100644 novelwriter/assets/icons/typicons_light/typ_chevron-up.svg
diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf
index e06aba68..bcaef71a 100644
--- a/novelwriter/assets/icons/typicons_dark/icons.conf
+++ b/novelwriter/assets/icons/typicons_dark/icons.conf
@@ -42,6 +42,7 @@ doc_h2 = mixed_heading2.svg
doc_h3 = mixed_heading3.svg
doc_h4 = mixed_heading4.svg
done = typ_input-checked.svg
+down = typ_chevron-down.svg
edit = typ_pencil.svg
forward = typ_chevron-right.svg
hash = typ_hash.svg
@@ -74,3 +75,4 @@ status_stats = typ_chart-bar-grey.svg
status_time = typ_stopwatch-grey.svg
sticky-off = typ_pin-outline.svg
sticky-on = typ_pin.svg
+up = typ_chevron-up.svg
diff --git a/novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg b/novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg
new file mode 100644
index 00000000..53389084
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg b/novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg
new file mode 100644
index 00000000..9ac7e927
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf
index 56e18b2a..8639908f 100644
--- a/novelwriter/assets/icons/typicons_light/icons.conf
+++ b/novelwriter/assets/icons/typicons_light/icons.conf
@@ -42,6 +42,7 @@ doc_h2 = mixed_heading2.svg
doc_h3 = mixed_heading3.svg
doc_h4 = mixed_heading4.svg
done = typ_input-checked.svg
+down = typ_chevron-down.svg
edit = typ_pencil.svg
forward = typ_chevron-right.svg
hash = typ_hash.svg
@@ -74,3 +75,4 @@ status_stats = typ_chart-bar-grey.svg
status_time = typ_stopwatch-grey.svg
sticky-off = typ_pin-outline.svg
sticky-on = typ_pin.svg
+up = typ_chevron-up.svg
diff --git a/novelwriter/assets/icons/typicons_light/typ_chevron-down.svg b/novelwriter/assets/icons/typicons_light/typ_chevron-down.svg
new file mode 100644
index 00000000..6ba80643
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/typ_chevron-down.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/typ_chevron-up.svg b/novelwriter/assets/icons/typicons_light/typ_chevron-up.svg
new file mode 100644
index 00000000..1b9eb901
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/typ_chevron-up.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 6cdf72ef..3c180b50 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -469,7 +469,7 @@ class GuiIcons:
"delete", "close", "done", "clear", "save", "add", "remove",
"search", "search_replace", "edit", "check", "cross", "hash",
"maximise", "minimise", "refresh", "reference", "backward",
- "forward", "settings",
+ "forward", "settings", "up", "down",
# Switches
"sticky-on", "sticky-off",
From 6aa13e318bc82af5e62a942b6fcc7468210557b6 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 16 Apr 2022 18:17:01 +0200
Subject: [PATCH 16/17] Add sorting capability to status and importance labels
---
novelwriter/core/project.py | 62 ++++++++++++----------------
novelwriter/core/status.py | 22 ++++++++++
novelwriter/dialogs/projsettings.py | 64 ++++++++++++++++++++---------
3 files changed, 93 insertions(+), 55 deletions(-)
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index a8a9cce3..9623520e 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -1072,46 +1072,14 @@ class NWProject():
return True
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.
+ """Update the list of novel file status flags.
"""
- 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
+ return self._setStatusImport(newCols, delCols, self.statusItems)
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.
+ """Update the list of note file importance flags.
"""
- 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
+ return self._setStatusImport(newCols, delCols, self.importItems)
def setAutoReplace(self, autoReplace):
"""Update the auto-replace dictionary.
@@ -1251,6 +1219,28 @@ class NWProject():
# Internal Functions
##
+ def _setStatusImport(self, new, delete, target):
+ """Update the list of novel file status or importance flags, and
+ delete those that have been requested deleted.
+ """
+ if not (new or delete):
+ return False
+
+ order = []
+ for entry in new:
+ key = entry.get("key", None)
+ name = entry.get("name", "")
+ cols = entry.get("cols", (100, 100, 100))
+ if name:
+ order.append(target.write(key, name, cols))
+
+ for key in delete:
+ target.remove(key)
+
+ target.reorder(order)
+
+ return True
+
def _loadProjectLocalisation(self):
"""Load the language data for the current project language.
"""
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index 4e9317f2..9fcfbb88 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -167,6 +167,28 @@ class NWStatus():
else:
return self._defaultIcon
+ def reorder(self, order):
+ """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 True
+
+ 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):
"""Clear the counts of references to the status entries.
"""
diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py
index 9599cc92..a0987418 100644
--- a/novelwriter/dialogs/projsettings.py
+++ b/novelwriter/dialogs/projsettings.py
@@ -306,6 +306,12 @@ class GuiProjectEditStatus(QWidget):
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
self.delButton.clicked.connect(self._delItem)
+ self.upButton = QPushButton(self.theTheme.getIcon("up"), "")
+ self.upButton.clicked.connect(lambda: self._moveItem(-1))
+
+ self.dnButton = QPushButton(self.theTheme.getIcon("down"), "")
+ self.dnButton.clicked.connect(lambda: self._moveItem(1))
+
# Edit Form
# =========
@@ -315,7 +321,7 @@ class GuiProjectEditStatus(QWidget):
self.editName.setPlaceholderText(self.tr("Select item to edit"))
self.colPixmap = QPixmap(self.iPx, self.iPx)
- self.colPixmap.fill(QColor(120, 120, 120))
+ self.colPixmap.fill(QColor(100, 100, 100))
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"))
self.colButton.setIconSize(self.colPixmap.rect().size())
self.colButton.clicked.connect(self._selectColour)
@@ -329,6 +335,8 @@ class GuiProjectEditStatus(QWidget):
self.listControls = QVBoxLayout()
self.listControls.addWidget(self.addButton)
self.listControls.addWidget(self.delButton)
+ self.listControls.addWidget(self.upButton)
+ self.listControls.addWidget(self.dnButton)
self.listControls.addStretch(1)
self.editBox = QHBoxLayout()
@@ -390,7 +398,7 @@ class GuiProjectEditStatus(QWidget):
def _newItem(self):
"""Create a new status item.
"""
- newItem = self._addItem(None, self.tr("New Item"), (0, 0, 0), 0)
+ newItem = self._addItem(None, self.tr("New Item"), (100, 100, 100), 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
@@ -445,23 +453,47 @@ class GuiProjectEditStatus(QWidget):
return item
+ def _moveItem(self, step):
+ """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 False
+
+ cItem = self.listBox.takeTopLevelItem(tIndex)
+ self.listBox.insertTopLevelItem(nIndex, cItem)
+ self.listBox.clearSelection()
+
+ cItem.setSelected(True)
+ self.colChanged = True
+
+ return
+
def _selectedItem(self):
"""Extract the info of a selected item and populate the settings
boxes and button.
"""
selItem = self._getSelectedItem()
- if selItem is not None:
- cols = selItem.data(self.COL_LABEL, self.COL_ROLE)
- name = selItem.text(self.COL_LABEL)
+ if selItem is None:
+ return
- 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()
+ 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()
return
@@ -477,12 +509,6 @@ class GuiProjectEditStatus(QWidget):
return selItem[0]
return None
- def _rowsMoved(self):
- """A row has been moved, so set the changed flag.
- """
- self.colChanged = True
- return
-
def _usageString(self, nUse):
"""Generate usage string.
"""
From 3ed74f0b02b94aee34846a881ad39974fca0af70 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 16 Apr 2022 18:35:26 +0200
Subject: [PATCH 17/17] Add test coverage of status and importance reordering
---
novelwriter/core/status.py | 2 +-
novelwriter/dialogs/projsettings.py | 2 +-
.../guiProjSettings_Dialog_nwProject.nwx | 4 +-
tests/test_core/test_core_status.py | 32 +++++++++++++++
tests/test_dialogs/test_dlg_projsettings.py | 39 ++++++++++++-------
5 files changed, 62 insertions(+), 17 deletions(-)
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index 9fcfbb88..4bade5e7 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -175,7 +175,7 @@ class NWStatus():
return False
if order == list(self._store.keys()):
- return True
+ return False
store = {}
for key in order:
diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py
index a0987418..2a2bb969 100644
--- a/novelwriter/dialogs/projsettings.py
+++ b/novelwriter/dialogs/projsettings.py
@@ -464,7 +464,7 @@ class GuiProjectEditStatus(QWidget):
nChild = self.listBox.topLevelItemCount()
nIndex = tIndex + step
if nIndex < 0 or nIndex >= nChild:
- return False
+ return
cItem = self.listBox.takeTopLevelItem(tIndex)
self.listBox.insertTopLevelItem(nIndex, cItem)
diff --git a/tests/reference/guiProjSettings_Dialog_nwProject.nwx b/tests/reference/guiProjSettings_Dialog_nwProject.nwx
index e894d5bf..1b3c818a 100644
--- a/tests/reference/guiProjSettings_Dialog_nwProject.nwx
+++ b/tests/reference/guiProjSettings_Dialog_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Project Name
Project Title
@@ -42,7 +42,7 @@
New
Minor
Major
- Final
+ Final
diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py
index 611f8327..e1d224d8 100644
--- a/tests/test_core/test_core_status.py
+++ b/tests/test_core/test_core_status.py
@@ -229,6 +229,38 @@ def testCoreStatus_Entries(constData):
assert theStatus.count(constData.statusKeys[2]) == 0
assert theStatus.count(constData.statusKeys[3]) == 0
+ # Reorder
+ # =======
+
+ cOrder = list(theStatus.keys())
+ assert cOrder == constData.statusKeys
+
+ # Wrong length
+ assert theStatus.reorder([]) is False
+
+ # No change
+ assert theStatus.reorder(cOrder) is False
+
+ # Actual reaorder
+ nOrder = [
+ constData.statusKeys[0],
+ constData.statusKeys[2],
+ constData.statusKeys[1],
+ constData.statusKeys[3],
+ ]
+ assert theStatus.reorder(nOrder) is True
+ assert list(theStatus.keys()) == nOrder
+
+ # Add an unknown key
+ wOrder = nOrder.copy()
+ wOrder[3] = theStatus._newKey()
+ assert theStatus.reorder(wOrder) is False
+ assert list(theStatus.keys()) == nOrder
+
+ # Put it back
+ assert theStatus.reorder(cOrder) is True
+ assert list(theStatus.keys()) == cOrder
+
# Default
# =======
diff --git a/tests/test_dialogs/test_dlg_projsettings.py b/tests/test_dialogs/test_dlg_projsettings.py
index f2aa8319..5a047522 100644
--- a/tests/test_dialogs/test_dlg_projsettings.py
+++ b/tests/test_dialogs/test_dlg_projsettings.py
@@ -28,9 +28,7 @@ from tools import cmpFiles, getGuiItem
from PyQt5.QtGui import QColor
from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import (
- QDialog, QAction, QMessageBox, QColorDialog, QTreeWidgetItem
-)
+from PyQt5.QtWidgets import QDialog, QAction, QMessageBox, QColorDialog
from novelwriter.dialogs import GuiProjectSettings
@@ -118,16 +116,6 @@ def testDlgProjSettings_Dialog(
assert projEdit.tabStatus.getNewList() == ([], [])
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4
- # Fake drag'n'drop should change changed status
- projEdit.tabStatus._rowsMoved()
- assert projEdit.tabStatus.colChanged is True
- projEdit.tabStatus.colChanged = False
-
- projEdit.tabStatus.listBox.clearSelection()
- assert projEdit.tabStatus._getSelectedItem() is None
- projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True)
- assert isinstance(projEdit.tabStatus._getSelectedItem(), QTreeWidgetItem)
-
# Can't delete the first item (it's in use)
projEdit.tabStatus.listBox.clearSelection()
projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True)
@@ -178,6 +166,31 @@ def testDlgProjSettings_Dialog(
]
)
+ # Move items
+ projEdit.tabStatus.listBox.clearSelection()
+ projEdit.tabStatus._moveItem(1)
+ assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
+ constData.statusKeys[0], constData.statusKeys[1], constData.statusKeys[3], None
+ ]
+
+ projEdit.tabStatus.listBox.clearSelection()
+ projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True)
+ projEdit.tabStatus._moveItem(-1)
+ assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
+ constData.statusKeys[0], constData.statusKeys[1], constData.statusKeys[3], None
+ ]
+
+ projEdit.tabStatus.listBox.clearSelection()
+ projEdit.tabStatus.listBox.topLevelItem(3).setSelected(True)
+ projEdit.tabStatus._moveItem(-1)
+ assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
+ constData.statusKeys[0], constData.statusKeys[1], None, constData.statusKeys[3]
+ ]
+ projEdit.tabStatus._moveItem(1)
+ assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
+ constData.statusKeys[0], constData.statusKeys[1], constData.statusKeys[3], None
+ ]
+
# Importance Tab
# ==============