Merge branch 'main' into multi_root

This commit is contained in:
Veronica Berglyd Olsen
2022-04-16 18:49:46 +02:00
40 changed files with 1461 additions and 801 deletions
@@ -42,6 +42,7 @@ doc_h2 = mixed_heading2.svg
doc_h3 = mixed_heading3.svg doc_h3 = mixed_heading3.svg
doc_h4 = mixed_heading4.svg doc_h4 = mixed_heading4.svg
done = typ_input-checked.svg done = typ_input-checked.svg
down = typ_chevron-down.svg
edit = typ_pencil.svg edit = typ_pencil.svg
forward = typ_chevron-right.svg forward = typ_chevron-right.svg
hash = typ_hash.svg hash = typ_hash.svg
@@ -74,3 +75,4 @@ status_stats = typ_chart-bar-grey.svg
status_time = typ_stopwatch-grey.svg status_time = typ_stopwatch-grey.svg
sticky-off = typ_pin-outline.svg sticky-off = typ_pin-outline.svg
sticky-on = typ_pin.svg sticky-on = typ_pin.svg
up = typ_chevron-up.svg
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8980"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<metadata
id="metadata8986">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8984" />
<path
style="fill:#6699cc;fill-opacity:1;stroke-width:1.28574"
id="path8978"
d="m 3.7531209,6.4345258 c -1.004161,1.002875 -1.004161,2.63319 0,3.6360652 l 8.2467181,8.248004 8.246719,-8.248004 C 20.749281,9.5691528 21,8.9108558 21,8.2525578 c 0,-0.658297 -0.250719,-1.316595 -0.753442,-1.818032 -1.004161,-1.004161 -2.631904,-1.004161 -3.636065,0 L 11.999839,11.043894 7.3891859,6.4345258 c -1.004161,-1.004161 -2.631905,-1.004161 -3.636065,0 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8980"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<metadata
id="metadata8986">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8984" />
<path
style="fill:#6699cc;fill-opacity:1;stroke-width:1.28574"
id="path8978"
d="m 20.246879,17.565474 c 1.004161,-1.002875 1.004161,-2.63319 0,-3.636065 L 12.000161,5.6814052 3.7534421,13.929409 c -0.502723,0.501438 -0.753442,1.159735 -0.753442,1.818033 0,0.658297 0.250719,1.316595 0.753442,1.818032 1.004161,1.004161 2.631904,1.004161 3.636065,0 l 4.6106539,-4.609368 4.610653,4.609368 c 1.004161,1.004161 2.631905,1.004161 3.636065,0 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -42,6 +42,7 @@ doc_h2 = mixed_heading2.svg
doc_h3 = mixed_heading3.svg doc_h3 = mixed_heading3.svg
doc_h4 = mixed_heading4.svg doc_h4 = mixed_heading4.svg
done = typ_input-checked.svg done = typ_input-checked.svg
down = typ_chevron-down.svg
edit = typ_pencil.svg edit = typ_pencil.svg
forward = typ_chevron-right.svg forward = typ_chevron-right.svg
hash = typ_hash.svg hash = typ_hash.svg
@@ -74,3 +75,4 @@ status_stats = typ_chart-bar-grey.svg
status_time = typ_stopwatch-grey.svg status_time = typ_stopwatch-grey.svg
sticky-off = typ_pin-outline.svg sticky-off = typ_pin-outline.svg
sticky-on = typ_pin.svg sticky-on = typ_pin.svg
up = typ_chevron-up.svg
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8980"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<metadata
id="metadata8986">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8984" />
<path
style="fill:#4271ae;fill-opacity:1;stroke-width:1.28574"
id="path8978"
d="m 3.7531209,6.4345258 c -1.004161,1.002875 -1.004161,2.63319 0,3.6360652 l 8.2467181,8.248004 8.246719,-8.248004 C 20.749281,9.5691528 21,8.9108558 21,8.2525578 c 0,-0.658297 -0.250719,-1.316595 -0.753442,-1.818032 -1.004161,-1.004161 -2.631904,-1.004161 -3.636065,0 L 11.999839,11.043894 7.3891859,6.4345258 c -1.004161,-1.004161 -2.631905,-1.004161 -3.636065,0 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8980"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<metadata
id="metadata8986">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8984" />
<path
style="fill:#4271ae;fill-opacity:1;stroke-width:1.28574"
id="path8978"
d="m 20.246879,17.565474 c 1.004161,-1.002875 1.004161,-2.63319 0,-3.636065 L 12.000161,5.6814052 3.7534421,13.929409 c -0.502723,0.501438 -0.753442,1.159735 -0.753442,1.818033 0,0.658297 0.250719,1.316595 0.753442,1.818032 1.004161,1.004161 2.631904,1.004161 3.636065,0 l 4.6106539,-4.609368 4.610653,4.609368 c 1.004161,1.004161 2.631905,1.004161 3.636065,0 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+13
View File
@@ -184,6 +184,12 @@ def checkIntRange(value, first, last, default):
return default return default
def minmax(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): def checkIntTuple(value, valid, default):
"""Check that an int is an element of a tuple. If it isn't, return """Check that an int is an element of a tuple. If it isn't, return
the default value. the default value.
@@ -245,6 +251,13 @@ def formatTime(tS):
# String Functions # 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): def splitVersionNumber(value):
"""Split a version string on the form aa.bb.cc into major, minor """Split a version string on the form aa.bb.cc into major, minor
and patch, and computes an integer value aabbcc. and patch, and computes an integer value aabbcc.
+10 -8
View File
@@ -679,16 +679,18 @@ class NWIndex():
logException() logException()
self._indexBroken = True 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: if self._indexBroken:
self.clearIndex() 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 return
+60 -60
View File
@@ -29,7 +29,7 @@ from lxml import etree
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
from novelwriter.common import ( from novelwriter.common import (
checkInt, isHandle, isItemClass, isItemLayout, isItemType checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified
) )
from novelwriter.constants import nwLabels, nwLists, trConst from novelwriter.constants import nwLabels, nwLists, trConst
@@ -287,11 +287,11 @@ class NWItem():
the current item based on its class. the current item based on its class.
""" """
if self._class in nwLists.CLS_NOVEL: if self._class in nwLists.CLS_NOVEL:
stName = self.theProject.statusItems.checkEntry(self._status) stName = self.theProject.statusItems.name(self._status)
stIcon = self.theProject.statusItems.getIcon(stName) stIcon = self.theProject.statusItems.icon(self._status)
else: else:
stName = self.theProject.importItems.checkEntry(self._import) stName = self.theProject.importItems.name(self._import)
stIcon = self.theProject.importItems.getIcon(stName) stIcon = self.theProject.importItems.icon(self._import)
return stName, stIcon return stName, stIcon
def setImportStatus(self, theLabel): def setImportStatus(self, theLabel):
@@ -308,153 +308,153 @@ class NWItem():
# Set Item Values # Set Item Values
## ##
def setName(self, theName): def setName(self, name):
"""Set the item name. """Set the item name.
""" """
if isinstance(theName, str): if isinstance(name, str):
self._name = theName.strip() self._name = simplified(name)
else: else:
self._name = "" self._name = ""
return return
def setHandle(self, theHandle): def setHandle(self, tHandle):
"""Set the item handle, and ensure it is valid. """Set the item handle, and ensure it is valid.
""" """
if isHandle(theHandle): if isHandle(tHandle):
self._handle = theHandle self._handle = tHandle
else: else:
self._handle = None self._handle = None
return return
def setParent(self, theParent): def setParent(self, pHandle):
"""Set the parent handle, and ensure it is valid. """Set the parent handle, and ensure it is valid.
""" """
if theParent is None: if pHandle is None:
self._parent = None self._parent = None
elif isHandle(theParent): elif isHandle(pHandle):
self._parent = theParent self._parent = pHandle
else: else:
self._parent = None self._parent = None
return return
def setRoot(self, theRoot): def setRoot(self, rHandle):
"""Set the root handle, and ensure it is valid. """Set the root handle, and ensure it is valid.
""" """
if theRoot is None: if rHandle is None:
self._root = None self._root = None
elif isHandle(theRoot): elif isHandle(rHandle):
self._root = theRoot self._root = rHandle
else: else:
self._root = None self._root = None
return return
def setOrder(self, theOrder): def setOrder(self, order):
"""Set the item order, and ensure that it is valid. This value """Set the item order, and ensure that it is valid. This value
is purely a meta value, and not actually used by novelWriter at is purely a meta value, and not actually used by novelWriter at
the moment. the moment.
""" """
self._order = checkInt(theOrder, 0) self._order = checkInt(order, 0)
return return
def setType(self, theType): def setType(self, itemType):
"""Set the item type from either a proper nwItemType, or set it """Set the item type from either a proper nwItemType, or set it
from a string representing an nwItemType. from a string representing an nwItemType.
""" """
if isinstance(theType, nwItemType): if isinstance(itemType, nwItemType):
self._type = theType self._type = itemType
elif isItemType(theType): elif isItemType(itemType):
self._type = nwItemType[theType] self._type = nwItemType[itemType]
else: else:
logger.error("Unrecognised item type '%s'", theType) logger.error("Unrecognised item type '%s'", itemType)
self._type = nwItemType.NO_TYPE self._type = nwItemType.NO_TYPE
return return
def setClass(self, theClass): def setClass(self, itemClass):
"""Set the item class from either a proper nwItemClass, or set """Set the item class from either a proper nwItemClass, or set
it from a string representing an nwItemClass. it from a string representing an nwItemClass.
""" """
if isinstance(theClass, nwItemClass): if isinstance(itemClass, nwItemClass):
self._class = theClass self._class = itemClass
elif isItemClass(theClass): elif isItemClass(itemClass):
self._class = nwItemClass[theClass] self._class = nwItemClass[itemClass]
else: else:
logger.error("Unrecognised item class '%s'", theClass) logger.error("Unrecognised item class '%s'", itemClass)
self._class = nwItemClass.NO_CLASS self._class = nwItemClass.NO_CLASS
return return
def setLayout(self, theLayout): def setLayout(self, itemLayout):
"""Set the item layout from either a proper nwItemLayout, or set """Set the item layout from either a proper nwItemLayout, or set
it from a string representing an nwItemLayout. it from a string representing an nwItemLayout.
""" """
if isinstance(theLayout, nwItemLayout): if isinstance(itemLayout, nwItemLayout):
self._layout = theLayout self._layout = itemLayout
elif isItemLayout(theLayout): elif isItemLayout(itemLayout):
self._layout = nwItemLayout[theLayout] self._layout = nwItemLayout[itemLayout]
elif theLayout in nwLists.DEP_LAYOUT: elif itemLayout in nwLists.DEP_LAYOUT:
self._layout = nwItemLayout.DOCUMENT self._layout = nwItemLayout.DOCUMENT
else: else:
logger.error("Unrecognised item layout '%s'", theLayout) logger.error("Unrecognised item layout '%s'", itemLayout)
self._layout = nwItemLayout.NO_LAYOUT self._layout = nwItemLayout.NO_LAYOUT
return return
def setStatus(self, theStatus): def setStatus(self, itemStatus):
"""Set the item status by looking it up in the valid status """Set the item status by looking it up in the valid status
items of the current project. items of the current project.
""" """
self._status = self.theProject.statusItems.checkEntry(theStatus) self._status = self.theProject.statusItems.check(itemStatus)
return return
def setImport(self, theImport): def setImport(self, itemImport):
"""Set the item importance by looking it up in the valid import """Set the item importance by looking it up in the valid import
items of the current project. items of the current project.
""" """
self._import = self.theProject.importItems.checkEntry(theImport) self._import = self.theProject.importItems.check(itemImport)
return return
def setExpanded(self, expState): def setExpanded(self, state):
"""Set the expanded status of an item in the project tree. """Set the expanded status of an item in the project tree.
""" """
if isinstance(expState, str): if isinstance(state, str):
self._expanded = (expState == str(True)) self._expanded = (state == str(True))
else: else:
self._expanded = (expState is True) self._expanded = (state is True)
return return
def setExported(self, expState): def setExported(self, state):
"""Set the export flag. """Set the export flag.
""" """
if isinstance(expState, str): if isinstance(state, str):
self._exported = (expState == str(True)) self._exported = (state == str(True))
else: else:
self._exported = (expState is True) self._exported = (state is True)
return return
## ##
# Set Document Meta Data # Set Document Meta Data
## ##
def setCharCount(self, theCount): def setCharCount(self, count):
"""Set the character count, and ensure that it is an integer. """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 return
def setWordCount(self, theCount): def setWordCount(self, count):
"""Set the word count, and ensure that it is an integer. """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 return
def setParaCount(self, theCount): def setParaCount(self, count):
"""Set the paragraph count, and ensure that it is an integer. """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 return
def setCursorPos(self, thePosition): def setCursorPos(self, position):
"""Set the cursor position, and ensure that it is an integer. """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 return
def saveInitialCount(self): def saveInitialCount(self):
+63 -45
View File
@@ -44,7 +44,7 @@ from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.common import ( from novelwriter.common import (
checkString, checkBool, checkInt, isHandle, formatTimeStamp, checkString, checkBool, checkInt, isHandle, formatTimeStamp,
makeFileNameSafe, hexToInt makeFileNameSafe, hexToInt, simplified
) )
from novelwriter.constants import nwLists, trConst, nwFiles, nwLabels from novelwriter.constants import nwLists, trConst, nwFiles, nwLabels
@@ -217,16 +217,16 @@ class NWProject():
} }
self.spellCheck = False self.spellCheck = False
self.autoOutline = True self.autoOutline = True
self.statusItems = NWStatus() self.statusItems = NWStatus(NWStatus.STATUS)
self.statusItems.addEntry(self.tr("New"), (100, 100, 100)) self.statusItems.write(None, self.tr("New"), (100, 100, 100))
self.statusItems.addEntry(self.tr("Note"), (200, 50, 0)) self.statusItems.write(None, self.tr("Note"), (200, 50, 0))
self.statusItems.addEntry(self.tr("Draft"), (200, 150, 0)) self.statusItems.write(None, self.tr("Draft"), (200, 150, 0))
self.statusItems.addEntry(self.tr("Finished"), (50, 200, 0)) self.statusItems.write(None, self.tr("Finished"), (50, 200, 0))
self.importItems = NWStatus() self.importItems = NWStatus(NWStatus.IMPORT)
self.importItems.addEntry(self.tr("New"), (100, 100, 100)) self.importItems.write(None, self.tr("New"), (100, 100, 100))
self.importItems.addEntry(self.tr("Minor"), (200, 50, 0)) self.importItems.write(None, self.tr("Minor"), (200, 50, 0))
self.importItems.addEntry(self.tr("Major"), (200, 150, 0)) self.importItems.write(None, self.tr("Major"), (200, 150, 0))
self.importItems.addEntry(self.tr("Main"), (50, 200, 0)) self.importItems.write(None, self.tr("Main"), (50, 200, 0))
self.lastEdited = None self.lastEdited = None
self.lastViewed = None self.lastViewed = None
self.lastWCount = 0 self.lastWCount = 0
@@ -266,6 +266,7 @@ class NWProject():
logger.error("No project path set for the new project") logger.error("No project path set for the new project")
return False return False
self.clearProject()
if not self.setProjectPath(projPath, newProject=True): if not self.setProjectPath(projPath, newProject=True):
return False return False
@@ -532,14 +533,16 @@ class NWProject():
if xItem.text is None: if xItem.text is None:
continue continue
if xItem.tag == "name": if xItem.tag == "name":
logger.verbose("Working Title: '%s'", xItem.text) self.projName = checkString(simplified(xItem.text), "")
self.projName = xItem.text logger.verbose("Working Title: '%s'", self.projName)
elif xItem.tag == "title": elif xItem.tag == "title":
logger.verbose("Title is '%s'", xItem.text) self.bookTitle = checkString(simplified(xItem.text), "")
self.bookTitle = xItem.text logger.verbose("Title is '%s'", self.bookTitle)
elif xItem.tag == "author": elif xItem.tag == "author":
logger.verbose("Author: '%s'", xItem.text) author = checkString(simplified(xItem.text), "")
self.bookAuthors.append(xItem.text) if author:
self.bookAuthors.append(author)
logger.verbose("Author: '%s'", author)
elif xItem.tag == "saveCount": elif xItem.tag == "saveCount":
self.saveCount = checkInt(xItem.text, 0) self.saveCount = checkInt(xItem.text, 0)
elif xItem.tag == "autoCount": elif xItem.tag == "autoCount":
@@ -693,6 +696,8 @@ class NWProject():
if len(aKey) > 0: if len(aKey) > 0:
self._packProjectValue(xTitleFmt, aKey, aValue) self._packProjectValue(xTitleFmt, aKey, aValue)
# Save Status/Importance
self.countStatus()
xStatus = etree.SubElement(xSettings, "status") xStatus = etree.SubElement(xSettings, "status")
self.statusItems.packXML(xStatus) self.statusItems.packXML(xStatus)
xStatus = etree.SubElement(xSettings, "importance") xStatus = etree.SubElement(xSettings, "importance")
@@ -959,14 +964,14 @@ class NWProject():
"""Set the project name (working title), This is the the title """Set the project name (working title), This is the the title
used for backup files etc. used for backup files etc.
""" """
self.projName = projName.strip() self.projName = simplified(projName)
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
def setBookTitle(self, bookTitle): def setBookTitle(self, bookTitle):
"""Set the book title, that is, the title to include in exports. """Set the book title, that is, the title to include in exports.
""" """
self.bookTitle = bookTitle.strip() self.bookTitle = simplified(bookTitle)
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
@@ -978,7 +983,7 @@ class NWProject():
self.bookAuthors = [] self.bookAuthors = []
for bookAuthor in bookAuthors.splitlines(): for bookAuthor in bookAuthors.splitlines():
bookAuthor = bookAuthor.strip() bookAuthor = simplified(bookAuthor)
if bookAuthor == "": if bookAuthor == "":
continue continue
self.bookAuthors.append(bookAuthor) self.bookAuthors.append(bookAuthor)
@@ -1024,7 +1029,8 @@ class NWProject():
if self.projSpell != theLang: if self.projSpell != theLang:
self.projSpell = theLang self.projSpell = theLang
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
return False
def setProjectLang(self, theLang): def setProjectLang(self, theLang):
"""Set the project-specific language. """Set the project-specific language.
@@ -1071,34 +1077,22 @@ class NWProject():
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
def setStatusColours(self, newCols): def setStatusColours(self, newCols, delCols):
"""Update the list of novel file status flags. Also iterate """Update the list of novel file status flags.
through the project and replace keys that have been renamed.
""" """
replaceMap = self.statusItems.setNewEntries(newCols) return self._setStatusImport(newCols, delCols, self.statusItems)
for nwItem in self.projTree:
if nwItem.itemClass in nwLists.CLS_NOVEL:
if nwItem.itemStatus in replaceMap:
nwItem.setStatus(replaceMap[nwItem.itemStatus])
self.setProjectChanged(True)
return True
def setImportColours(self, newCols): def setImportColours(self, newCols, delCols):
"""Update the list of note file importance flags. Also iterate """Update the list of note file importance flags.
through the project and replace keys that have been renamed.
""" """
replaceMap = self.importItems.setNewEntries(newCols) return self._setStatusImport(newCols, delCols, self.importItems)
for nwItem in self.projTree:
if nwItem.itemClass not in nwLists.CLS_NOVEL:
if nwItem.itemImport in replaceMap:
nwItem.setImport(replaceMap[nwItem.itemImport])
self.setProjectChanged(True)
return True
def setAutoReplace(self, autoReplace): def setAutoReplace(self, autoReplace):
"""Update the auto-replace dictionary. """Update the auto-replace dictionary.
""" """
self.autoReplace = autoReplace self.autoReplace = {}
for key, entry in autoReplace.items():
self.autoReplace[key] = simplified(entry)
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
@@ -1107,7 +1101,9 @@ class NWProject():
""" """
for valKey, valEntry in titleFormat.items(): for valKey, valEntry in titleFormat.items():
if valKey in self.titleFormat: if valKey in self.titleFormat:
self.titleFormat[valKey] = checkString(valEntry, self.titleFormat[valKey]) self.titleFormat[valKey] = checkString(
simplified(valEntry), self.titleFormat[valKey]
)
return True return True
def setProjectChanged(self, bValue): def setProjectChanged(self, bValue):
@@ -1213,9 +1209,9 @@ class NWProject():
self.importItems.resetCounts() self.importItems.resetCounts()
for nwItem in self.projTree: for nwItem in self.projTree:
if nwItem.itemClass in nwLists.CLS_NOVEL: if nwItem.itemClass in nwLists.CLS_NOVEL:
self.statusItems.countEntry(nwItem.itemStatus) self.statusItems.increment(nwItem.itemStatus)
else: else:
self.importItems.countEntry(nwItem.itemImport) self.importItems.increment(nwItem.itemImport)
return return
def localLookup(self, theWord): def localLookup(self, theWord):
@@ -1229,6 +1225,28 @@ class NWProject():
# Internal Functions # 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): def _loadProjectLocalisation(self):
"""Load the language data for the current project language. """Load the language data for the current project language.
""" """
+198 -109
View File
@@ -4,7 +4,8 @@ novelWriter Project Item Status Class
Data class for the status/importance settings of a project item Data class for the status/importance settings of a project item
File History: 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 This file is a part of novelWriter
Copyright 20182022, Veronica Berglyd Olsen Copyright 20182022, Veronica Berglyd Olsen
@@ -23,6 +24,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import random
import logging import logging
import novelwriter import novelwriter
@@ -30,134 +32,208 @@ from lxml import etree
from PyQt5.QtGui import QIcon, QPixmap, QColor from PyQt5.QtGui import QIcon, QPixmap, QColor
from novelwriter.common import checkInt from novelwriter.common import checkInt, minmax, simplified
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class NWStatus(): class NWStatus():
def __init__(self): STATUS = 1
IMPORT = 2
def __init__(self, type):
self._type = type
self._store = {}
self._reverse = {}
self._default = None
self._theLabels = []
self._theColours = []
self._theCounts = []
self._theIcons = []
self._theMap = {}
self._theLength = 0
self._theIndex = 0
self._iconSize = novelwriter.CONFIG.pxInt(32) self._iconSize = novelwriter.CONFIG.pxInt(32)
pixmap = QPixmap(self._iconSize, self._iconSize)
pixmap.fill(QColor(100, 100, 100))
self._defaultIcon = QIcon(pixmap)
if self._type == self.STATUS:
self._prefix = "s"
elif self._type == self.IMPORT:
self._prefix = "i"
else:
raise Exception("This is a bug!")
return return
def addEntry(self, theLabel, theColours): def write(self, key, name, cols, count=None):
"""Add a status entry to the status object, but ensure it isn't """Add or update a status entry. If the key is invalid, a new
a duplicate. key is generated.
""" """
theLabel = theLabel.strip() if not self._isKey(key):
if self._getIndex(theLabel) is None: key = self._newKey()
theIcon = QPixmap(self._iconSize, self._iconSize) if not isinstance(cols, tuple):
theIcon.fill(QColor(*theColours)) cols = (100, 100, 100)
self._theIcons.append(QIcon(theIcon)) if len(cols) != 3:
self._theLabels.append(theLabel) cols = (100, 100, 100)
self._theColours.append(theColours)
self._theCounts.append(0) pixmap = QPixmap(self._iconSize, self._iconSize)
self._theMap[theLabel] = self._theLength pixmap.fill(QColor(*cols))
self._theLength += 1
name = simplified(name)
if count is None:
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 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]
keys = list(self._store.keys())
if key == self._default:
if len(keys) > 0:
self._default = keys[0]
else:
self._default = None
return True return True
def checkEntry(self, theStatus): def check(self, value):
"""Check if a status value is valid, and returns the safe """Check the key against the stored status names.
reference to be used internally.
""" """
if isinstance(theStatus, str): if self._isKey(value) and value in self._store:
if self._getIndex(theStatus) is not None: return value
return theStatus.strip() elif value in self._reverse:
return self._theLabels[0] return self._reverse[value]
elif self._default is not None:
return self._default
else:
return ""
def getIcon(self, theLabel): def name(self, key):
"""Return the icon for the given status item. """Return the name associated with a given key.
""" """
theIndex = self._getIndex(theLabel) if key in self._store:
if theIndex is not None: return self._store[key]["name"]
return self._theIcons[theIndex] elif self._default is not None:
return QIcon() return self._store[self._default]["name"]
else:
return ""
def setNewEntries(self, newList): def cols(self, key):
"""Update the list of entries after they have been modified by """Return the colours associated with a given key.
the GUI tool.
""" """
replaceMap = {} 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)
if newList is not None: def count(self, key):
self._theLabels = [] """Return the count associated with a given key.
self._theColours = [] """
self._theCounts = [] if key in self._store:
self._theIcons = [] return self._store[key]["count"]
self._theMap = {} elif self._default is not None:
self._theLength = 0 return self._store[self._default]["count"]
self._theIndex = 0 else:
return 0
for nName, nR, nG, nB, oName in newList: def icon(self, key):
self.addEntry(nName, (nR, nG, nB)) """Return the icon associated with a given key.
if nName != oName and oName is not None: """
replaceMap[oName] = nName 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
return replaceMap 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 False
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): def resetCounts(self):
"""Clear the counts of references to the status entries. """Clear the counts of references to the status entries.
""" """
self._theCounts = [0]*self._theLength for key in self._store:
self._store[key]["count"] = 0
return return
def countEntry(self, theLabel): def increment(self, key):
"""Increment the counter for a given label. This should be used """Increment the counter for a given entry.
together with resetCounts in a loop over project items.
""" """
theIndex = self._getIndex(theLabel) if key in self._store:
if theIndex is not None: self._store[key]["count"] += 1
self._theCounts[theIndex] += 1
return return
def packXML(self, xParent): def packXML(self, xParent):
"""Pack the status entries into an XML object for saving to the """Pack the status entries into an XML object for saving to the
main project file. main project file.
""" """
for n in range(self._theLength): for key, data in self._store.items():
xSub = etree.SubElement(xParent, "entry", attrib={ xSub = etree.SubElement(xParent, "entry", attrib={
"red": str(self._theColours[n][0]), "key": key,
"green": str(self._theColours[n][1]), "count": str(data["count"]),
"blue": str(self._theColours[n][2]), "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 return True
def unpackXML(self, xParent): def unpackXML(self, xParent):
"""Unpack an XML tree and set the class values. """Unpack an XML tree and set the class values.
""" """
theLabels = [] self._store = {}
theColours = [] self._reverse = {}
self._default = None
for xChild in xParent: for xChild in xParent:
theLabels.append(xChild.text) key = xChild.attrib.get("key", None)
cR = checkInt(xChild.attrib.get("red", 0), 0, False) name = xChild.text.strip()
cG = checkInt(xChild.attrib.get("green", 0), 0, False) count = max(checkInt(xChild.attrib.get("count", 0), 0), 0)
cB = checkInt(xChild.attrib.get("blue", 0), 0, False) red = minmax(checkInt(xChild.attrib.get("red", 100), 100), 0, 255)
theColours.append((cR, cG, cB)) green = minmax(checkInt(xChild.attrib.get("green", 100), 100), 0, 255)
blue = minmax(checkInt(xChild.attrib.get("blue", 100), 100), 0, 255)
if len(theLabels) > 0: self.write(key, name, (red, green, blue), count)
self._theLabels = []
self._theColours = []
self._theCounts = []
self._theIcons = []
self._theMap = {}
self._theLength = 0
self._theIndex = 0
for n in range(len(theLabels)):
self.addEntry(theLabels[n], theColours[n])
return True return True
@@ -165,39 +241,52 @@ class NWStatus():
# Internal Functions # Internal Functions
## ##
def _getIndex(self, theLabel): def _newKey(self):
"""Look up a status entry in the object lists, and return it if """Generate a new key for a status flag. This method is
it exists. recursive, but should only fail if there is an issue with the
random number generator or the user has added a lot of status
flags. The Python recursion limit is given the job to handle
the extreme case and will cause an app crash.
""" """
if theLabel is None: key = f"{self._prefix}{random.getrandbits(24):06x}"
return None if key in self._store:
return self._theMap.get(theLabel.strip(), None) key = self._newKey()
return key
def _isKey(self, value):
"""Check if a value is a key or not.
"""
if not isinstance(value, str):
return False
if len(value) != 7:
return False
if value[0] != self._prefix:
return False
for c in value[1:]:
if c not in "0123456789abcdef":
return False
return True
## ##
# Iterator Bits # Iterator Bits
## ##
def __getitem__(self, n): def __len__(self):
"""Return an entry by its index. return len(self._store)
"""
if n >= 0 and n < self._theLength: def __getitem__(self, key):
return self._theLabels[n], self._theColours[n], self._theCounts[n], self._theIcons[n] return self._store[key]
return None, None, None, QIcon()
def __iter__(self): def __iter__(self):
"""Initialise the iterator. return iter(self._store)
"""
self._theIndex = 0
return self
def __next__(self): def keys(self):
"""Return the next entry for the iterator. return self._store.keys()
"""
if self._theIndex < self._theLength: def items(self):
theLabel, theColour, theCount, theIcon = self.__getitem__(self._theIndex) return self._store.items()
self._theIndex += 1
return theLabel, theColour, theCount, theIcon def values(self):
else: return self._store.values()
raise StopIteration
# END Class NWStatus # END Class NWStatus
+17 -13
View File
@@ -75,11 +75,20 @@ class GuiItemEditor(QDialog):
self.editStatus = QComboBox() self.editStatus = QComboBox()
self.editStatus.setMinimumWidth(mVd) self.editStatus.setMinimumWidth(mVd)
if self.theItem.itemClass in nwLists.CLS_NOVEL: if self.theItem.itemClass in nwLists.CLS_NOVEL:
for sLabel, _, _, sIcon in self.theProject.statusItems: for key, entry in self.theProject.statusItems.items():
self.editStatus.addItem(sIcon, sLabel, sLabel) self.editStatus.addItem(entry["icon"], entry["name"], key)
index = self.editStatus.findData(self.theItem.itemStatus)
if index != -1:
self.editStatus.setCurrentIndex(index)
else: else:
for sLabel, _, _, sIcon in self.theProject.importItems: for key, entry in self.theProject.importItems.items():
self.editStatus.addItem(sIcon, sLabel, sLabel) self.editStatus.addItem(entry["icon"], entry["name"], key)
index = self.editStatus.findData(self.theItem.itemImport)
if index != -1:
self.editStatus.setCurrentIndex(index)
# Item Layout # Item Layout
self.editLayout = QComboBox() self.editLayout = QComboBox()
@@ -97,6 +106,10 @@ class GuiItemEditor(QDialog):
if itemLayout in validLayouts: if itemLayout in validLayouts:
self.editLayout.addItem(trConst(nwLabels.LAYOUT_NAME[itemLayout]), itemLayout) 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 # Export Switch
self.textExport = QLabel(self.tr("Include when building project")) self.textExport = QLabel(self.tr("Include when building project"))
self.editExport = QSwitch() self.editExport = QSwitch()
@@ -116,15 +129,6 @@ class GuiItemEditor(QDialog):
self.editName.setText(self.theItem.itemName) self.editName.setText(self.theItem.itemName)
self.editName.selectAll() 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 # Assemble
## ##
+101 -65
View File
@@ -35,6 +35,7 @@ from PyQt5.QtWidgets import (
) )
from novelwriter.enum import nwAlert from novelwriter.enum import nwAlert
from novelwriter.common import simplified
from novelwriter.gui.custom import QSwitch, PagedDialog, QConfigLayout from novelwriter.gui.custom import QSwitch, PagedDialog, QConfigLayout
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -81,6 +82,9 @@ class GuiProjectSettings(PagedDialog):
self.buttonBox.rejected.connect(self._doClose) self.buttonBox.rejected.connect(self._doClose)
self.addControls(self.buttonBox) self.addControls(self.buttonBox)
# Flags
self.spellChanged = False
logger.debug("GuiProjectSettings initialisation complete") logger.debug("GuiProjectSettings initialisation complete")
return return
@@ -103,16 +107,18 @@ class GuiProjectSettings(PagedDialog):
self.theProject.setProjectName(projName) self.theProject.setProjectName(projName)
self.theProject.setBookTitle(bookTitle) self.theProject.setBookTitle(bookTitle)
self.theProject.setBookAuthors(bookAuthors) self.theProject.setBookAuthors(bookAuthors)
self.theProject.setSpellLang(spellLang)
self.theProject.setProjBackup(doBackup) self.theProject.setProjBackup(doBackup)
# Remember this as updating spell dictionary can be expensive
self.spellChanged = self.theProject.setSpellLang(spellLang)
if self.tabStatus.colChanged: if self.tabStatus.colChanged:
statusCol = self.tabStatus.getNewList() newList, delList = self.tabStatus.getNewList()
self.theProject.setStatusColours(statusCol) self.theProject.setStatusColours(newList, delList)
if self.tabImport.colChanged: if self.tabImport.colChanged:
importCol = self.tabImport.getNewList() newList, delList = self.tabImport.getNewList()
self.theProject.setImportColours(importCol) self.theProject.setImportColours(newList, delList)
if self.tabStatus.colChanged or self.tabImport.colChanged: if self.tabStatus.colChanged or self.tabImport.colChanged:
self.theParent.rebuildTrees() self.theParent.rebuildTrees()
@@ -245,6 +251,10 @@ class GuiProjectEditStatus(QWidget):
COL_LABEL = 0 COL_LABEL = 0
COL_USAGE = 1 COL_USAGE = 1
KEY_ROLE = Qt.UserRole
COL_ROLE = Qt.UserRole + 1
NUM_ROLE = Qt.UserRole + 2
def __init__(self, theParent, theProject, isStatus): def __init__(self, theParent, theProject, isStatus):
QWidget.__init__(self, theParent) QWidget.__init__(self, theParent)
@@ -267,10 +277,9 @@ class GuiProjectEditStatus(QWidget):
self.optState.getInt("GuiProjectSettings", colSetting, 130) self.optState.getInt("GuiProjectSettings", colSetting, 130)
) )
self.colData = [] self.colDeleted = []
self.colCounts = []
self.colChanged = False self.colChanged = False
self.selColour = None self.selColour = QColor(100, 100, 100)
self.iPx = self.theTheme.baseIconSize self.iPx = self.theTheme.baseIconSize
@@ -285,8 +294,8 @@ class GuiProjectEditStatus(QWidget):
self.listBox.setColumnWidth(self.COL_LABEL, wCol0) self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
self.listBox.setIndentation(0) self.listBox.setIndentation(0)
for iName, iCol, nUse, _ in self.theStatus: for key, entry in self.theStatus.items():
self._addItem(iName, iCol, iName, nUse) self._addItem(key, entry["name"], entry["cols"], entry["count"])
# List Controls # List Controls
# ============= # =============
@@ -297,6 +306,12 @@ class GuiProjectEditStatus(QWidget):
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "") self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
self.delButton.clicked.connect(self._delItem) 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 # Edit Form
# ========= # =========
@@ -306,7 +321,7 @@ class GuiProjectEditStatus(QWidget):
self.editName.setPlaceholderText(self.tr("Select item to edit")) self.editName.setPlaceholderText(self.tr("Select item to edit"))
self.colPixmap = QPixmap(self.iPx, self.iPx) 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 = QPushButton(QIcon(self.colPixmap), self.tr("Colour"))
self.colButton.setIconSize(self.colPixmap.rect().size()) self.colButton.setIconSize(self.colPixmap.rect().size())
self.colButton.clicked.connect(self._selectColour) self.colButton.clicked.connect(self._selectColour)
@@ -320,6 +335,8 @@ class GuiProjectEditStatus(QWidget):
self.listControls = QVBoxLayout() self.listControls = QVBoxLayout()
self.listControls.addWidget(self.addButton) self.listControls.addWidget(self.addButton)
self.listControls.addWidget(self.delButton) self.listControls.addWidget(self.delButton)
self.listControls.addWidget(self.upButton)
self.listControls.addWidget(self.dnButton)
self.listControls.addStretch(1) self.listControls.addStretch(1)
self.editBox = QHBoxLayout() self.editBox = QHBoxLayout()
@@ -349,12 +366,15 @@ class GuiProjectEditStatus(QWidget):
if self.colChanged: if self.colChanged:
newList = [] newList = []
for n in range(self.listBox.topLevelItemCount()): for n in range(self.listBox.topLevelItemCount()):
nItem = self.listBox.topLevelItem(n) item = self.listBox.topLevelItem(n)
nIdx = nItem.data(self.COL_LABEL, Qt.UserRole) newList.append({
newList.append(self.colData[nIdx]) "key": item.data(self.COL_LABEL, self.KEY_ROLE),
return newList "name": item.text(self.COL_LABEL),
"cols": item.data(self.COL_LABEL, self.COL_ROLE),
})
return newList, self.colDeleted
return None return [], []
## ##
# User Actions # User Actions
@@ -369,16 +389,16 @@ class GuiProjectEditStatus(QWidget):
) )
if newCol.isValid(): if newCol.isValid():
self.selColour = newCol self.selColour = newCol
colPixmap = QPixmap(self.iPx, self.iPx) pixmap = QPixmap(self.iPx, self.iPx)
colPixmap.fill(newCol) pixmap.fill(newCol)
self.colButton.setIcon(QIcon(colPixmap)) self.colButton.setIcon(QIcon(pixmap))
self.colButton.setIconSize(colPixmap.rect().size()) self.colButton.setIconSize(pixmap.rect().size())
return return
def _newItem(self): def _newItem(self):
"""Create a new status item. """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"), (100, 100, 100), 0)
newItem.setBackground(self.COL_LABEL, QBrush(QColor(0, 255, 0, 70))) newItem.setBackground(self.COL_LABEL, QBrush(QColor(0, 255, 0, 70)))
newItem.setBackground(self.COL_USAGE, QBrush(QColor(0, 255, 0, 70))) newItem.setBackground(self.COL_USAGE, QBrush(QColor(0, 255, 0, 70)))
self.colChanged = True self.colChanged = True
@@ -390,14 +410,14 @@ class GuiProjectEditStatus(QWidget):
selItem = self._getSelectedItem() selItem = self._getSelectedItem()
if selItem is not None: if selItem is not None:
iRow = self.listBox.indexOfTopLevelItem(selItem) iRow = self.listBox.indexOfTopLevelItem(selItem)
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole) if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
if self.colCounts[selIdx] == 0:
self.listBox.takeTopLevelItem(iRow)
self.colChanged = True
else:
self.theParent.makeAlert(self.tr( self.theParent.makeAlert(self.tr(
"Cannot delete a status item that is in use." "Cannot delete a status item that is in use."
), nwAlert.ERROR) ), nwAlert.ERROR)
else:
self.listBox.takeTopLevelItem(iRow)
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
self.colChanged = True
return return
def _saveItem(self): def _saveItem(self):
@@ -405,53 +425,75 @@ class GuiProjectEditStatus(QWidget):
""" """
selItem = self._getSelectedItem() selItem = self._getSelectedItem()
if selItem is not None: if selItem is not None:
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole) selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
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.setIcon(self.COL_LABEL, self.colButton.icon()) 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.editName.setEnabled(False)
self.colChanged = True self.colChanged = True
return return
def _addItem(self, iName, iCol, oName, nUse): def _addItem(self, key, name, cols, count):
"""Add a status item to the list. """Add a status item to the list.
""" """
newIcon = QPixmap(self.iPx, self.iPx) pixmap = QPixmap(self.iPx, self.iPx)
newIcon.fill(QColor(*iCol)) pixmap.fill(QColor(*cols))
newItem = QTreeWidgetItem()
newItem.setText(self.COL_LABEL, iName) item = QTreeWidgetItem()
newItem.setText(self.COL_USAGE, self._usageString(nUse)) item.setText(self.COL_LABEL, name)
newItem.setIcon(self.COL_LABEL, QIcon(newIcon)) item.setIcon(self.COL_LABEL, QIcon(pixmap))
newItem.setData(self.COL_LABEL, Qt.UserRole, len(self.colData)) item.setData(self.COL_LABEL, self.KEY_ROLE, key)
self.listBox.addTopLevelItem(newItem) item.setData(self.COL_LABEL, self.COL_ROLE, cols)
self.colData.append((iName, iCol[0], iCol[1], iCol[2], oName)) item.setData(self.COL_LABEL, self.NUM_ROLE, count)
self.colCounts.append(nUse) item.setText(self.COL_USAGE, self._usageString(count))
return newItem
self.listBox.addTopLevelItem(item)
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
cItem = self.listBox.takeTopLevelItem(tIndex)
self.listBox.insertTopLevelItem(nIndex, cItem)
self.listBox.clearSelection()
cItem.setSelected(True)
self.colChanged = True
return
def _selectedItem(self): def _selectedItem(self):
"""Extract the info of a selected item and populate the settings """Extract the info of a selected item and populate the settings
boxes and button. boxes and button.
""" """
selItem = self._getSelectedItem() selItem = self._getSelectedItem()
if selItem is not None: if selItem is None:
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole) return
selVal = self.colData[selIdx]
self.selColour = QColor(selVal[1], selVal[2], selVal[3]) cols = selItem.data(self.COL_LABEL, self.COL_ROLE)
newIcon = QPixmap(self.iPx, self.iPx) name = selItem.text(self.COL_LABEL)
newIcon.fill(self.selColour)
self.editName.setText(selVal[0]) pixmap = QPixmap(self.iPx, self.iPx)
self.colButton.setIcon(QIcon(newIcon)) pixmap.fill(QColor(*cols))
self.editName.setEnabled(True) self.selColour = QColor(*cols)
self.editName.selectAll() self.editName.setText(name)
self.editName.setFocus() self.colButton.setIcon(QIcon(pixmap))
self.editName.setEnabled(True)
self.editName.selectAll()
self.editName.setFocus()
return return
@@ -467,12 +509,6 @@ class GuiProjectEditStatus(QWidget):
return selItem[0] return selItem[0]
return None return None
def _rowsMoved(self):
"""A row has been moved, so set the changed flag.
"""
self.colChanged = True
return
def _usageString(self, nUse): def _usageString(self, nUse):
"""Generate usage string. """Generate usage string.
""" """
+5
View File
@@ -214,6 +214,11 @@ class GuiItemDetails(QWidget):
return return
def refreshDetails(self):
"""Reload the content of the details panel.
"""
self.updateViewBox(self._itemHandle)
def updateViewBox(self, tHandle): def updateViewBox(self, tHandle):
"""Populate the details box from a given handle. """Populate the details box from a given handle.
""" """
+3 -1
View File
@@ -291,8 +291,10 @@ class GuiOutlineDetails(QScrollArea):
self.titleLabel.setText("<b>%s</b>" % self.tr("Title")) self.titleLabel.setText("<b>%s</b>" % self.tr("Title"))
self.titleValue.setText(novIdx["title"]) self.titleValue.setText(novIdx["title"])
itemStatus, _ = nwItem.getImportStatus()
self.fileValue.setText(nwItem.itemName) self.fileValue.setText(nwItem.itemName)
self.itemValue.setText(nwItem.itemStatus) self.itemValue.setText(itemStatus)
cC = checkInt(novIdx["cCount"], 0) cC = checkInt(novIdx["cCount"], 0)
wC = checkInt(novIdx["wCount"], 0) wC = checkInt(novIdx["wCount"], 0)
+1 -1
View File
@@ -469,7 +469,7 @@ class GuiIcons:
"delete", "close", "done", "clear", "save", "add", "remove", "delete", "close", "done", "clear", "save", "add", "remove",
"search", "search_replace", "edit", "check", "cross", "hash", "search", "search_replace", "edit", "check", "cross", "hash",
"maximise", "minimise", "refresh", "reference", "backward", "maximise", "minimise", "refresh", "reference", "backward",
"forward", "settings", "forward", "settings", "up", "down",
# Switches # Switches
"sticky-on", "sticky-off", "sticky-on", "sticky-off",
+4 -2
View File
@@ -545,7 +545,7 @@ class GuiMain(QMainWindow):
if self.theIndex.indexBroken: if self.theIndex.indexBroken:
self.makeAlert(self.tr( self.makeAlert(self.tr(
"The project index is outdated or broken. Rebuilding index." "The project index is outdated or broken. Rebuilding index."
), nwAlert.WARN) ), nwAlert.INFO)
self.rebuildIndex() self.rebuildIndex()
# Make sure the changed status is set to false on things opened # Make sure the changed status is set to false on things opened
@@ -1002,7 +1002,9 @@ class GuiMain(QMainWindow):
if dlgProj.result() == QDialog.Accepted: if dlgProj.result() == QDialog.Accepted:
logger.debug("Applying new project settings") logger.debug("Applying new project settings")
self.docEditor.setDictionaries() if dlgProj.spellChanged:
self.docEditor.setDictionaries()
self.treeMeta.refreshDetails()
self._updateWindowTitle(self.theProject.projName) self._updateWindowTitle(self.theProject.projName)
return True return True
+38 -38
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 22:32:30"> <novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-05 23:14:56">
<project> <project>
<name>Sample Project</name> <name>Sample Project</name>
<title>Sample Project</title> <title>Sample Project</title>
@@ -7,7 +7,7 @@
<author>Jay Doh</author> <author>Jay Doh</author>
<saveCount>1303</saveCount> <saveCount>1303</saveCount>
<autoCount>199</autoCount> <autoCount>199</autoCount>
<editTime>65005</editTime> <editTime>65049</editTime>
</project> </project>
<settings> <settings>
<doBackup>False</doBackup> <doBackup>False</doBackup>
@@ -33,121 +33,121 @@
<section></section> <section></section>
</titleFormat> </titleFormat>
<status> <status>
<entry red="100" green="100" blue="100">New</entry> <entry key="sf12341" count="6" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Notes</entry> <entry key="sf24ce6" count="1" red="200" green="50" blue="0">Notes</entry>
<entry red="182" green="60" blue="0">Started</entry> <entry key="sc24b8f" count="2" red="182" green="60" blue="0">Started</entry>
<entry red="193" green="129" blue="0">1st Draft</entry> <entry key="s90e6c9" count="6" red="193" green="129" blue="0">1st Draft</entry>
<entry red="193" green="129" blue="0">2nd Draft</entry> <entry key="sd51c5b" count="1" red="193" green="129" blue="0">2nd Draft</entry>
<entry red="193" green="129" blue="0">3rd Draft</entry> <entry key="s8ae72a" count="0" red="193" green="129" blue="0">3rd Draft</entry>
<entry red="58" green="180" blue="58">Finished</entry> <entry key="s78ea90" count="0" red="58" green="180" blue="58">Finished</entry>
</status> </status>
<importance> <importance>
<entry red="100" green="100" blue="100">None</entry> <entry key="ia857f0" count="4" red="100" green="100" blue="100">None</entry>
<entry red="0" green="122" blue="188">Minor</entry> <entry key="icfb3a5" count="2" red="0" green="122" blue="188">Minor</entry>
<entry red="21" green="0" blue="180">Major</entry> <entry key="i2d7a54" count="2" red="21" green="0" blue="180">Major</entry>
<entry red="117" green="0" blue="175">Main</entry> <entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="25"> <content count="25">
<item handle="7031beac91f75" parent="None" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/> <meta expanded="True"/>
<name status="Started" import="None">Novel</name> <name status="sc24b8f" import="ia857f0">Novel</name>
</item> </item>
<item handle="53b69b83cdafc" parent="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="53b69b83cdafc" parent="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="93" wordCount="19" paraCount="2" cursorPos="2"/> <meta charCount="93" wordCount="19" paraCount="2" cursorPos="2"/>
<name status="Started" import="None" exported="True">Title Page</name> <name status="sc24b8f" import="ia857f0" exported="True">Title Page</name>
</item> </item>
<item handle="974e400180a99" parent="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="974e400180a99" parent="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="186" wordCount="39" paraCount="2" cursorPos="212"/> <meta charCount="186" wordCount="39" paraCount="2" cursorPos="212"/>
<name status="New" import="None" exported="True">Page</name> <name status="sf12341" import="ia857f0" exported="True">Page</name>
</item> </item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="edca4be2fcaf8" parent="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="26" wordCount="6" paraCount="1" cursorPos="33"/> <meta charCount="26" wordCount="6" paraCount="1" cursorPos="33"/>
<name status="New" import="None" exported="True">Part One</name> <name status="sf12341" import="ia857f0" exported="True">Part One</name>
</item> </item>
<item handle="e7ded148d6e4a" parent="7031beac91f75" order="3" type="FOLDER" class="NOVEL"> <item handle="e7ded148d6e4a" parent="7031beac91f75" order="3" type="FOLDER" class="NOVEL">
<meta expanded="True"/> <meta expanded="True"/>
<name status="1st Draft" import="None">A Folder</name> <name status="s90e6c9" import="ia857f0">A Folder</name>
</item> </item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="75" wordCount="14" paraCount="1" cursorPos="279"/> <meta charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
<name status="Notes" import="None" exported="True">Chapter One</name> <name status="sf24ce6" import="ia857f0" exported="True">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="636b6aa9b697b" parent="e7ded148d6e4a" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="2429" wordCount="432" paraCount="14" cursorPos="219"/> <meta charCount="2429" wordCount="432" paraCount="14" cursorPos="219"/>
<name status="1st Draft" import="None" exported="True">Making a Scene</name> <name status="s90e6c9" import="ia857f0" exported="True">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="476" wordCount="93" paraCount="3" cursorPos="577"/> <meta charCount="476" wordCount="93" paraCount="3" cursorPos="577"/>
<name status="1st Draft" import="None" exported="True">Another Scene</name> <name status="s90e6c9" import="ia857f0" exported="True">Another Scene</name>
</item> </item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" order="3" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="ba8a28a246524" parent="e7ded148d6e4a" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="617" wordCount="101" paraCount="3" cursorPos="4"/> <meta charCount="617" wordCount="101" paraCount="3" cursorPos="4"/>
<name status="New" import="None" exported="True">Interlude</name> <name status="sf12341" import="ia857f0" exported="True">Interlude</name>
</item> </item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" order="4" type="FILE" class="NOVEL" layout="NOTE"> <item handle="96b68994dfa3d" parent="e7ded148d6e4a" order="4" type="FILE" class="NOVEL" layout="NOTE">
<meta charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/> <meta charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/>
<name status="2nd Draft" import="None" exported="False">A Note on Structure</name> <name status="sd51c5b" import="ia857f0" exported="False">A Note on Structure</name>
</item> </item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" order="5" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="88706ddc78b1b" parent="e7ded148d6e4a" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="139" wordCount="28" paraCount="1" cursorPos="343"/> <meta charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="1st Draft" import="None" exported="True">Chapter Two</name> <name status="s90e6c9" import="ia857f0" exported="True">Chapter Two</name>
</item> </item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" order="6" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="ae7339df26ded" parent="e7ded148d6e4a" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="189" wordCount="37" paraCount="1" cursorPos="224"/> <meta charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="1st Draft" import="None" exported="True">We Found John!</name> <name status="s90e6c9" import="ia857f0" exported="True">We Found John!</name>
</item> </item>
<item handle="f6622b4617424" parent="None" order="1" type="ROOT" class="CHARACTER"> <item handle="f6622b4617424" parent="None" order="1" type="ROOT" class="CHARACTER">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="None">Characters</name> <name status="sf12341" import="ia857f0">Characters</name>
</item> </item>
<item handle="f7e2d9f330615" parent="f6622b4617424" order="0" type="FOLDER" class="CHARACTER"> <item handle="f7e2d9f330615" parent="f6622b4617424" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="None">Main Characters</name> <name status="sf12341" import="ia857f0">Main Characters</name>
</item> </item>
<item handle="14298de4d9524" parent="f7e2d9f330615" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="14298de4d9524" parent="f7e2d9f330615" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta charCount="49" wordCount="9" paraCount="1" cursorPos="24"/> <meta charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="New" import="Minor" exported="True">John Smith</name> <name status="sf12341" import="icfb3a5" exported="True">John Smith</name>
</item> </item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" order="1" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="bb2c23b3c42cc" parent="f7e2d9f330615" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta charCount="55" wordCount="9" paraCount="1" cursorPos="25"/> <meta charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="New" import="Major" exported="True">Jane Smith</name> <name status="sf12341" import="i2d7a54" exported="True">Jane Smith</name>
</item> </item>
<item handle="15c4492bd5107" parent="None" order="2" type="ROOT" class="WORLD"> <item handle="15c4492bd5107" parent="None" order="2" type="ROOT" class="WORLD">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="None">Locations</name> <name status="sf12341" import="ia857f0">Locations</name>
</item> </item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE"> <item handle="b3e74dbc1f584" parent="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta charCount="76" wordCount="15" paraCount="1" cursorPos="20"/> <meta charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="New" import="Main" exported="True">Earth</name> <name status="sf12341" import="i56be10" exported="True">Earth</name>
</item> </item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE"> <item handle="f1471bef9f2ae" parent="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta charCount="115" wordCount="24" paraCount="1" cursorPos="133"/> <meta charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="New" import="Minor" exported="True">Space</name> <name status="sf12341" import="icfb3a5" exported="True">Space</name>
</item> </item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE"> <item handle="5eaea4e8cdee8" parent="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta charCount="28" wordCount="6" paraCount="1" cursorPos="45"/> <meta charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="New" import="Major" exported="True">Mars</name> <name status="sf12341" import="i2d7a54" exported="True">Mars</name>
</item> </item>
<item handle="6827118336ac1" parent="None" order="3" type="ROOT" class="ARCHIVE"> <item handle="6827118336ac1" parent="None" order="3" type="ROOT" class="ARCHIVE">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="None">Archive</name> <name status="sf12341" import="ia857f0">Archive</name>
</item> </item>
<item handle="ae9bf3c3ea159" parent="6827118336ac1" order="0" type="FOLDER" class="ARCHIVE"> <item handle="ae9bf3c3ea159" parent="6827118336ac1" order="0" type="FOLDER" class="ARCHIVE">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="None">Scenes</name> <name status="sf12341" import="ia857f0">Scenes</name>
</item> </item>
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="315" wordCount="55" paraCount="1" cursorPos="322"/> <meta charCount="315" wordCount="55" paraCount="1" cursorPos="322"/>
<name status="1st Draft" import="None" exported="True">Old File</name> <name status="s90e6c9" import="ia857f0" exported="True">Old File</name>
</item> </item>
<item handle="98acd8c76c93a" parent="None" order="4" type="TRASH" class="TRASH"> <item handle="98acd8c76c93a" parent="None" order="4" type="TRASH" class="TRASH">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="None">Trash</name> <name status="sf12341" import="ia857f0">Trash</name>
</item> </item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="b8136a5a774a0" parent="98acd8c76c93a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="30" wordCount="6" paraCount="1" cursorPos="36"/> <meta charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="New" import="None" exported="True">Delete Me!</name> <name status="sf12341" import="ia857f0" exported="True">Delete Me!</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
+18 -1
View File
@@ -24,6 +24,8 @@ import sys
import pytest import pytest
import shutil import shutil
from dataclasses import dataclass
from mock import MockGuiMain from mock import MockGuiMain
from tools import cleanProject 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") @pytest.fixture(scope="session")
def ipsumText(): def ipsumText():
"""Return five paragraphs of Lorem Ipsum text. """Return five paragraphs of Lorem Ipsum text.
+32 -32
View File
@@ -1,12 +1,12 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:36:12"> <novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 14:19:39">
<project> <project>
<name>Lorem Ipsum</name> <name>Lorem Ipsum</name>
<title>Lorem Ipsum</title> <title>Lorem Ipsum</title>
<author>lipsum.com</author> <author>lipsum.com</author>
<saveCount>23</saveCount> <saveCount>24</saveCount>
<autoCount>24</autoCount> <autoCount>24</autoCount>
<editTime>1854</editTime> <editTime>1856</editTime>
</project> </project>
<settings> <settings>
<doBackup>False</doBackup> <doBackup>False</doBackup>
@@ -31,102 +31,102 @@
<section></section> <section></section>
</titleFormat> </titleFormat>
<status> <status>
<entry red="100" green="100" blue="100">New</entry> <entry key="sbaa94f" count="3" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Note</entry> <entry key="s27bf7c" count="0" red="200" green="50" blue="0">Note</entry>
<entry red="200" green="150" blue="0">Draft</entry> <entry key="s92a87b" count="5" red="200" green="150" blue="0">Draft</entry>
<entry red="50" green="200" blue="0">Finished</entry> <entry key="sedd043" count="7" red="50" green="200" blue="0">Finished</entry>
</status> </status>
<importance> <importance>
<entry red="100" green="100" blue="100">New</entry> <entry key="i613591" count="6" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Minor</entry> <entry key="i560cbf" count="0" red="200" green="50" blue="0">Minor</entry>
<entry red="200" green="150" blue="0">Major</entry> <entry key="i37861c" count="0" red="200" green="150" blue="0">Major</entry>
<entry red="50" green="200" blue="0">Main</entry> <entry key="id6b1d0" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="21"> <content count="21">
<item handle="b3643d0f92e32" parent="None" root="b3643d0f92e32" order="0" type="ROOT" class="NOVEL"> <item handle="b3643d0f92e32" parent="None" root="b3643d0f92e32" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="New">Novel</name> <name status="sbaa94f" import="i613591">Novel</name>
</item> </item>
<item handle="7a992350f3eb6" parent="b3643d0f92e32" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="7a992350f3eb6" parent="b3643d0f92e32" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="230" wordCount="40" paraCount="3" cursorPos="148"/> <meta charCount="230" wordCount="40" paraCount="3" cursorPos="148"/>
<name status="Finished" import="New" exported="True">Lorem Ipsum</name> <name status="sedd043" import="i613591" exported="True">Lorem Ipsum</name>
</item> </item>
<item handle="8c58a65414c23" parent="b3643d0f92e32" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="8c58a65414c23" parent="b3643d0f92e32" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="1058" wordCount="176" paraCount="2" cursorPos="43"/> <meta charCount="1058" wordCount="176" paraCount="2" cursorPos="43"/>
<name status="Finished" import="New" exported="True">Front Matter</name> <name status="sedd043" import="i613591" exported="True">Front Matter</name>
</item> </item>
<item handle="88d59a277361b" parent="b3643d0f92e32" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="88d59a277361b" parent="b3643d0f92e32" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="584" wordCount="92" paraCount="1" cursorPos="4"/> <meta charCount="584" wordCount="92" paraCount="1" cursorPos="4"/>
<name status="Draft" import="New" exported="True">Prologue</name> <name status="s92a87b" import="i613591" exported="True">Prologue</name>
</item> </item>
<item handle="db7e733775d4d" parent="b3643d0f92e32" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="db7e733775d4d" parent="b3643d0f92e32" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="35" wordCount="6" paraCount="1" cursorPos="42"/> <meta charCount="35" wordCount="6" paraCount="1" cursorPos="42"/>
<name status="New" import="New" exported="True">Act One</name> <name status="sbaa94f" import="i613591" exported="True">Act One</name>
</item> </item>
<item handle="45e6b01ca35c1" parent="b3643d0f92e32" root="b3643d0f92e32" order="4" type="FOLDER" class="NOVEL"> <item handle="45e6b01ca35c1" parent="b3643d0f92e32" root="b3643d0f92e32" order="4" type="FOLDER" class="NOVEL">
<meta expanded="True"/> <meta expanded="True"/>
<name status="Draft" import="New">Chapter One</name> <name status="s92a87b" import="i613591">Chapter One</name>
</item> </item>
<item handle="fb609cd8319dc" parent="45e6b01ca35c1" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="fb609cd8319dc" parent="45e6b01ca35c1" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="419" wordCount="67" paraCount="1" cursorPos="56"/> <meta charCount="419" wordCount="67" paraCount="1" cursorPos="56"/>
<name status="Draft" import="New" exported="True">Chapter One</name> <name status="s92a87b" import="i613591" exported="True">Chapter One</name>
</item> </item>
<item handle="88243afbe5ed8" parent="45e6b01ca35c1" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="88243afbe5ed8" parent="45e6b01ca35c1" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="2758" wordCount="404" paraCount="4" cursorPos="1528"/> <meta charCount="2758" wordCount="404" paraCount="4" cursorPos="1528"/>
<name status="Finished" import="New" exported="True">Scene One</name> <name status="sedd043" import="i613591" exported="True">Scene One</name>
</item> </item>
<item handle="f96ec11c6a3da" parent="45e6b01ca35c1" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="f96ec11c6a3da" parent="45e6b01ca35c1" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="4043" wordCount="600" paraCount="6" cursorPos="2335"/> <meta charCount="4043" wordCount="600" paraCount="6" cursorPos="2335"/>
<name status="Finished" import="New" exported="True">Scene Two</name> <name status="sedd043" import="i613591" exported="True">Scene Two</name>
</item> </item>
<item handle="846352075de7d" parent="b3643d0f92e32" root="b3643d0f92e32" order="5" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="846352075de7d" parent="b3643d0f92e32" root="b3643d0f92e32" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="631" wordCount="109" paraCount="3" cursorPos="376"/> <meta charCount="631" wordCount="109" paraCount="3" cursorPos="376"/>
<name status="New" import="New" exported="False">Interlude</name> <name status="sbaa94f" import="i613591" exported="False">Interlude</name>
</item> </item>
<item handle="6bd935d2490cd" parent="b3643d0f92e32" root="b3643d0f92e32" order="6" type="FOLDER" class="NOVEL"> <item handle="6bd935d2490cd" parent="b3643d0f92e32" root="b3643d0f92e32" order="6" type="FOLDER" class="NOVEL">
<meta expanded="True"/> <meta expanded="True"/>
<name status="Draft" import="New">Chapter Two</name> <name status="s92a87b" import="i613591">Chapter Two</name>
</item> </item>
<item handle="441420a886d82" parent="6bd935d2490cd" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="441420a886d82" parent="6bd935d2490cd" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="477" wordCount="70" paraCount="1" cursorPos="56"/> <meta charCount="477" wordCount="70" paraCount="1" cursorPos="56"/>
<name status="Draft" import="New" exported="True">Chapter Two</name> <name status="s92a87b" import="i613591" exported="True">Chapter Two</name>
</item> </item>
<item handle="eb103bc70c90c" parent="6bd935d2490cd" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="eb103bc70c90c" parent="6bd935d2490cd" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="3006" wordCount="439" paraCount="4" cursorPos="57"/> <meta charCount="3006" wordCount="439" paraCount="4" cursorPos="57"/>
<name status="Finished" import="New" exported="True">Scene Three</name> <name status="sedd043" import="i613591" exported="True">Scene Three</name>
</item> </item>
<item handle="f8c0562e50f1b" parent="6bd935d2490cd" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="f8c0562e50f1b" parent="6bd935d2490cd" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="3839" wordCount="563" paraCount="6" cursorPos="56"/> <meta charCount="3839" wordCount="563" paraCount="6" cursorPos="56"/>
<name status="Finished" import="New" exported="True">Scene Four</name> <name status="sedd043" import="i613591" exported="True">Scene Four</name>
</item> </item>
<item handle="47666c91c7ccf" parent="6bd935d2490cd" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="47666c91c7ccf" parent="6bd935d2490cd" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="3644" wordCount="543" paraCount="5" cursorPos="351"/> <meta charCount="3644" wordCount="543" paraCount="5" cursorPos="351"/>
<name status="Finished" import="New" exported="True">Scene Five</name> <name status="sedd043" import="i613591" exported="True">Scene Five</name>
</item> </item>
<item handle="67a8707f2f249" parent="None" root="67a8707f2f249" order="1" type="ROOT" class="CHARACTER"> <item handle="67a8707f2f249" parent="None" root="67a8707f2f249" order="1" type="ROOT" class="CHARACTER">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="New">Characters</name> <name status="sbaa94f" import="i613591">Characters</name>
</item> </item>
<item handle="4c4f28287af27" parent="67a8707f2f249" root="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="4c4f28287af27" parent="67a8707f2f249" root="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta charCount="1864" wordCount="284" paraCount="3" cursorPos="1883"/> <meta charCount="1864" wordCount="284" paraCount="3" cursorPos="1883"/>
<name status="New" import="New" exported="True">Mr. Nobody</name> <name status="sbaa94f" import="i613591" exported="True">Mr. Nobody</name>
</item> </item>
<item handle="6c6afb1247750" parent="None" root="6c6afb1247750" order="2" type="ROOT" class="PLOT"> <item handle="6c6afb1247750" parent="None" root="6c6afb1247750" order="2" type="ROOT" class="PLOT">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="New">Plot</name> <name status="sbaa94f" import="i613591">Plot</name>
</item> </item>
<item handle="2426c6f0ca922" parent="6c6afb1247750" root="6c6afb1247750" order="0" type="FILE" class="PLOT" layout="NOTE"> <item handle="2426c6f0ca922" parent="6c6afb1247750" root="6c6afb1247750" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta charCount="1369" wordCount="195" paraCount="2" cursorPos="1387"/> <meta charCount="1369" wordCount="195" paraCount="2" cursorPos="1387"/>
<name status="New" import="New" exported="True">Main</name> <name status="sbaa94f" import="i613591" exported="True">Main</name>
</item> </item>
<item handle="60bdf227455cc" parent="None" root="60bdf227455cc" order="3" type="ROOT" class="WORLD"> <item handle="60bdf227455cc" parent="None" root="60bdf227455cc" order="3" type="ROOT" class="WORLD">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="New">World</name> <name status="sbaa94f" import="i613591">World</name>
</item> </item>
<item handle="04468803b92e1" parent="60bdf227455cc" root="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE"> <item handle="04468803b92e1" parent="60bdf227455cc" root="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta charCount="1770" wordCount="259" paraCount="3" cursorPos="1792"/> <meta charCount="1770" wordCount="259" paraCount="3" cursorPos="1792"/>
<name status="New" import="New" exported="True">Ancient Europe</name> <name status="sbaa94f" import="i613591" exported="True">Ancient Europe</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
+19 -19
View File
@@ -1,13 +1,13 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:36:18"> <novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 14:19:35">
<project> <project>
<name>Test Minimal</name> <name>Test Minimal</name>
<title>Minimal</title> <title>Minimal</title>
<author>Jane Doe</author> <author>Jane Doe</author>
<author>John Doh</author> <author>John Doh</author>
<saveCount>14</saveCount> <saveCount>15</saveCount>
<autoCount>2</autoCount> <autoCount>2</autoCount>
<editTime>135</editTime> <editTime>146</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>True</doBackup>
@@ -29,50 +29,50 @@
<section></section> <section></section>
</titleFormat> </titleFormat>
<status> <status>
<entry red="100" green="100" blue="100">New</entry> <entry key="s72322d" count="5" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Note</entry> <entry key="sb6adc7" count="0" red="200" green="50" blue="0">Note</entry>
<entry red="200" green="150" blue="0">Draft</entry> <entry key="s2ae76d" count="0" red="200" green="150" blue="0">Draft</entry>
<entry red="50" green="200" blue="0">Finished</entry> <entry key="s541953" count="0" red="50" green="200" blue="0">Finished</entry>
</status> </status>
<importance> <importance>
<entry red="100" green="100" blue="100">New</entry> <entry key="iffcacb" count="3" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Minor</entry> <entry key="i6b130b" count="0" red="200" green="50" blue="0">Minor</entry>
<entry red="200" green="150" blue="0">Major</entry> <entry key="iece803" count="0" red="200" green="150" blue="0">Major</entry>
<entry red="50" green="200" blue="0">Main</entry> <entry key="i5ba06e" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="8"> <content count="8">
<item handle="a508bb932959c" parent="None" root="a508bb932959c" order="0" type="ROOT" class="NOVEL"> <item handle="a508bb932959c" parent="None" root="a508bb932959c" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="New">Novel</name> <name status="s72322d" import="iffcacb">Novel</name>
</item> </item>
<item handle="a35baf2e93843" parent="a508bb932959c" root="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="a35baf2e93843" parent="a508bb932959c" root="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="28" wordCount="6" paraCount="1" cursorPos="33"/> <meta charCount="28" wordCount="6" paraCount="1" cursorPos="33"/>
<name status="New" import="New" exported="True">Title Page</name> <name status="s72322d" import="iffcacb" exported="True">Title Page</name>
</item> </item>
<item handle="a6d311a93600a" parent="a508bb932959c" root="a508bb932959c" order="1" type="FOLDER" class="NOVEL"> <item handle="a6d311a93600a" parent="a508bb932959c" root="a508bb932959c" order="1" type="FOLDER" class="NOVEL">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="New">New Chapter</name> <name status="s72322d" import="iffcacb">New Chapter</name>
</item> </item>
<item handle="f5ab3e30151e1" parent="a6d311a93600a" root="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="f5ab3e30151e1" parent="a6d311a93600a" root="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="16"/> <meta charCount="11" wordCount="2" paraCount="0" cursorPos="16"/>
<name status="New" import="New" exported="True">New Chapter</name> <name status="s72322d" import="iffcacb" exported="True">New Chapter</name>
</item> </item>
<item handle="8c659a11cd429" parent="a6d311a93600a" root="a508bb932959c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="8c659a11cd429" parent="a6d311a93600a" root="a508bb932959c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="9" wordCount="2" paraCount="0" cursorPos="15"/> <meta charCount="9" wordCount="2" paraCount="0" cursorPos="15"/>
<name status="New" import="New" exported="True">New Scene</name> <name status="s72322d" import="iffcacb" exported="True">New Scene</name>
</item> </item>
<item handle="7695ce551d265" parent="None" root="7695ce551d265" order="1" type="ROOT" class="PLOT"> <item handle="7695ce551d265" parent="None" root="7695ce551d265" order="1" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">Plot</name> <name status="s72322d" import="iffcacb">Plot</name>
</item> </item>
<item handle="afb3043c7b2b3" parent="None" root="afb3043c7b2b3" order="2" type="ROOT" class="CHARACTER"> <item handle="afb3043c7b2b3" parent="None" root="afb3043c7b2b3" order="2" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">Characters</name> <name status="s72322d" import="iffcacb">Characters</name>
</item> </item>
<item handle="9d5247ab588e0" parent="None" root="9d5247ab588e0" order="3" type="ROOT" class="WORLD"> <item handle="9d5247ab588e0" parent="None" root="9d5247ab588e0" order="3" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">World</name> <name status="s72322d" import="iffcacb">World</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:38:23"> <novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 14:14:40">
<project> <project>
<name>Test Custom</name> <name>Test Custom</name>
<title>Test Novel</title> <title>Test Novel</title>
@@ -29,110 +29,110 @@
<section></section> <section></section>
</titleFormat> </titleFormat>
<status> <status>
<entry red="100" green="100" blue="100">New</entry> <entry key="sbc8960" count="17" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Note</entry> <entry key="s1a3d1f" count="0" red="200" green="50" blue="0">Note</entry>
<entry red="200" green="150" blue="0">Draft</entry> <entry key="sad3c2d" count="0" red="200" green="150" blue="0">Draft</entry>
<entry red="50" green="200" blue="0">Finished</entry> <entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
</status> </status>
<importance> <importance>
<entry red="100" green="100" blue="100">New</entry> <entry key="ie465e1" count="0" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Minor</entry> <entry key="i8b9d24" count="0" red="200" green="50" blue="0">Minor</entry>
<entry red="200" green="150" blue="0">Major</entry> <entry key="i16419f" count="0" red="200" green="150" blue="0">Major</entry>
<entry red="50" green="200" blue="0">Main</entry> <entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="23"> <content count="23">
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL"> <item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Novel</name> <name status="sbc8960" import="None">Novel</name>
</item> </item>
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT"> <item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Plot</name> <name status="sbc8960" import="None">Plot</name>
</item> </item>
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER"> <item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Characters</name> <name status="sbc8960" import="None">Characters</name>
</item> </item>
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD"> <item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Locations</name> <name status="sbc8960" import="None">Locations</name>
</item> </item>
<item handle="25fc0e7096fc6" parent="None" root="25fc0e7096fc6" order="0" type="ROOT" class="TIMELINE"> <item handle="25fc0e7096fc6" parent="None" root="25fc0e7096fc6" order="0" type="ROOT" class="TIMELINE">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Timeline</name> <name status="sbc8960" import="None">Timeline</name>
</item> </item>
<item handle="31489056e0916" parent="None" root="31489056e0916" order="0" type="ROOT" class="OBJECT"> <item handle="31489056e0916" parent="None" root="31489056e0916" order="0" type="ROOT" class="OBJECT">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Objects</name> <name status="sbc8960" import="None">Objects</name>
</item> </item>
<item handle="98010bd9270f9" parent="None" root="98010bd9270f9" order="0" type="ROOT" class="ENTITY"> <item handle="98010bd9270f9" parent="None" root="98010bd9270f9" order="0" type="ROOT" class="ENTITY">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Entities</name> <name status="sbc8960" import="None">Entities</name>
</item> </item>
<item handle="0e17daca5f3e1" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0e17daca5f3e1" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Title Page</name> <name status="sbc8960" import="None" exported="True">Title Page</name>
</item> </item>
<item handle="1a6562590ef19" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL"> <item handle="1a6562590ef19" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Chapter 1</name> <name status="sbc8960" import="None">Chapter 1</name>
</item> </item>
<item handle="031b4af5197ec" parent="1a6562590ef19" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="031b4af5197ec" parent="1a6562590ef19" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Chapter 1</name> <name status="sbc8960" import="None" exported="True">Chapter 1</name>
</item> </item>
<item handle="41cfc0d1f2d12" parent="1a6562590ef19" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="41cfc0d1f2d12" parent="1a6562590ef19" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 1.1</name> <name status="sbc8960" import="None" exported="True">Scene 1.1</name>
</item> </item>
<item handle="2858dcd1057d3" parent="1a6562590ef19" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="2858dcd1057d3" parent="1a6562590ef19" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 1.2</name> <name status="sbc8960" import="None" exported="True">Scene 1.2</name>
</item> </item>
<item handle="2fca346db6561" parent="1a6562590ef19" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="2fca346db6561" parent="1a6562590ef19" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 1.3</name> <name status="sbc8960" import="None" exported="True">Scene 1.3</name>
</item> </item>
<item handle="02d20bbd7e394" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL"> <item handle="02d20bbd7e394" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Chapter 2</name> <name status="sbc8960" import="None">Chapter 2</name>
</item> </item>
<item handle="7688b6ef52555" parent="02d20bbd7e394" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="7688b6ef52555" parent="02d20bbd7e394" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Chapter 2</name> <name status="sbc8960" import="None" exported="True">Chapter 2</name>
</item> </item>
<item handle="c837649cce43f" parent="02d20bbd7e394" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="c837649cce43f" parent="02d20bbd7e394" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 2.1</name> <name status="sbc8960" import="None" exported="True">Scene 2.1</name>
</item> </item>
<item handle="6208ef0f7750c" parent="02d20bbd7e394" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="6208ef0f7750c" parent="02d20bbd7e394" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 2.2</name> <name status="sbc8960" import="None" exported="True">Scene 2.2</name>
</item> </item>
<item handle="3e1e967e9b793" parent="02d20bbd7e394" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="3e1e967e9b793" parent="02d20bbd7e394" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 2.3</name> <name status="sbc8960" import="None" exported="True">Scene 2.3</name>
</item> </item>
<item handle="39fa9ec190eee" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL"> <item handle="39fa9ec190eee" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Chapter 3</name> <name status="sbc8960" import="None">Chapter 3</name>
</item> </item>
<item handle="d029fa3a95e17" parent="39fa9ec190eee" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="d029fa3a95e17" parent="39fa9ec190eee" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Chapter 3</name> <name status="sbc8960" import="None" exported="True">Chapter 3</name>
</item> </item>
<item handle="81b8a03f97e87" parent="39fa9ec190eee" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="81b8a03f97e87" parent="39fa9ec190eee" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 3.1</name> <name status="sbc8960" import="None" exported="True">Scene 3.1</name>
</item> </item>
<item handle="da4ea2a5506f2" parent="39fa9ec190eee" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="da4ea2a5506f2" parent="39fa9ec190eee" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 3.2</name> <name status="sbc8960" import="None" exported="True">Scene 3.2</name>
</item> </item>
<item handle="a68b412c42825" parent="39fa9ec190eee" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="a68b412c42825" parent="39fa9ec190eee" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 3.3</name> <name status="sbc8960" import="None" exported="True">Scene 3.3</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:38:23"> <novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 14:15:44">
<project> <project>
<name>Test Custom</name> <name>Test Custom</name>
<title>Test Novel</title> <title>Test Novel</title>
@@ -29,74 +29,74 @@
<section></section> <section></section>
</titleFormat> </titleFormat>
<status> <status>
<entry red="100" green="100" blue="100">New</entry> <entry key="sbc8960" count="8" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Note</entry> <entry key="s1a3d1f" count="0" red="200" green="50" blue="0">Note</entry>
<entry red="200" green="150" blue="0">Draft</entry> <entry key="sad3c2d" count="0" red="200" green="150" blue="0">Draft</entry>
<entry red="50" green="200" blue="0">Finished</entry> <entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
</status> </status>
<importance> <importance>
<entry red="100" green="100" blue="100">New</entry> <entry key="ie465e1" count="0" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Minor</entry> <entry key="i8b9d24" count="0" red="200" green="50" blue="0">Minor</entry>
<entry red="200" green="150" blue="0">Major</entry> <entry key="i16419f" count="0" red="200" green="150" blue="0">Major</entry>
<entry red="50" green="200" blue="0">Main</entry> <entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="14"> <content count="14">
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL"> <item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Novel</name> <name status="sbc8960" import="None">Novel</name>
</item> </item>
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT"> <item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Plot</name> <name status="sbc8960" import="None">Plot</name>
</item> </item>
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER"> <item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Characters</name> <name status="sbc8960" import="None">Characters</name>
</item> </item>
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD"> <item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Locations</name> <name status="sbc8960" import="None">Locations</name>
</item> </item>
<item handle="25fc0e7096fc6" parent="None" root="25fc0e7096fc6" order="0" type="ROOT" class="TIMELINE"> <item handle="25fc0e7096fc6" parent="None" root="25fc0e7096fc6" order="0" type="ROOT" class="TIMELINE">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Timeline</name> <name status="sbc8960" import="None">Timeline</name>
</item> </item>
<item handle="31489056e0916" parent="None" root="31489056e0916" order="0" type="ROOT" class="OBJECT"> <item handle="31489056e0916" parent="None" root="31489056e0916" order="0" type="ROOT" class="OBJECT">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Objects</name> <name status="sbc8960" import="None">Objects</name>
</item> </item>
<item handle="98010bd9270f9" parent="None" root="98010bd9270f9" order="0" type="ROOT" class="ENTITY"> <item handle="98010bd9270f9" parent="None" root="98010bd9270f9" order="0" type="ROOT" class="ENTITY">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Entities</name> <name status="sbc8960" import="None">Entities</name>
</item> </item>
<item handle="0e17daca5f3e1" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0e17daca5f3e1" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Title Page</name> <name status="sbc8960" import="None" exported="True">Title Page</name>
</item> </item>
<item handle="1a6562590ef19" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="1a6562590ef19" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 1</name> <name status="sbc8960" import="None" exported="True">Scene 1</name>
</item> </item>
<item handle="031b4af5197ec" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="031b4af5197ec" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 2</name> <name status="sbc8960" import="None" exported="True">Scene 2</name>
</item> </item>
<item handle="41cfc0d1f2d12" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="41cfc0d1f2d12" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 3</name> <name status="sbc8960" import="None" exported="True">Scene 3</name>
</item> </item>
<item handle="2858dcd1057d3" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="2858dcd1057d3" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 4</name> <name status="sbc8960" import="None" exported="True">Scene 4</name>
</item> </item>
<item handle="2fca346db6561" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="2fca346db6561" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 5</name> <name status="sbc8960" import="None" exported="True">Scene 5</name>
</item> </item>
<item handle="02d20bbd7e394" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="02d20bbd7e394" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Scene 6</name> <name status="sbc8960" import="None" exported="True">Scene 6</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:38:23"> <novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 14:17:50">
<project> <project>
<name>New Project</name> <name>New Project</name>
<title></title> <title></title>
@@ -27,58 +27,58 @@
<section></section> <section></section>
</titleFormat> </titleFormat>
<status> <status>
<entry red="100" green="100" blue="100">New</entry> <entry key="sbc8960" count="6" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Note</entry> <entry key="s1a3d1f" count="0" red="200" green="50" blue="0">Note</entry>
<entry red="200" green="150" blue="0">Draft</entry> <entry key="sad3c2d" count="0" red="200" green="150" blue="0">Draft</entry>
<entry red="50" green="200" blue="0">Finished</entry> <entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
</status> </status>
<importance> <importance>
<entry red="100" green="100" blue="100">New</entry> <entry key="ie465e1" count="3" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Minor</entry> <entry key="i8b9d24" count="0" red="200" green="50" blue="0">Minor</entry>
<entry red="200" green="150" blue="0">Major</entry> <entry key="i16419f" count="0" red="200" green="150" blue="0">Major</entry>
<entry red="50" green="200" blue="0">Main</entry> <entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="10"> <content count="10">
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL"> <item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">Novel</name> <name status="sbc8960" import="ie465e1">Novel</name>
</item> </item>
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT"> <item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">Plot</name> <name status="sbc8960" import="ie465e1">Plot</name>
</item> </item>
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER"> <item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">Characters</name> <name status="sbc8960" import="ie465e1">Characters</name>
</item> </item>
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD"> <item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">World</name> <name status="sbc8960" import="ie465e1">World</name>
</item> </item>
<item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="New" exported="True">Title Page</name> <name status="sbc8960" import="ie465e1" exported="True">Title Page</name>
</item> </item>
<item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL"> <item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">New Chapter</name> <name status="sbc8960" import="ie465e1">New Chapter</name>
</item> </item>
<item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="New" exported="True">New Chapter</name> <name status="sbc8960" import="ie465e1" exported="True">New Chapter</name>
</item> </item>
<item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="New" exported="True">New Scene</name> <name status="sbc8960" import="ie465e1" exported="True">New Scene</name>
</item> </item>
<item handle="1a6562590ef19" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="1a6562590ef19" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Hello</name> <name status="sbc8960" import="None" exported="True">Hello</name>
</item> </item>
<item handle="031b4af5197ec" parent="71ee45a3c0db9" root="71ee45a3c0db9" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="031b4af5197ec" parent="71ee45a3c0db9" root="71ee45a3c0db9" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Jane</name> <name status="sbc8960" import="None" exported="True">Jane</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:54:40"> <novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 14:13:27">
<project> <project>
<name>New Project</name> <name>New Project</name>
<title></title> <title></title>
@@ -27,50 +27,50 @@
<section></section> <section></section>
</titleFormat> </titleFormat>
<status> <status>
<entry red="100" green="100" blue="100">New</entry> <entry key="sbc8960" count="5" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Note</entry> <entry key="s1a3d1f" count="0" red="200" green="50" blue="0">Note</entry>
<entry red="200" green="150" blue="0">Draft</entry> <entry key="sad3c2d" count="0" red="200" green="150" blue="0">Draft</entry>
<entry red="50" green="200" blue="0">Finished</entry> <entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
</status> </status>
<importance> <importance>
<entry red="100" green="100" blue="100">New</entry> <entry key="ie465e1" count="3" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Minor</entry> <entry key="i8b9d24" count="0" red="200" green="50" blue="0">Minor</entry>
<entry red="200" green="150" blue="0">Major</entry> <entry key="i16419f" count="0" red="200" green="150" blue="0">Major</entry>
<entry red="50" green="200" blue="0">Main</entry> <entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="8"> <content count="8">
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL"> <item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">Novel</name> <name status="sbc8960" import="ie465e1">Novel</name>
</item> </item>
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT"> <item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">Plot</name> <name status="sbc8960" import="ie465e1">Plot</name>
</item> </item>
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER"> <item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">Characters</name> <name status="sbc8960" import="ie465e1">Characters</name>
</item> </item>
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD"> <item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">World</name> <name status="sbc8960" import="ie465e1">World</name>
</item> </item>
<item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="New" exported="True">Title Page</name> <name status="sbc8960" import="ie465e1" exported="True">Title Page</name>
</item> </item>
<item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL"> <item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">New Chapter</name> <name status="sbc8960" import="ie465e1">New Chapter</name>
</item> </item>
<item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="New" exported="True">New Chapter</name> <name status="sbc8960" import="ie465e1" exported="True">New Chapter</name>
</item> </item>
<item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="New" exported="True">New Scene</name> <name status="sbc8960" import="ie465e1" exported="True">New Scene</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 22:49:41"> <novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 18:46:31">
<project> <project>
<name>New Project</name> <name>New Project</name>
<title></title> <title></title>
@@ -27,82 +27,82 @@
<section></section> <section></section>
</titleFormat> </titleFormat>
<status> <status>
<entry red="100" green="100" blue="100">New</entry> <entry key="sbc8960" count="6" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Note</entry> <entry key="s1a3d1f" count="0" red="200" green="50" blue="0">Note</entry>
<entry red="200" green="150" blue="0">Draft</entry> <entry key="sad3c2d" count="0" red="200" green="150" blue="0">Draft</entry>
<entry red="50" green="200" blue="0">Finished</entry> <entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
</status> </status>
<importance> <importance>
<entry red="100" green="100" blue="100">New</entry> <entry key="ie465e1" count="3" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Minor</entry> <entry key="i8b9d24" count="0" red="200" green="50" blue="0">Minor</entry>
<entry red="200" green="150" blue="0">Major</entry> <entry key="i16419f" count="0" red="200" green="150" blue="0">Major</entry>
<entry red="50" green="200" blue="0">Main</entry> <entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="16"> <content count="16">
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL"> <item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">Novel</name> <name status="sbc8960" import="ie465e1">Novel</name>
</item> </item>
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT"> <item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">Plot</name> <name status="sbc8960" import="ie465e1">Plot</name>
</item> </item>
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER"> <item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">Characters</name> <name status="sbc8960" import="ie465e1">Characters</name>
</item> </item>
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD"> <item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">World</name> <name status="sbc8960" import="ie465e1">World</name>
</item> </item>
<item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="New" exported="True">Title Page</name> <name status="sbc8960" import="ie465e1" exported="True">Title Page</name>
</item> </item>
<item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL"> <item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="New">New Chapter</name> <name status="sbc8960" import="ie465e1">New Chapter</name>
</item> </item>
<item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="New" exported="True">New Chapter</name> <name status="sbc8960" import="ie465e1" exported="True">New Chapter</name>
</item> </item>
<item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="New" import="New" exported="True">New Scene</name> <name status="sbc8960" import="ie465e1" exported="True">New Scene</name>
</item> </item>
<item handle="1a6562590ef19" parent="None" root="1a6562590ef19" order="0" type="ROOT" class="NOVEL"> <item handle="1a6562590ef19" parent="None" root="1a6562590ef19" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Novel</name> <name status="sbc8960" import="None">Novel</name>
</item> </item>
<item handle="031b4af5197ec" parent="None" root="031b4af5197ec" order="0" type="ROOT" class="PLOT"> <item handle="031b4af5197ec" parent="None" root="031b4af5197ec" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Plot</name> <name status="sbc8960" import="None">Plot</name>
</item> </item>
<item handle="41cfc0d1f2d12" parent="None" root="41cfc0d1f2d12" order="0" type="ROOT" class="CHARACTER"> <item handle="41cfc0d1f2d12" parent="None" root="41cfc0d1f2d12" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Character</name> <name status="sbc8960" import="None">Character</name>
</item> </item>
<item handle="2858dcd1057d3" parent="None" root="2858dcd1057d3" order="0" type="ROOT" class="WORLD"> <item handle="2858dcd1057d3" parent="None" root="2858dcd1057d3" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">World</name> <name status="sbc8960" import="None">World</name>
</item> </item>
<item handle="2fca346db6561" parent="None" root="2fca346db6561" order="0" type="ROOT" class="TIMELINE"> <item handle="2fca346db6561" parent="None" root="2fca346db6561" order="0" type="ROOT" class="TIMELINE">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Timeline</name> <name status="sbc8960" import="None">Timeline</name>
</item> </item>
<item handle="02d20bbd7e394" parent="None" root="02d20bbd7e394" order="0" type="ROOT" class="OBJECT"> <item handle="02d20bbd7e394" parent="None" root="02d20bbd7e394" order="0" type="ROOT" class="OBJECT">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Object</name> <name status="sbc8960" import="None">Object</name>
</item> </item>
<item handle="7688b6ef52555" parent="None" root="7688b6ef52555" order="0" type="ROOT" class="CUSTOM"> <item handle="7688b6ef52555" parent="None" root="7688b6ef52555" order="0" type="ROOT" class="CUSTOM">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Custom1</name> <name status="sbc8960" import="None">Custom1</name>
</item> </item>
<item handle="c837649cce43f" parent="None" root="c837649cce43f" order="0" type="ROOT" class="CUSTOM"> <item handle="c837649cce43f" parent="None" root="c837649cce43f" order="0" type="ROOT" class="CUSTOM">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Custom2</name> <name status="sbc8960" import="None">Custom2</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:31:53"> <novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 12:58:48">
<project> <project>
<name>New Project</name> <name>New Project</name>
<title></title> <title></title>
@@ -27,62 +27,62 @@
<section></section> <section></section>
</titleFormat> </titleFormat>
<status> <status>
<entry red="100" green="100" blue="100">New</entry> <entry key="sa3b179" count="5" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Note</entry> <entry key="s1c8031" count="0" red="200" green="50" blue="0">Note</entry>
<entry red="200" green="150" blue="0">Draft</entry> <entry key="s06671a" count="0" red="200" green="150" blue="0">Draft</entry>
<entry red="50" green="200" blue="0">Finished</entry> <entry key="sbdd640" count="0" red="50" green="200" blue="0">Finished</entry>
</status> </status>
<importance> <importance>
<entry red="100" green="100" blue="100">New</entry> <entry key="i466852" count="3" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Minor</entry> <entry key="i3eb13b" count="0" red="200" green="50" blue="0">Minor</entry>
<entry red="200" green="150" blue="0">Major</entry> <entry key="i392456" count="0" red="200" green="150" blue="0">Major</entry>
<entry red="50" green="200" blue="0">Main</entry> <entry key="i23b8c1" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="12"> <content count="12">
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL"> <item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="New">Novel</name> <name status="sa3b179" import="i466852">Novel</name>
</item> </item>
<item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/> <meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="New" import="New" exported="True">Title Page</name> <name status="sa3b179" import="i466852" exported="True">Title Page</name>
</item> </item>
<item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="1" type="FOLDER" class="NOVEL"> <item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="1" type="FOLDER" class="NOVEL">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="New">New Chapter</name> <name status="sa3b179" import="i466852">New Chapter</name>
</item> </item>
<item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/> <meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="New" import="New" exported="True">New Chapter</name> <name status="sa3b179" import="i466852" exported="True">New Chapter</name>
</item> </item>
<item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="612" wordCount="95" paraCount="10" cursorPos="768"/> <meta charCount="612" wordCount="95" paraCount="10" cursorPos="768"/>
<name status="New" import="New" exported="True">New Scene</name> <name status="sa3b179" import="i466852" exported="True">New Scene</name>
</item> </item>
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="1" type="ROOT" class="PLOT"> <item handle="44cb730c42048" parent="None" root="44cb730c42048" order="1" type="ROOT" class="PLOT">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="New">Plot</name> <name status="sa3b179" import="i466852">Plot</name>
</item> </item>
<item handle="031b4af5197ec" parent="44cb730c42048" root="44cb730c42048" order="0" type="FILE" class="PLOT" layout="NOTE"> <item handle="031b4af5197ec" parent="44cb730c42048" root="44cb730c42048" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta charCount="48" wordCount="10" paraCount="1" cursorPos="69"/> <meta charCount="48" wordCount="10" paraCount="1" cursorPos="69"/>
<name status="New" import="None" exported="True">New File</name> <name status="sa3b179" import="None" exported="True">New File</name>
</item> </item>
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="2" type="ROOT" class="CHARACTER"> <item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="2" type="ROOT" class="CHARACTER">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="New">Characters</name> <name status="sa3b179" import="i466852">Characters</name>
</item> </item>
<item handle="1a6562590ef19" parent="71ee45a3c0db9" root="71ee45a3c0db9" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="1a6562590ef19" parent="71ee45a3c0db9" root="71ee45a3c0db9" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta charCount="34" wordCount="8" paraCount="1" cursorPos="51"/> <meta charCount="34" wordCount="8" paraCount="1" cursorPos="51"/>
<name status="New" import="None" exported="True">New File</name> <name status="sa3b179" import="None" exported="True">New File</name>
</item> </item>
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="3" type="ROOT" class="WORLD"> <item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="3" type="ROOT" class="WORLD">
<meta expanded="True"/> <meta expanded="True"/>
<name status="New" import="New">World</name> <name status="sa3b179" import="i466852">World</name>
</item> </item>
<item handle="41cfc0d1f2d12" parent="811786ad1ae74" root="811786ad1ae74" order="0" type="FILE" class="WORLD" layout="NOTE"> <item handle="41cfc0d1f2d12" parent="811786ad1ae74" root="811786ad1ae74" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta charCount="51" wordCount="9" paraCount="1" cursorPos="68"/> <meta charCount="51" wordCount="9" paraCount="1" cursorPos="68"/>
<name status="New" import="None" exported="True">New File</name> <name status="sa3b179" import="None" exported="True">New File</name>
</item> </item>
<item handle="2fca346db6561" parent="None" root="2fca346db6561" order="4" type="TRASH" class="TRASH"> <item handle="2fca346db6561" parent="None" root="2fca346db6561" order="4" type="TRASH" class="TRASH">
<meta expanded="True"/> <meta expanded="True"/>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:00:21"> <novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 12:48:08">
<project> <project>
<name>New Project</name> <name>New Project</name>
<title></title> <title></title>
@@ -27,50 +27,50 @@
<section></section> <section></section>
</titleFormat> </titleFormat>
<status> <status>
<entry red="100" green="100" blue="100">New</entry> <entry key="sa3b179" count="5" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Note</entry> <entry key="s1c8031" count="0" red="200" green="50" blue="0">Note</entry>
<entry red="200" green="150" blue="0">Draft</entry> <entry key="s06671a" count="0" red="200" green="150" blue="0">Draft</entry>
<entry red="50" green="200" blue="0">Finished</entry> <entry key="sbdd640" count="0" red="50" green="200" blue="0">Finished</entry>
</status> </status>
<importance> <importance>
<entry red="100" green="100" blue="100">New</entry> <entry key="i466852" count="0" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Minor</entry> <entry key="i3eb13b" count="0" red="200" green="50" blue="0">Minor</entry>
<entry red="200" green="150" blue="0">Major</entry> <entry key="i392456" count="0" red="200" green="150" blue="0">Major</entry>
<entry red="50" green="200" blue="0">Main</entry> <entry key="i23b8c1" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="8"> <content count="8">
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL"> <item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Novel</name> <name status="sa3b179" import="None">Novel</name>
</item> </item>
<item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/> <meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Title Page</name> <name status="sa3b179" import="None" exported="True">Title Page</name>
</item> </item>
<item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="1" type="FOLDER" class="NOVEL"> <item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="1" type="FOLDER" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">New Chapter</name> <name status="sa3b179" import="None">New Chapter</name>
</item> </item>
<item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/> <meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">New Chapter</name> <name status="sa3b179" import="None" exported="True">New Chapter</name>
</item> </item>
<item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="9" wordCount="2" paraCount="0" cursorPos="0"/> <meta charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">New Scene</name> <name status="sa3b179" import="None" exported="True">New Scene</name>
</item> </item>
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="1" type="ROOT" class="PLOT"> <item handle="44cb730c42048" parent="None" root="44cb730c42048" order="1" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Plot</name> <name status="sa3b179" import="None">Plot</name>
</item> </item>
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="2" type="ROOT" class="CHARACTER"> <item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="2" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Characters</name> <name status="sa3b179" import="None">Characters</name>
</item> </item>
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="3" type="ROOT" class="WORLD"> <item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="3" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">World</name> <name status="sa3b179" import="None">World</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:00:18"> <novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 18:15:12">
<project> <project>
<name>Project Name</name> <name>Project Name</name>
<title>Project Title</title> <title>Project Title</title>
@@ -23,7 +23,7 @@
<autoReplace> <autoReplace>
<entry key="A">B</entry> <entry key="A">B</entry>
<entry key="C">D</entry> <entry key="C">D</entry>
<entry key="This">With This Stuff </entry> <entry key="This">With This Stuff</entry>
</autoReplace> </autoReplace>
<titleFormat> <titleFormat>
<title>%title%</title> <title>%title%</title>
@@ -33,50 +33,50 @@
<section></section> <section></section>
</titleFormat> </titleFormat>
<status> <status>
<entry red="100" green="100" blue="100">New</entry> <entry key="sa3b179" count="5" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Note</entry> <entry key="s1c8031" count="0" red="200" green="50" blue="0">Note</entry>
<entry red="50" green="200" blue="0">Finished</entry> <entry key="sbdd640" count="0" red="50" green="200" blue="0">Finished</entry>
<entry red="20" green="30" blue="40">Final</entry> <entry key="sbc8960" count="0" red="20" green="30" blue="40">Final</entry>
</status> </status>
<importance> <importance>
<entry red="100" green="100" blue="100">New</entry> <entry key="i466852" count="0" red="100" green="100" blue="100">New</entry>
<entry red="200" green="50" blue="0">Minor</entry> <entry key="i3eb13b" count="0" red="200" green="50" blue="0">Minor</entry>
<entry red="200" green="150" blue="0">Major</entry> <entry key="i392456" count="0" red="200" green="150" blue="0">Major</entry>
<entry red="0" green="0" blue="0">Final</entry> <entry key="i1a3d1f" count="0" red="100" green="100" blue="100">Final</entry>
</importance> </importance>
</settings> </settings>
<content count="8"> <content count="8">
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL"> <item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Novel</name> <name status="sa3b179" import="None">Novel</name>
</item> </item>
<item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/> <meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">Title Page</name> <name status="sa3b179" import="None" exported="True">Title Page</name>
</item> </item>
<item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="1" type="FOLDER" class="NOVEL"> <item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="1" type="FOLDER" class="NOVEL">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">New Chapter</name> <name status="sa3b179" import="None">New Chapter</name>
</item> </item>
<item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/> <meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">New Chapter</name> <name status="sa3b179" import="None" exported="True">New Chapter</name>
</item> </item>
<item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta charCount="9" wordCount="2" paraCount="0" cursorPos="0"/> <meta charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="New" import="None" exported="True">New Scene</name> <name status="sa3b179" import="None" exported="True">New Scene</name>
</item> </item>
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="1" type="ROOT" class="PLOT"> <item handle="44cb730c42048" parent="None" root="44cb730c42048" order="1" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Plot</name> <name status="sa3b179" import="None">Plot</name>
</item> </item>
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="2" type="ROOT" class="CHARACTER"> <item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="2" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">Characters</name> <name status="sa3b179" import="None">Characters</name>
</item> </item>
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="3" type="ROOT" class="WORLD"> <item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="3" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="False"/>
<name status="New" import="None">World</name> <name status="sa3b179" import="None">World</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
+24 -3
View File
@@ -31,9 +31,9 @@ from novelwriter.guimain import GuiMain
from novelwriter.common import ( from novelwriter.common import (
checkString, checkInt, checkFloat, checkBool, checkHandle, isHandle, checkString, checkInt, checkFloat, checkBool, checkHandle, isHandle,
isTitleTag, isItemClass, isItemType, isItemLayout, hexToInt, checkIntRange, isTitleTag, isItemClass, isItemType, isItemLayout, hexToInt, checkIntRange,
checkIntTuple, formatInt, formatTimeStamp, formatTime, splitVersionNumber, minmax, checkIntTuple, formatInt, formatTimeStamp, formatTime, simplified,
transferCase, fuzzyTime, numberToRoman, jsonEncode, readTextFile, splitVersionNumber, transferCase, fuzzyTime, numberToRoman, jsonEncode,
makeFileNameSafe, sha256sum, getGuiItem, NWConfigParser readTextFile, makeFileNameSafe, sha256sum, getGuiItem, NWConfigParser
) )
@@ -226,6 +226,16 @@ def testBaseCommon_CheckIntRange():
# END Test 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 @pytest.mark.base
def testBaseCommon_CheckIntTuple(): def testBaseCommon_CheckIntTuple():
"""Test the checkIntTuple function. """Test the checkIntTuple function.
@@ -270,6 +280,17 @@ def testBaseCommon_FormatTime():
# END Test 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 @pytest.mark.base
def testBaseCommon_SplitVersionNumber(): def testBaseCommon_SplitVersionNumber():
"""Test the splitVersionNumber function. """Test the splitVersionNumber function.
+37 -44
View File
@@ -20,6 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import pytest import pytest
import random
from lxml import etree from lxml import etree
@@ -31,9 +32,10 @@ from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
@pytest.mark.core @pytest.mark.core
def testCoreItem_Setters(mockGUI): def testCoreItem_Setters(mockGUI, constData):
"""Test all the simple setters for the NWItem class. """Test all the simple setters for the NWItem class.
""" """
random.seed(42)
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theItem = NWItem(theProject) theItem = NWItem(theProject)
@@ -42,6 +44,8 @@ def testCoreItem_Setters(mockGUI):
assert theItem.itemName == "A Name" assert theItem.itemName == "A Name"
theItem.setName("\t A Name ") theItem.setName("\t A Name ")
assert theItem.itemName == "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) theItem.setName(123)
assert theItem.itemName == "" assert theItem.itemName == ""
@@ -89,50 +93,32 @@ def testCoreItem_Setters(mockGUI):
# Importance # Importance
theItem._class = nwItemClass.CHARACTER theItem._class = nwItemClass.CHARACTER
theItem.setImport("Nonsense") theItem.setImport("Word")
assert theItem.itemImport == "New" assert theItem.itemImport == constData.importKeys[0] # Default
theItem.setImport("New") for key in constData.importKeys:
assert theItem.itemImport == "New" theItem.setImport(key)
theItem.setImport("Minor") assert theItem.itemImport == key
assert theItem.itemImport == "Minor"
theItem.setImport("Major")
assert theItem.itemImport == "Major"
theItem.setImport("Main")
assert theItem.itemImport == "Main"
# Status # Status
theItem._class = nwItemClass.NOVEL theItem._class = nwItemClass.NOVEL
theItem.setStatus("Nonsense") theItem.setStatus("Word")
assert theItem.itemStatus == "New" assert theItem.itemStatus == constData.statusKeys[0] # Default
theItem.setStatus("New") for key in constData.statusKeys:
assert theItem.itemStatus == "New" theItem.setStatus(key)
theItem.setStatus("Note") assert theItem.itemStatus == key
assert theItem.itemStatus == "Note"
theItem.setStatus("Draft")
assert theItem.itemStatus == "Draft"
theItem.setStatus("Finished")
assert theItem.itemStatus == "Finished"
# Status/Importance Wrapper # Status/Importance Wrapper
theItem._class = nwItemClass.CHARACTER theItem._class = nwItemClass.CHARACTER
theItem.setImportStatus("New") for key in constData.importKeys:
assert theItem.itemImport == "New" theItem.setImport(key)
theItem.setImportStatus("Minor") assert theItem.itemImport == key
assert theItem.itemImport == "Minor" assert theItem.itemStatus == constData.statusKeys[3] # Should not change
theItem.setImportStatus("Note")
assert theItem.itemImport == "New"
theItem.setImportStatus("Draft")
assert theItem.itemImport == "New"
theItem._class = nwItemClass.NOVEL theItem._class = nwItemClass.NOVEL
theItem.setImportStatus("New") for key in constData.statusKeys:
assert theItem.itemStatus == "New" theItem.setStatus(key)
theItem.setImportStatus("Minor") assert theItem.itemImport == constData.importKeys[3] # Should not change
assert theItem.itemStatus == "New" assert theItem.itemStatus == key
theItem.setImportStatus("Note")
assert theItem.itemStatus == "Note"
theItem.setImportStatus("Draft")
assert theItem.itemStatus == "Draft"
# Expanded # Expanded
theItem.setExpanded(8) theItem.setExpanded(8)
@@ -366,9 +352,10 @@ def testCoreItem_LayoutSetter(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreItem_XMLPackUnpack(mockGUI, caplog): def testCoreItem_XMLPackUnpack(mockGUI, caplog, constData):
"""Test packing and unpacking XML objects for the NWItem class. """Test packing and unpacking XML objects for the NWItem class.
""" """
random.seed(42)
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
nwXML = etree.Element("novelWriterXML") nwXML = etree.Element("novelWriterXML")
@@ -383,7 +370,7 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
theItem.setName("A Name") theItem.setName("A Name")
theItem.setClass("NOVEL") theItem.setClass("NOVEL")
theItem.setType("FILE") theItem.setType("FILE")
theItem.setStatus("Main") theItem.setImport(constData.importKeys[3])
theItem.setLayout("NOTE") theItem.setLayout("NOTE")
theItem.setExported(False) theItem.setExported(False)
theItem.setParaCount(3) theItem.setParaCount(3)
@@ -398,15 +385,16 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
b'<content>' b'<content>'
b'<item handle="0123456789abc" parent="0123456789abc" root="0123456789abc" order="1" ' b'<item handle="0123456789abc" parent="0123456789abc" root="0123456789abc" order="1" '
b'type="FILE" class="NOVEL" layout="NOTE"><meta charCount="7" wordCount="5" paraCount="3" ' b'type="FILE" class="NOVEL" layout="NOTE"><meta charCount="7" wordCount="5" paraCount="3" '
b'cursorPos="11"/><name status="New" import="None" exported="False">A Name</name></item>' b'cursorPos="11"/><name status="None" import="%s" exported="False">A Name</name></item>'
b'</content>' b'</content>'
) ) % bytes(constData.importKeys[3], encoding="utf8")
# Unpack # Unpack
theItem = NWItem(theProject) theItem = NWItem(theProject)
assert theItem.unpackXML(xContent[0]) assert theItem.unpackXML(xContent[0])
assert theItem.itemHandle == "0123456789abc" assert theItem.itemHandle == "0123456789abc"
assert theItem.itemParent == "0123456789abc" assert theItem.itemParent == "0123456789abc"
assert theItem.itemRoot == "0123456789abc"
assert theItem.itemOrder == 1 assert theItem.itemOrder == 1
assert theItem.isExported is False assert theItem.isExported is False
assert theItem.paraCount == 3 assert theItem.paraCount == 3
@@ -416,6 +404,8 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
assert theItem.itemClass == nwItemClass.NOVEL assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FILE assert theItem.itemType == nwItemType.FILE
assert theItem.itemLayout == nwItemLayout.NOTE assert theItem.itemLayout == nwItemLayout.NOTE
assert theItem.itemStatus == constData.statusKeys[0] # Was None, should now be default
assert theItem.itemImport == constData.importKeys[3]
# Folder # Folder
# ====== # ======
@@ -428,7 +418,7 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
theItem.setName("A Name") theItem.setName("A Name")
theItem.setClass("NOVEL") theItem.setClass("NOVEL")
theItem.setType("FOLDER") theItem.setType("FOLDER")
theItem.setStatus("Main") theItem.setStatus(constData.statusKeys[1])
theItem.setLayout("NOTE") theItem.setLayout("NOTE")
theItem.setExpanded(True) theItem.setExpanded(True)
theItem.setExported(False) theItem.setExported(False)
@@ -443,16 +433,17 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == ( assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
b'<content>' b'<content>'
b'<item handle="0123456789abc" parent="0123456789abc" root="0123456789abc" order="1" ' b'<item handle="0123456789abc" parent="0123456789abc" root="0123456789abc" order="1" '
b'type="FOLDER" class="NOVEL"><meta expanded="True"/><name status="New" ' b'type="FOLDER" class="NOVEL"><meta expanded="True"/><name status="%s" '
b'import="None">A Name</name></item>' b'import="None">A Name</name></item>'
b'</content>' b'</content>'
) ) % bytes(constData.statusKeys[1], encoding="utf8")
# Unpack # Unpack
theItem = NWItem(theProject) theItem = NWItem(theProject)
assert theItem.unpackXML(xContent[0]) assert theItem.unpackXML(xContent[0])
assert theItem.itemHandle == "0123456789abc" assert theItem.itemHandle == "0123456789abc"
assert theItem.itemParent == "0123456789abc" assert theItem.itemParent == "0123456789abc"
assert theItem.itemRoot == "0123456789abc"
assert theItem.itemOrder == 1 assert theItem.itemOrder == 1
assert theItem.isExpanded is True assert theItem.isExpanded is True
assert theItem.isExported is True assert theItem.isExported is True
@@ -463,6 +454,8 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog):
assert theItem.itemClass == nwItemClass.NOVEL assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FOLDER assert theItem.itemType == nwItemType.FOLDER
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT 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 # Errors
# ====== # ======
+141 -69
View File
@@ -19,8 +19,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import pytest
import os import os
import pytest
import random
from shutil import copyfile from shutil import copyfile
from zipfile import ZipFile from zipfile import ZipFile
@@ -44,6 +45,7 @@ def testCoreProject_NewMinimal(fncDir, outDir, refDir, mockGUI):
testFile = os.path.join(outDir, "coreProject_NewMinimal_nwProject.nwx") testFile = os.path.join(outDir, "coreProject_NewMinimal_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewMinimal_nwProject.nwx") compFile = os.path.join(refDir, "coreProject_NewMinimal_nwProject.nwx")
random.seed(42)
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
@@ -112,6 +114,7 @@ def testCoreProject_NewCustomA(fncDir, outDir, refDir, mockGUI):
"numScenes": 3, "numScenes": 3,
"chFolders": True, "chFolders": True,
} }
random.seed(42)
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
@@ -154,6 +157,7 @@ def testCoreProject_NewCustomB(fncDir, outDir, refDir, mockGUI):
"numScenes": 6, "numScenes": 6,
"chFolders": True, "chFolders": True,
} }
random.seed(42)
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
@@ -262,6 +266,7 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI):
testFile = os.path.join(outDir, "coreProject_NewRoot_nwProject.nwx") testFile = os.path.join(outDir, "coreProject_NewRoot_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewRoot_nwProject.nwx") compFile = os.path.join(refDir, "coreProject_NewRoot_nwProject.nwx")
random.seed(42)
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
@@ -299,6 +304,7 @@ def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI):
testFile = os.path.join(outDir, "coreProject_NewFile_nwProject.nwx") testFile = os.path.join(outDir, "coreProject_NewFile_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewFile_nwProject.nwx") compFile = os.path.join(refDir, "coreProject_NewFile_nwProject.nwx")
random.seed(42)
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
@@ -680,13 +686,127 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
@pytest.mark.core @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. """Test other project class methods and functions.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
assert theProject.openProject(nwMinimal) assert theProject.newProject({"projPath": fncDir}) is True
assert theProject.projPath == nwMinimal
# Setting project path # Setting project path
assert theProject.setProjectPath(None) assert theProject.setProjectPath(None)
@@ -697,16 +817,16 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
assert theProject.projPath == os.path.expanduser("~") assert theProject.projPath == os.path.expanduser("~")
# Create a new folder and populate it # 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) assert theProject.setProjectPath(projPath, newProject=True)
# Make os.mkdir fail # Make os.mkdir fail
monkeypatch.setattr("os.mkdir", causeOSError) monkeypatch.setattr("os.mkdir", causeOSError)
projPath = os.path.join(nwMinimal, "mock2") projPath = os.path.join(fncDir, "mock2")
assert not theProject.setProjectPath(projPath, newProject=True) assert not theProject.setProjectPath(projPath, newProject=True)
# Set back # Set back
assert theProject.setProjectPath(nwMinimal) assert theProject.setProjectPath(fncDir)
# Project Name # Project Name
assert theProject.setProjectName(" A Name ") assert theProject.setProjectName(" A Name ")
@@ -744,9 +864,10 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
# Trash folder # Trash folder
# Should create on first call, and just returned on later calls # Should create on first call, and just returned on later calls
assert theProject.projTree["73475cb40a568"] is None hTrash = "1a6562590ef19"
assert theProject.trashFolder() == "73475cb40a568" assert theProject.projTree[hTrash] is None
assert theProject.trashFolder() == "73475cb40a568" assert theProject.trashFolder() == hTrash
assert theProject.trashFolder() == hTrash
# Project backup # Project backup
assert theProject.doBackup is True assert theProject.doBackup is True
@@ -770,9 +891,10 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
# Spell language # Spell language
theProject.projChanged = False theProject.projChanged = False
assert theProject.setSpellLang(None)
assert theProject.projSpell is 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.projSpell is None
assert theProject.setSpellLang("en_GB") assert theProject.setSpellLang("en_GB")
assert theProject.projSpell == "en_GB" assert theProject.projSpell == "en_GB"
@@ -812,14 +934,14 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
# Change project tree order # Change project tree order
oldOrder = [ oldOrder = [
"a508bb932959c", "a35baf2e93843", "a6d311a93600a", "73475cb40a568", "44cb730c42048", "71ee45a3c0db9",
"f5ab3e30151e1", "8c659a11cd429", "7695ce551d265", "811786ad1ae74", "25fc0e7096fc6", "31489056e0916",
"afb3043c7b2b3", "9d5247ab588e0", "73475cb40a568", "98010bd9270f9", "0e17daca5f3e1", "1a6562590ef19",
] ]
newOrder = [ newOrder = [
"f5ab3e30151e1", "8c659a11cd429", "7695ce551d265", "811786ad1ae74", "25fc0e7096fc6", "31489056e0916",
"a508bb932959c", "a35baf2e93843", "a6d311a93600a", "73475cb40a568", "44cb730c42048", "71ee45a3c0db9",
"afb3043c7b2b3", "9d5247ab588e0", "98010bd9270f9", "0e17daca5f3e1",
] ]
assert theProject.projTree.handles() == oldOrder assert theProject.projTree.handles() == oldOrder
assert theProject.setTreeOrder(newOrder) assert theProject.setTreeOrder(newOrder)
@@ -827,56 +949,6 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
assert theProject.setTreeOrder(oldOrder) assert theProject.setTreeOrder(oldOrder)
assert theProject.projTree.handles() == 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 # Session stats
theProject.currWCount = 200 theProject.currWCount = 200
theProject.lastWCount = 100 theProject.lastWCount = 100
@@ -890,7 +962,7 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
assert not theProject._appendSessionStats(idleTime=0) assert not theProject._appendSessionStats(idleTime=0)
# Write entry # 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) statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS)
theProject.projOpened = 1600002000 theProject.projOpened = 1600002000
+290 -86
View File
@@ -20,6 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import pytest import pytest
import random
from lxml import etree from lxml import etree
@@ -29,103 +30,300 @@ from novelwriter.core.status import NWStatus
@pytest.mark.core @pytest.mark.core
def testCoreStatus_Entries(): def testCoreStatus_Internal(constData):
"""Test all the simple setters for the NWItem class. """Test all the internal functions of the NWStatus class.
""" """
theStatus = NWStatus() random.seed(42)
theStatus = NWStatus(NWStatus.STATUS)
theImport = NWStatus(NWStatus.IMPORT)
# Add entries with pytest.raises(Exception):
theStatus.addEntry("New", (100, 100, 100)) NWStatus(999)
theStatus.addEntry("Minor", (200, 50, 0))
theStatus.addEntry("Major", (200, 150, 0))
theStatus.addEntry("Main", (50, 200, 0))
assert theStatus._theLabels == ["New", "Minor", "Major", "Main"] # Generate Key
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
# Lookups assert theStatus._newKey() == constData.statusKeys[0]
assert theStatus._getIndex(None) is None assert theStatus._newKey() == constData.statusKeys[1]
assert theStatus._getIndex("stuff") is None
assert theStatus._getIndex("Main") == 3
# Checks # Key collision, should move to key 3
assert theStatus.checkEntry(123) == "New" theStatus.write(constData.statusKeys[2], "Crash", (0, 0, 0))
assert theStatus.checkEntry("Stuff") == "New" assert theStatus._newKey() == constData.statusKeys[3]
assert theStatus.checkEntry("New ") == "New"
assert theStatus.checkEntry(" Main ") == "Main"
# Icons assert theImport._newKey() == constData.importKeys[0]
assert isinstance(theStatus.getIcon("Stuff"), QIcon) assert theImport._newKey() == constData.importKeys[1]
assert isinstance(theStatus.getIcon("New"), QIcon)
# Set new list # Key collision, should move to key 3
newList = [ theImport.write(constData.importKeys[2], "Crash", (0, 0, 0))
("New", 1, 1, 1, "New"), assert theImport._newKey() == constData.importKeys[3]
("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"}
assert theStatus._theLabels == ["New", "Minor", "Major", "Min", "Max"] # Check Key
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
# Add counts assert theStatus._isKey(None) is False # Not a string
countTo = [3, 5, 7, 9, 11] assert theStatus._isKey("s00000") is False # Too short
for i, n in enumerate(countTo): assert theStatus._isKey("s000000") is True # Correct length
for _ in range(n): assert theStatus._isKey("s0000000") is False # Too long
theStatus.countEntry(theStatus._theLabels[i]) assert theStatus._isKey("i000000") is False # Wrong type
assert theStatus._theCounts == countTo 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 # Iterate
for i, (sA, sB, sC, sD) in enumerate(theStatus): entries = list(theStatus)
assert sA == theStatus._theLabels[i] assert len(entries) == 4
assert sB == theStatus._theColours[i] assert len(theStatus) == 4
assert sC == theStatus._theCounts[i]
assert sD == theStatus._theIcons[i]
sA, sB, sC, sD = theStatus[9] # Keys
assert sA is None assert list(theStatus.keys()) == constData.statusKeys
assert sB is None
assert sC is None # Items
assert isinstance(sD, QIcon) 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() 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
# 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
# =======
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 # END Test testCoreStatus_Entries
@pytest.mark.core @pytest.mark.core
def testCoreStatus_XMLPackUnpack(): def testCoreStatus_XMLPackUnpack(constData):
"""Test all the simple setters for the NWItem class. """Test all the XML pack/unpack of the NWStatus class.
""" """
theStatus = NWStatus() random.seed(42)
theStatus.addEntry("New", (100, 100, 100)) theStatus = NWStatus(NWStatus.STATUS)
theStatus.addEntry("Minor", (200, 50, 0)) theStatus.write(None, "New", (100, 100, 100))
theStatus.addEntry("Major", (200, 150, 0)) theStatus.write(None, "Note", (200, 50, 0))
theStatus.addEntry("Main", (50, 200, 0)) theStatus.write(None, "Draft", (200, 150, 0))
theStatus.write(None, "Finished", (50, 200, 0))
countTo = [3, 5, 7, 9] countTo = [3, 5, 7, 9]
for i, n in enumerate(countTo): for i, n in enumerate(countTo):
for _ in range(n): for _ in range(n):
theStatus.countEntry(theStatus._theLabels[i]) theStatus.increment(constData.statusKeys[i])
nwXML = etree.Element("novelWriterXML") nwXML = etree.Element("novelWriterXML")
@@ -134,23 +332,29 @@ def testCoreStatus_XMLPackUnpack():
theStatus.packXML(xStatus) theStatus.packXML(xStatus)
assert etree.tostring(xStatus, pretty_print=False, encoding="utf-8") == ( assert etree.tostring(xStatus, pretty_print=False, encoding="utf-8") == (
b'<status>' b'<status>'
b'<entry red="100" green="100" blue="100">New</entry>' b'<entry key="sa3b179" count="3" red="100" green="100" blue="100">New</entry>'
b'<entry red="200" green="50" blue="0">Minor</entry>' b'<entry key="s1c8031" count="5" red="200" green="50" blue="0">Note</entry>'
b'<entry red="200" green="150" blue="0">Major</entry>' b'<entry key="s06671a" count="7" red="200" green="150" blue="0">Draft</entry>'
b'<entry red="50" green="200" blue="0">Main</entry>' b'<entry key="sbdd640" count="9" red="50" green="200" blue="0">Finished</entry>'
b'</status>' b'</status>'
) )
# Unpack # Unpack
theStatus = NWStatus() theStatus = NWStatus(NWStatus.STATUS)
assert theStatus.unpackXML(xStatus) assert theStatus.unpackXML(xStatus)
assert theStatus._theLabels == ["New", "Minor", "Major", "Main"] assert len(theStatus._store) == 4
assert theStatus._theColours == [(100, 100, 100), (200, 50, 0), (200, 150, 0), (50, 200, 0)] assert list(theStatus._store.keys()) == constData.statusKeys
assert theStatus._theCounts == [0, 0, 0, 0] assert theStatus._store[constData.statusKeys[0]]["name"] == "New"
assert theStatus._theMap["New"] == 0 assert theStatus._store[constData.statusKeys[1]]["name"] == "Note"
assert theStatus._theMap["Minor"] == 1 assert theStatus._store[constData.statusKeys[2]]["name"] == "Draft"
assert theStatus._theMap["Major"] == 2 assert theStatus._store[constData.statusKeys[3]]["name"] == "Finished"
assert theStatus._theMap["Main"] == 3 assert theStatus._store[constData.statusKeys[0]]["cols"] == (100, 100, 100)
assert theStatus._theLength == 4 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 # END Test testCoreStatus_XMLPackUnpack
+25 -11
View File
@@ -20,6 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import pytest import pytest
import random
from tools import getGuiItem from tools import getGuiItem
@@ -88,7 +89,7 @@ def testDlgItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncProj):
@pytest.mark.gui @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. """Test the item editor dialog for a novel document.
""" """
# Block message box # Block message box
@@ -96,9 +97,13 @@ def testDlgItemEditor_Novel(qtbot, monkeypatch, nwGUI, fncProj):
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True) monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create Project and Open Document # Create Project and Open Document
random.seed(42)
nwGUI.theProject.projTree.setSeed(42) nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj}) 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 # Check that an invalid handle is managed
itemEdit = GuiItemEditor(nwGUI, "whatever") itemEdit = GuiItemEditor(nwGUI, "whatever")
@@ -111,7 +116,7 @@ def testDlgItemEditor_Novel(qtbot, monkeypatch, nwGUI, fncProj):
# Check Existing Settings # Check Existing Settings
assert itemEdit.editName.text() == "New Scene" 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.editLayout.currentData() == nwItemLayout.DOCUMENT
assert itemEdit.editExport.isChecked() is True assert itemEdit.editExport.isChecked() is True
@@ -125,7 +130,7 @@ def testDlgItemEditor_Novel(qtbot, monkeypatch, nwGUI, fncProj):
# Check New Settings # Check New Settings
itemEdit._doSave() itemEdit._doSave()
assert itemEdit.theItem.itemName == "Great Scene" 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.itemLayout == nwItemLayout.NOTE
assert itemEdit.theItem.isExported is False assert itemEdit.theItem.isExported is False
@@ -141,7 +146,7 @@ def testDlgItemEditor_Novel(qtbot, monkeypatch, nwGUI, fncProj):
@pytest.mark.gui @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. """Test the item editor dialog for a project note.
""" """
# Block message box # Block message box
@@ -149,8 +154,13 @@ def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj):
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True) monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create Project and Open Document # Create Project and Open Document
random.seed(42)
nwGUI.theProject.projTree.setSeed(42) nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj}) 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 # Create Note
nwGUI.treeView.clearSelection() nwGUI.treeView.clearSelection()
@@ -166,7 +176,7 @@ def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj):
# Check Existing Settings # Check Existing Settings
assert itemEdit.editName.text() == "New File" 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.editLayout.currentData() == nwItemLayout.NOTE
assert itemEdit.editExport.isChecked() is True assert itemEdit.editExport.isChecked() is True
@@ -178,8 +188,8 @@ def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj):
# Check New Settings # Check New Settings
assert itemEdit.theItem.itemName == "New Character" assert itemEdit.theItem.itemName == "New Character"
assert itemEdit.theItem.itemStatus == "New" assert itemEdit.theItem.itemStatus == constData.statusKeys[0]
assert itemEdit.theItem.itemImport == "Minor" assert itemEdit.theItem.itemImport == constData.importKeys[1]
assert itemEdit.theItem.itemLayout == nwItemLayout.NOTE assert itemEdit.theItem.itemLayout == nwItemLayout.NOTE
assert itemEdit.theItem.isExported is False assert itemEdit.theItem.isExported is False
@@ -191,7 +201,7 @@ def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj):
@pytest.mark.gui @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. """Test the item editor dialog for a folder.
""" """
# Block message box # Block message box
@@ -199,6 +209,7 @@ def testDlgItemEditor_Folder(qtbot, monkeypatch, nwGUI, fncProj):
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True) monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create Project and Open Document # Create Project and Open Document
random.seed(42)
nwGUI.theProject.projTree.setSeed(42) nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj}) assert nwGUI.newProject({"projPath": fncProj})
@@ -206,9 +217,12 @@ def testDlgItemEditor_Folder(qtbot, monkeypatch, nwGUI, fncProj):
itemEdit = GuiItemEditor(nwGUI, "31489056e0916") itemEdit = GuiItemEditor(nwGUI, "31489056e0916")
itemEdit.show() itemEdit.show()
assert nwGUI.theProject.statusItems.name(constData.statusKeys[0]) == "New"
assert nwGUI.theProject.statusItems.name(constData.statusKeys[1]) == "Note"
# Check Existing Settings # Check Existing Settings
assert itemEdit.editName.text() == "New Chapter" 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.editLayout.currentData() == nwItemLayout.NO_LAYOUT
assert itemEdit.editExport.isChecked() is False assert itemEdit.editExport.isChecked() is False
@@ -222,7 +236,7 @@ def testDlgItemEditor_Folder(qtbot, monkeypatch, nwGUI, fncProj):
# Check New Settings # Check New Settings
itemEdit._doSave() itemEdit._doSave()
assert itemEdit.theItem.itemName == "Chapter One" 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.itemLayout == nwItemLayout.NO_LAYOUT
assert itemEdit.theItem.isExported is False assert itemEdit.theItem.isExported is False
+2 -1
View File
@@ -42,7 +42,8 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
"""Test the load project wizard. """Test the load project wizard.
""" """
# Block message box # 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.openProject(nwMinimal)
assert nwGUI.closeProject() assert nwGUI.closeProject()
+55 -21
View File
@@ -19,17 +19,16 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import pytest
import os import os
import pytest
import random
from shutil import copyfile from shutil import copyfile
from tools import cmpFiles, getGuiItem from tools import cmpFiles, getGuiItem
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import QDialog, QAction, QMessageBox, QColorDialog
QDialog, QAction, QMessageBox, QColorDialog, QTreeWidgetItem
)
from novelwriter.dialogs import GuiProjectSettings from novelwriter.dialogs import GuiProjectSettings
@@ -39,7 +38,9 @@ stepDelay = 20
@pytest.mark.gui @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. """Test the full project settings dialog.
""" """
projFile = os.path.join(fncProj, "nwProject.nwx") projFile = os.path.join(fncProj, "nwProject.nwx")
@@ -55,6 +56,7 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDi
assert getGuiItem("GuiProjectSettings") is None assert getGuiItem("GuiProjectSettings") is None
# Create new project # Create new project
random.seed(42)
nwGUI.theProject.projTree.setSeed(42) nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj}) assert nwGUI.newProject({"projPath": fncProj})
nwGUI.mainConf.backupPath = fncDir nwGUI.mainConf.backupPath = fncDir
@@ -111,19 +113,9 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDi
projEdit._tabBox.setCurrentWidget(projEdit.tabStatus) projEdit._tabBox.setCurrentWidget(projEdit.tabStatus)
assert projEdit.tabStatus.colChanged is False assert projEdit.tabStatus.colChanged is False
assert projEdit.tabStatus.getNewList() is None assert projEdit.tabStatus.getNewList() == ([], [])
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4 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) # Can't delete the first item (it's in use)
projEdit.tabStatus.listBox.clearSelection() projEdit.tabStatus.listBox.clearSelection()
projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True) projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True)
@@ -150,11 +142,53 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDi
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
assert projEdit.tabStatus.colChanged is True assert projEdit.tabStatus.colChanged is True
assert projEdit.tabStatus.getNewList() == [ assert projEdit.tabStatus.getNewList() == (
("New", 100, 100, 100, "New"), [
("Note", 200, 50, 0, "Note"), {
("Finished", 50, 200, 0, "Finished"), "key": constData.statusKeys[0],
("Final", 20, 30, 40, None) "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
]
)
# 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 # Importance Tab
+1
View File
@@ -40,6 +40,7 @@ stepDelay = 20
def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal): def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal):
"""test the word list editor. """test the word list editor.
""" """
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiWordList, "exec_", lambda *a: None) monkeypatch.setattr(GuiWordList, "exec_", lambda *a: None)
+1
View File
@@ -43,6 +43,7 @@ def testGuiEditor_Init(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
""" """
# Block message box # Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
# Open project # Open project
assert nwGUI.openProject(nwMinimal) assert nwGUI.openProject(nwMinimal)
+3 -1
View File
@@ -19,8 +19,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import pytest
import os import os
import random
import pytest
from shutil import copyfile from shutil import copyfile
from tools import cmpFiles from tools import cmpFiles
@@ -139,6 +140,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True) monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
# Create new, save, close project # Create new, save, close project
random.seed(42)
nwGUI.theProject.projTree.setSeed(42) nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj}) assert nwGUI.newProject({"projPath": fncProj})
assert nwGUI.saveProject() assert nwGUI.saveProject()
+1
View File
@@ -35,6 +35,7 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
"""Test the theme and icon classes. """Test the theme and icon classes.
""" """
# Block message box # Block message box
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)