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