Allow multiple roots of same class (#1031)
This commit is contained in:
@@ -25,7 +25,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
|
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
|
||||||
|
|
||||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwOutline
|
from novelwriter.enum import nwItemClass, nwItemLayout, nwOutline
|
||||||
|
|
||||||
|
|
||||||
def trConst(tString):
|
def trConst(tString):
|
||||||
@@ -42,31 +42,12 @@ class nwConst():
|
|||||||
FMT_DSTAMP = "%Y-%m-%d" # Date only format
|
FMT_DSTAMP = "%Y-%m-%d" # Date only format
|
||||||
|
|
||||||
# Various Hard Limits
|
# Various Hard Limits
|
||||||
MAX_DEPTH = 30 # Maximum folder depth of a project
|
|
||||||
MAX_DOCSIZE = 5000000 # Maxium size of a single document
|
MAX_DOCSIZE = 5000000 # Maxium size of a single document
|
||||||
MAX_BUILDSIZE = 10000000 # Maxium size of a project build
|
MAX_BUILDSIZE = 10000000 # Maxium size of a project build
|
||||||
|
|
||||||
# END Class nwConst
|
# END Class nwConst
|
||||||
|
|
||||||
|
|
||||||
class nwLists():
|
|
||||||
"""Lists used for grouping various other constants.
|
|
||||||
"""
|
|
||||||
# Regular user-accessible item types
|
|
||||||
REG_TYPES = {nwItemType.ROOT, nwItemType.FOLDER, nwItemType.FILE}
|
|
||||||
|
|
||||||
# Item classes where the full list of novel layouts are allowed
|
|
||||||
CLS_NOVEL = {nwItemClass.NOVEL, nwItemClass.ARCHIVE}
|
|
||||||
|
|
||||||
# Item classes which do not require items to have same class
|
|
||||||
FREE_CLASS = {nwItemClass.ARCHIVE, nwItemClass.TRASH}
|
|
||||||
|
|
||||||
# Deprecated nwItemLayout entries
|
|
||||||
DEP_LAYOUT = ("TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE")
|
|
||||||
|
|
||||||
# END Class nwLists
|
|
||||||
|
|
||||||
|
|
||||||
class nwRegEx():
|
class nwRegEx():
|
||||||
|
|
||||||
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
|
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import logging
|
|||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
from novelwriter.enum import nwItemType, nwItemLayout
|
||||||
from novelwriter.error import logException
|
from novelwriter.error import logException
|
||||||
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode
|
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode
|
||||||
from novelwriter.core.document import NWDoc
|
from novelwriter.core.document import NWDoc
|
||||||
@@ -208,8 +208,6 @@ class NWIndex():
|
|||||||
text.
|
text.
|
||||||
"""
|
"""
|
||||||
theItem = self.theProject.projTree[tHandle]
|
theItem = self.theProject.projTree[tHandle]
|
||||||
theRoot = self.theProject.projTree.getRootItem(tHandle)
|
|
||||||
|
|
||||||
if theItem is None:
|
if theItem is None:
|
||||||
logger.info("Not indexing unknown item '%s'", tHandle)
|
logger.info("Not indexing unknown item '%s'", tHandle)
|
||||||
return False
|
return False
|
||||||
@@ -229,11 +227,8 @@ class NWIndex():
|
|||||||
if theItem.itemParent is None:
|
if theItem.itemParent is None:
|
||||||
logger.info("Not indexing orphaned item '%s'", tHandle)
|
logger.info("Not indexing orphaned item '%s'", tHandle)
|
||||||
return False
|
return False
|
||||||
if self.theProject.projTree.isTrashRoot(theItem.itemParent):
|
if theItem.isInactive():
|
||||||
logger.debug("Not indexing trash item '%s'", tHandle)
|
logger.debug("Not indexing inactive item '%s'", tHandle)
|
||||||
return False
|
|
||||||
if theRoot.itemClass == nwItemClass.ARCHIVE:
|
|
||||||
logger.debug("Not indexing archived item '%s'", tHandle)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
itemClass = theItem.itemClass
|
itemClass = theItem.itemClass
|
||||||
|
|||||||
+100
-37
@@ -31,7 +31,7 @@ from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
|||||||
from novelwriter.common import (
|
from novelwriter.common import (
|
||||||
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified
|
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified
|
||||||
)
|
)
|
||||||
from novelwriter.constants import nwLabels, nwLists, trConst
|
from novelwriter.constants import nwLabels, trConst
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -45,6 +45,7 @@ class NWItem():
|
|||||||
self._name = ""
|
self._name = ""
|
||||||
self._handle = None
|
self._handle = None
|
||||||
self._parent = None
|
self._parent = None
|
||||||
|
self._root = None
|
||||||
self._order = 0
|
self._order = 0
|
||||||
self._type = nwItemType.NO_TYPE
|
self._type = nwItemType.NO_TYPE
|
||||||
self._class = nwItemClass.NO_CLASS
|
self._class = nwItemClass.NO_CLASS
|
||||||
@@ -85,6 +86,10 @@ class NWItem():
|
|||||||
def itemParent(self):
|
def itemParent(self):
|
||||||
return self._parent
|
return self._parent
|
||||||
|
|
||||||
|
@property
|
||||||
|
def itemRoot(self):
|
||||||
|
return self._root
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def itemOrder(self):
|
def itemOrder(self):
|
||||||
return self._order
|
return self._order
|
||||||
@@ -147,6 +152,7 @@ class NWItem():
|
|||||||
itemAttrib = {}
|
itemAttrib = {}
|
||||||
itemAttrib["handle"] = str(self._handle)
|
itemAttrib["handle"] = str(self._handle)
|
||||||
itemAttrib["parent"] = str(self._parent)
|
itemAttrib["parent"] = str(self._parent)
|
||||||
|
itemAttrib["root"] = str(self._root)
|
||||||
itemAttrib["order"] = str(self._order)
|
itemAttrib["order"] = str(self._order)
|
||||||
itemAttrib["type"] = str(self._type.name)
|
itemAttrib["type"] = str(self._type.name)
|
||||||
itemAttrib["class"] = str(self._class.name)
|
itemAttrib["class"] = str(self._class.name)
|
||||||
@@ -188,6 +194,7 @@ class NWItem():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
self.setParent(xItem.attrib.get("parent", None))
|
self.setParent(xItem.attrib.get("parent", None))
|
||||||
|
self.setRoot(xItem.attrib.get("root", None))
|
||||||
self.setOrder(xItem.attrib.get("order", 0))
|
self.setOrder(xItem.attrib.get("order", 0))
|
||||||
self.setType(xItem.attrib.get("type", nwItemType.NO_TYPE))
|
self.setType(xItem.attrib.get("type", nwItemType.NO_TYPE))
|
||||||
self.setClass(xItem.attrib.get("class", nwItemClass.NO_CLASS))
|
self.setClass(xItem.attrib.get("class", nwItemClass.NO_CLASS))
|
||||||
@@ -249,7 +256,7 @@ class NWItem():
|
|||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Methods
|
# Lookup Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def describeMe(self, hLevel=None):
|
def describeMe(self, hLevel=None):
|
||||||
@@ -275,11 +282,26 @@ class NWItem():
|
|||||||
|
|
||||||
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
|
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
|
||||||
|
|
||||||
|
def isNovelLike(self):
|
||||||
|
"""Returns true if the item is of a novel-like class.
|
||||||
|
"""
|
||||||
|
return self._class in (nwItemClass.NOVEL, nwItemClass.ARCHIVE)
|
||||||
|
|
||||||
|
def documentAllowed(self):
|
||||||
|
"""Returns true if the item is allowed to be of document layout.
|
||||||
|
"""
|
||||||
|
return self._class in (nwItemClass.NOVEL, nwItemClass.ARCHIVE, nwItemClass.TRASH)
|
||||||
|
|
||||||
|
def isInactive(self):
|
||||||
|
"""Returns true if the item is in an inactive class.
|
||||||
|
"""
|
||||||
|
return self._class in (nwItemClass.NO_CLASS, nwItemClass.ARCHIVE, nwItemClass.TRASH)
|
||||||
|
|
||||||
def getImportStatus(self):
|
def getImportStatus(self):
|
||||||
"""Return the relevant importance or status label and icon for
|
"""Return the relevant importance or status label and icon for
|
||||||
the current item based on its class.
|
the current item based on its class.
|
||||||
"""
|
"""
|
||||||
if self._class in nwLists.CLS_NOVEL:
|
if self.isNovelLike():
|
||||||
stName = self.theProject.statusItems.name(self._status)
|
stName = self.theProject.statusItems.name(self._status)
|
||||||
stIcon = self.theProject.statusItems.icon(self._status)
|
stIcon = self.theProject.statusItems.icon(self._status)
|
||||||
else:
|
else:
|
||||||
@@ -287,14 +309,44 @@ class NWItem():
|
|||||||
stIcon = self.theProject.importItems.icon(self._import)
|
stIcon = self.theProject.importItems.icon(self._import)
|
||||||
return stName, stIcon
|
return stName, stIcon
|
||||||
|
|
||||||
def setImportStatus(self, theLabel):
|
##
|
||||||
|
# Special Setters
|
||||||
|
##
|
||||||
|
|
||||||
|
def setImportStatus(self, value):
|
||||||
"""Update the importance or status value based on class. This is
|
"""Update the importance or status value based on class. This is
|
||||||
a wrapper setter for setStatus and setImport.
|
a wrapper setter for setStatus and setImport.
|
||||||
"""
|
"""
|
||||||
if self._class in nwLists.CLS_NOVEL:
|
if self.isNovelLike():
|
||||||
self.setStatus(theLabel)
|
self.setStatus(value)
|
||||||
else:
|
else:
|
||||||
self.setImport(theLabel)
|
self.setImport(value)
|
||||||
|
return
|
||||||
|
|
||||||
|
def setClassDefaults(self, itemClass):
|
||||||
|
"""Set the default values based on the item's class and the
|
||||||
|
project settings.
|
||||||
|
"""
|
||||||
|
if self._parent is not None:
|
||||||
|
# Only update for child items
|
||||||
|
self.setClass(itemClass)
|
||||||
|
|
||||||
|
if self._layout == nwItemLayout.NO_LAYOUT:
|
||||||
|
# If no layout is set, pick one
|
||||||
|
if self.isNovelLike():
|
||||||
|
self._layout = nwItemLayout.DOCUMENT
|
||||||
|
else:
|
||||||
|
self._layout = nwItemLayout.NOTE
|
||||||
|
elif not self.documentAllowed():
|
||||||
|
# Change layout to note if it is not in an allowed folder
|
||||||
|
self._layout = nwItemLayout.NOTE
|
||||||
|
|
||||||
|
if self._status is None:
|
||||||
|
self.setStatus("New") # This forces a default value lookup
|
||||||
|
|
||||||
|
if self._import is None:
|
||||||
|
self.setImport("New") # This forces a default value lookup
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -310,26 +362,37 @@ class NWItem():
|
|||||||
self._name = ""
|
self._name = ""
|
||||||
return
|
return
|
||||||
|
|
||||||
def setHandle(self, tHandle):
|
def setHandle(self, handle):
|
||||||
"""Set the item handle, and ensure it is valid.
|
"""Set the item handle, and ensure it is valid.
|
||||||
"""
|
"""
|
||||||
if isHandle(tHandle):
|
if isHandle(handle):
|
||||||
self._handle = tHandle
|
self._handle = handle
|
||||||
else:
|
else:
|
||||||
self._handle = None
|
self._handle = None
|
||||||
return
|
return
|
||||||
|
|
||||||
def setParent(self, pHandle):
|
def setParent(self, handle):
|
||||||
"""Set the parent handle, and ensure it is valid.
|
"""Set the parent handle, and ensure it is valid.
|
||||||
"""
|
"""
|
||||||
if pHandle is None:
|
if handle is None:
|
||||||
self._parent = None
|
self._parent = None
|
||||||
elif isHandle(pHandle):
|
elif isHandle(handle):
|
||||||
self._parent = pHandle
|
self._parent = handle
|
||||||
else:
|
else:
|
||||||
self._parent = None
|
self._parent = None
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def setRoot(self, handle):
|
||||||
|
"""Set the root handle, and ensure it is valid.
|
||||||
|
"""
|
||||||
|
if handle is None:
|
||||||
|
self._root = None
|
||||||
|
elif isHandle(handle):
|
||||||
|
self._root = handle
|
||||||
|
else:
|
||||||
|
self._root = None
|
||||||
|
return
|
||||||
|
|
||||||
def setOrder(self, order):
|
def setOrder(self, order):
|
||||||
"""Set the item order, and ensure that it is valid. This value
|
"""Set the item order, and ensure that it is valid. This value
|
||||||
is purely a meta value, and not actually used by novelWriter at
|
is purely a meta value, and not actually used by novelWriter at
|
||||||
@@ -338,59 +401,59 @@ class NWItem():
|
|||||||
self._order = checkInt(order, 0)
|
self._order = checkInt(order, 0)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setType(self, itemType):
|
def setType(self, value):
|
||||||
"""Set the item type from either a proper nwItemType, or set it
|
"""Set the item type from either a proper nwItemType, or set it
|
||||||
from a string representing an nwItemType.
|
from a string representing an nwItemType.
|
||||||
"""
|
"""
|
||||||
if isinstance(itemType, nwItemType):
|
if isinstance(value, nwItemType):
|
||||||
self._type = itemType
|
self._type = value
|
||||||
elif isItemType(itemType):
|
elif isItemType(value):
|
||||||
self._type = nwItemType[itemType]
|
self._type = nwItemType[value]
|
||||||
else:
|
else:
|
||||||
logger.error("Unrecognised item type '%s'", itemType)
|
logger.error("Unrecognised item type '%s'", value)
|
||||||
self._type = nwItemType.NO_TYPE
|
self._type = nwItemType.NO_TYPE
|
||||||
return
|
return
|
||||||
|
|
||||||
def setClass(self, itemClass):
|
def setClass(self, value):
|
||||||
"""Set the item class from either a proper nwItemClass, or set
|
"""Set the item class from either a proper nwItemClass, or set
|
||||||
it from a string representing an nwItemClass.
|
it from a string representing an nwItemClass.
|
||||||
"""
|
"""
|
||||||
if isinstance(itemClass, nwItemClass):
|
if isinstance(value, nwItemClass):
|
||||||
self._class = itemClass
|
self._class = value
|
||||||
elif isItemClass(itemClass):
|
elif isItemClass(value):
|
||||||
self._class = nwItemClass[itemClass]
|
self._class = nwItemClass[value]
|
||||||
else:
|
else:
|
||||||
logger.error("Unrecognised item class '%s'", itemClass)
|
logger.error("Unrecognised item class '%s'", value)
|
||||||
self._class = nwItemClass.NO_CLASS
|
self._class = nwItemClass.NO_CLASS
|
||||||
return
|
return
|
||||||
|
|
||||||
def setLayout(self, itemLayout):
|
def setLayout(self, value):
|
||||||
"""Set the item layout from either a proper nwItemLayout, or set
|
"""Set the item layout from either a proper nwItemLayout, or set
|
||||||
it from a string representing an nwItemLayout.
|
it from a string representing an nwItemLayout.
|
||||||
"""
|
"""
|
||||||
if isinstance(itemLayout, nwItemLayout):
|
if isinstance(value, nwItemLayout):
|
||||||
self._layout = itemLayout
|
self._layout = value
|
||||||
elif isItemLayout(itemLayout):
|
elif isItemLayout(value):
|
||||||
self._layout = nwItemLayout[itemLayout]
|
self._layout = nwItemLayout[value]
|
||||||
elif itemLayout in nwLists.DEP_LAYOUT:
|
elif value in ("TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE"):
|
||||||
self._layout = nwItemLayout.DOCUMENT
|
self._layout = nwItemLayout.DOCUMENT
|
||||||
else:
|
else:
|
||||||
logger.error("Unrecognised item layout '%s'", itemLayout)
|
logger.error("Unrecognised item layout '%s'", value)
|
||||||
self._layout = nwItemLayout.NO_LAYOUT
|
self._layout = nwItemLayout.NO_LAYOUT
|
||||||
return
|
return
|
||||||
|
|
||||||
def setStatus(self, itemStatus):
|
def setStatus(self, value):
|
||||||
"""Set the item status by looking it up in the valid status
|
"""Set the item status by looking it up in the valid status
|
||||||
items of the current project.
|
items of the current project.
|
||||||
"""
|
"""
|
||||||
self._status = self.theProject.statusItems.check(itemStatus)
|
self._status = self.theProject.statusItems.check(value)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setImport(self, itemImport):
|
def setImport(self, value):
|
||||||
"""Set the item importance by looking it up in the valid import
|
"""Set the item importance by looking it up in the valid import
|
||||||
items of the current project.
|
items of the current project.
|
||||||
"""
|
"""
|
||||||
self._import = self.theProject.importItems.check(itemImport)
|
self._import = self.theProject.importItems.check(value)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setExpanded(self, state):
|
def setExpanded(self, state):
|
||||||
|
|||||||
+33
-37
@@ -46,14 +46,14 @@ from novelwriter.common import (
|
|||||||
checkString, checkBool, checkInt, isHandle, formatTimeStamp,
|
checkString, checkBool, checkInt, isHandle, formatTimeStamp,
|
||||||
makeFileNameSafe, hexToInt, simplified
|
makeFileNameSafe, hexToInt, simplified
|
||||||
)
|
)
|
||||||
from novelwriter.constants import nwLists, trConst, nwFiles, nwLabels
|
from novelwriter.constants import trConst, nwFiles, nwLabels
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class NWProject():
|
class NWProject():
|
||||||
|
|
||||||
FILE_VERSION = "1.4"
|
FILE_VERSION = "1.4" # The current project file format version
|
||||||
|
|
||||||
def __init__(self, theParent):
|
def __init__(self, theParent):
|
||||||
|
|
||||||
@@ -121,46 +121,34 @@ class NWProject():
|
|||||||
##
|
##
|
||||||
|
|
||||||
def newRoot(self, rootName, rootClass):
|
def newRoot(self, rootName, rootClass):
|
||||||
"""Add a new root item. These items are unique, except for item class
|
"""Add a new root item.
|
||||||
CUSTOM, and always have parent handle set to None.
|
|
||||||
"""
|
"""
|
||||||
if not self.projTree.checkRootUnique(rootClass):
|
|
||||||
self.theParent.makeAlert(self.tr("Duplicate root item detected."), nwAlert.ERROR)
|
|
||||||
return None
|
|
||||||
|
|
||||||
newItem = NWItem(self)
|
newItem = NWItem(self)
|
||||||
newItem.setName(rootName)
|
newItem.setName(rootName)
|
||||||
newItem.setType(nwItemType.ROOT)
|
newItem.setType(nwItemType.ROOT)
|
||||||
newItem.setClass(rootClass)
|
newItem.setClass(rootClass)
|
||||||
newItem.setStatus(0)
|
|
||||||
self.projTree.append(None, None, newItem)
|
self.projTree.append(None, None, newItem)
|
||||||
|
self.projTree.updateItemData(newItem.itemHandle)
|
||||||
return newItem.itemHandle
|
return newItem.itemHandle
|
||||||
|
|
||||||
def newFolder(self, folderName, folderClass, pHandle):
|
def newFolder(self, folderName, pHandle):
|
||||||
"""Add a new folder with a given name and class and parent item.
|
"""Add a new folder with a given name and parent item.
|
||||||
"""
|
"""
|
||||||
newItem = NWItem(self)
|
newItem = NWItem(self)
|
||||||
newItem.setName(folderName)
|
newItem.setName(folderName)
|
||||||
newItem.setType(nwItemType.FOLDER)
|
newItem.setType(nwItemType.FOLDER)
|
||||||
newItem.setClass(folderClass)
|
|
||||||
newItem.setStatus(0)
|
|
||||||
self.projTree.append(None, pHandle, newItem)
|
self.projTree.append(None, pHandle, newItem)
|
||||||
|
self.projTree.updateItemData(newItem.itemHandle)
|
||||||
return newItem.itemHandle
|
return newItem.itemHandle
|
||||||
|
|
||||||
def newFile(self, fileName, fileClass, pHandle):
|
def newFile(self, fileName, pHandle):
|
||||||
"""Add a new file with a given name and class, and set a layout
|
"""Add a new file with a given name and parent item.
|
||||||
based on the class. DOCUMENT for NOVEL, otherwise NOTE.
|
|
||||||
"""
|
"""
|
||||||
newItem = NWItem(self)
|
newItem = NWItem(self)
|
||||||
newItem.setName(fileName)
|
newItem.setName(fileName)
|
||||||
newItem.setType(nwItemType.FILE)
|
newItem.setType(nwItemType.FILE)
|
||||||
if fileClass == nwItemClass.NOVEL:
|
|
||||||
newItem.setLayout(nwItemLayout.DOCUMENT)
|
|
||||||
else:
|
|
||||||
newItem.setLayout(nwItemLayout.NOTE)
|
|
||||||
newItem.setClass(fileClass)
|
|
||||||
newItem.setStatus(0)
|
|
||||||
self.projTree.append(None, pHandle, newItem)
|
self.projTree.append(None, pHandle, newItem)
|
||||||
|
self.projTree.updateItemData(newItem.itemHandle)
|
||||||
return newItem.itemHandle
|
return newItem.itemHandle
|
||||||
|
|
||||||
def trashFolder(self):
|
def trashFolder(self):
|
||||||
@@ -173,6 +161,7 @@ class NWProject():
|
|||||||
newItem.setType(nwItemType.TRASH)
|
newItem.setType(nwItemType.TRASH)
|
||||||
newItem.setClass(nwItemClass.TRASH)
|
newItem.setClass(nwItemClass.TRASH)
|
||||||
self.projTree.append(None, None, newItem)
|
self.projTree.append(None, None, newItem)
|
||||||
|
self.projTree.updateItemData(newItem.itemHandle)
|
||||||
return newItem.itemHandle
|
return newItem.itemHandle
|
||||||
|
|
||||||
return trashHandle
|
return trashHandle
|
||||||
@@ -287,10 +276,10 @@ class NWProject():
|
|||||||
xHandle[2] = self.newRoot(self.tr("Plot"), nwItemClass.PLOT)
|
xHandle[2] = self.newRoot(self.tr("Plot"), nwItemClass.PLOT)
|
||||||
xHandle[3] = self.newRoot(self.tr("Characters"), nwItemClass.CHARACTER)
|
xHandle[3] = self.newRoot(self.tr("Characters"), nwItemClass.CHARACTER)
|
||||||
xHandle[4] = self.newRoot(self.tr("World"), nwItemClass.WORLD)
|
xHandle[4] = self.newRoot(self.tr("World"), nwItemClass.WORLD)
|
||||||
xHandle[5] = self.newFile(self.tr("Title Page"), nwItemClass.NOVEL, xHandle[1])
|
xHandle[5] = self.newFile(self.tr("Title Page"), xHandle[1])
|
||||||
xHandle[6] = self.newFolder(self.tr("New Chapter"), nwItemClass.NOVEL, xHandle[1])
|
xHandle[6] = self.newFolder(self.tr("New Chapter"), xHandle[1])
|
||||||
xHandle[7] = self.newFile(self.tr("New Chapter"), nwItemClass.NOVEL, xHandle[6])
|
xHandle[7] = self.newFile(self.tr("New Chapter"), xHandle[6])
|
||||||
xHandle[8] = self.newFile(self.tr("New Scene"), nwItemClass.NOVEL, xHandle[6])
|
xHandle[8] = self.newFile(self.tr("New Scene"), xHandle[6])
|
||||||
|
|
||||||
aDoc = NWDoc(self, xHandle[5])
|
aDoc = NWDoc(self, xHandle[5])
|
||||||
aDoc.writeDocument(titlePage)
|
aDoc.writeDocument(titlePage)
|
||||||
@@ -313,8 +302,7 @@ class NWProject():
|
|||||||
self.newRoot(trConst(nwLabels.CLASS_NAME[newRoot]), newRoot)
|
self.newRoot(trConst(nwLabels.CLASS_NAME[newRoot]), newRoot)
|
||||||
|
|
||||||
# Create a title page
|
# Create a title page
|
||||||
tHandle = self.newFile(self.tr("Title Page"), nwItemClass.NOVEL, nHandle)
|
tHandle = self.newFile(self.tr("Title Page"), nHandle)
|
||||||
self.projTree.setFileItemLayout(tHandle, nwItemLayout.DOCUMENT)
|
|
||||||
|
|
||||||
aDoc = NWDoc(self, tHandle)
|
aDoc = NWDoc(self, tHandle)
|
||||||
aDoc.writeDocument(titlePage)
|
aDoc.writeDocument(titlePage)
|
||||||
@@ -330,10 +318,9 @@ class NWProject():
|
|||||||
chTitle = self.tr("Chapter {0}").format(f"{ch+1:d}")
|
chTitle = self.tr("Chapter {0}").format(f"{ch+1:d}")
|
||||||
pHandle = nHandle
|
pHandle = nHandle
|
||||||
if chFolders:
|
if chFolders:
|
||||||
pHandle = self.newFolder(chTitle, nwItemClass.NOVEL, nHandle)
|
pHandle = self.newFolder(chTitle, nHandle)
|
||||||
|
|
||||||
cHandle = self.newFile(chTitle, nwItemClass.NOVEL, pHandle)
|
cHandle = self.newFile(chTitle, pHandle)
|
||||||
self.projTree.setFileItemLayout(cHandle, nwItemLayout.DOCUMENT)
|
|
||||||
|
|
||||||
aDoc = NWDoc(self, cHandle)
|
aDoc = NWDoc(self, cHandle)
|
||||||
aDoc.writeDocument("## %s\n\n" % chTitle)
|
aDoc.writeDocument("## %s\n\n" % chTitle)
|
||||||
@@ -342,7 +329,7 @@ class NWProject():
|
|||||||
if numScenes > 0:
|
if numScenes > 0:
|
||||||
for sc in range(numScenes):
|
for sc in range(numScenes):
|
||||||
scTitle = self.tr("Scene {0}").format(f"{ch+1:d}.{sc+1:d}")
|
scTitle = self.tr("Scene {0}").format(f"{ch+1:d}.{sc+1:d}")
|
||||||
sHandle = self.newFile(scTitle, nwItemClass.NOVEL, pHandle)
|
sHandle = self.newFile(scTitle, pHandle)
|
||||||
|
|
||||||
aDoc = NWDoc(self, sHandle)
|
aDoc = NWDoc(self, sHandle)
|
||||||
aDoc.writeDocument("### %s\n\n" % scTitle)
|
aDoc.writeDocument("### %s\n\n" % scTitle)
|
||||||
@@ -351,7 +338,7 @@ class NWProject():
|
|||||||
elif numScenes > 0:
|
elif numScenes > 0:
|
||||||
for sc in range(numScenes):
|
for sc in range(numScenes):
|
||||||
scTitle = self.tr("Scene {0}").format(f"{sc+1:d}")
|
scTitle = self.tr("Scene {0}").format(f"{sc+1:d}")
|
||||||
sHandle = self.newFile(scTitle, nwItemClass.NOVEL, nHandle)
|
sHandle = self.newFile(scTitle, nHandle)
|
||||||
|
|
||||||
aDoc = NWDoc(self, sHandle)
|
aDoc = NWDoc(self, sHandle)
|
||||||
aDoc.writeDocument("### %s\n\n" % scTitle)
|
aDoc.writeDocument("### %s\n\n" % scTitle)
|
||||||
@@ -481,8 +468,9 @@ class NWProject():
|
|||||||
# documents and one for project notes. Introduced in
|
# documents and one for project notes. Introduced in
|
||||||
# version 1.5.
|
# version 1.5.
|
||||||
# 1.4 : Introduces a more compact format for storing items. All
|
# 1.4 : Introduces a more compact format for storing items. All
|
||||||
# settings aside from name are now attributes. Introduced
|
# settings aside from name are now attributes. This format
|
||||||
# in version 1.7.
|
# also changes the way satus and importance labels are
|
||||||
|
# stored and handled. Introduced in version 1.7.
|
||||||
|
|
||||||
if fileVersion not in ("1.0", "1.1", "1.2", "1.3", "1.4"):
|
if fileVersion not in ("1.0", "1.1", "1.2", "1.3", "1.4"):
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
@@ -613,7 +601,13 @@ class NWProject():
|
|||||||
self.mainConf.updateRecentCache(self.projPath, self.projName, self.lastWCount, time())
|
self.mainConf.updateRecentCache(self.projPath, self.projName, self.lastWCount, time())
|
||||||
self.mainConf.saveRecentCache()
|
self.mainConf.saveRecentCache()
|
||||||
|
|
||||||
self.theParent.setStatus(self.tr("Opened Project: {0}").format(self.projName))
|
# Check the project tree consistency
|
||||||
|
for tItem in self.projTree:
|
||||||
|
tHandle = tItem.itemHandle
|
||||||
|
logger.verbose("Checking item '%s'", tHandle)
|
||||||
|
if not self.projTree.updateItemData(tHandle):
|
||||||
|
logger.error("There was a problem item '%s', and it has been removed", tHandle)
|
||||||
|
del self.projTree[tHandle] # The file will be re-added as orphaned
|
||||||
|
|
||||||
self._scanProjectFolder()
|
self._scanProjectFolder()
|
||||||
self._loadProjectLocalisation()
|
self._loadProjectLocalisation()
|
||||||
@@ -624,6 +618,7 @@ class NWProject():
|
|||||||
|
|
||||||
self._writeLockFile()
|
self._writeLockFile()
|
||||||
self.setProjectChanged(False)
|
self.setProjectChanged(False)
|
||||||
|
self.theParent.setStatus(self.tr("Opened Project: {0}").format(self.projName))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1202,7 +1197,7 @@ class NWProject():
|
|||||||
self.statusItems.resetCounts()
|
self.statusItems.resetCounts()
|
||||||
self.importItems.resetCounts()
|
self.importItems.resetCounts()
|
||||||
for nwItem in self.projTree:
|
for nwItem in self.projTree:
|
||||||
if nwItem.itemClass in nwLists.CLS_NOVEL:
|
if nwItem.isNovelLike():
|
||||||
self.statusItems.increment(nwItem.itemStatus)
|
self.statusItems.increment(nwItem.itemStatus)
|
||||||
else:
|
else:
|
||||||
self.importItems.increment(nwItem.itemImport)
|
self.importItems.increment(nwItem.itemImport)
|
||||||
@@ -1452,6 +1447,7 @@ class NWProject():
|
|||||||
orphItem.setClass(oClass)
|
orphItem.setClass(oClass)
|
||||||
orphItem.setLayout(oLayout)
|
orphItem.setLayout(oLayout)
|
||||||
self.projTree.append(oHandle, oParent, orphItem)
|
self.projTree.append(oHandle, oParent, orphItem)
|
||||||
|
self.projTree.updateItemData(orphItem.itemHandle)
|
||||||
|
|
||||||
if noWhere:
|
if noWhere:
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
|
|||||||
+78
-72
@@ -33,7 +33,7 @@ from hashlib import sha256
|
|||||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
||||||
from novelwriter.error import logException
|
from novelwriter.error import logException
|
||||||
from novelwriter.common import checkHandle
|
from novelwriter.common import checkHandle
|
||||||
from novelwriter.constants import nwConst, nwFiles
|
from novelwriter.constants import nwFiles
|
||||||
from novelwriter.core.item import NWItem
|
from novelwriter.core.item import NWItem
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -41,13 +41,15 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class NWTree():
|
class NWTree():
|
||||||
|
|
||||||
|
MAX_DEPTH = 1000 # Cap of tree traversing for loops
|
||||||
|
|
||||||
def __init__(self, theProject):
|
def __init__(self, theProject):
|
||||||
|
|
||||||
self.theProject = theProject
|
self.theProject = theProject
|
||||||
|
|
||||||
self._projTree = {} # Holds all the items of the project
|
self._projTree = {} # Holds all the items of the project
|
||||||
self._treeOrder = [] # The order of the tree items on the tree view
|
self._treeOrder = [] # The order of the tree items on the tree view
|
||||||
self._treeRoots = [] # The root items of the tree
|
self._treeRoots = {} # The root items of the tree
|
||||||
self._trashRoot = None # The handle of the trash root folder
|
self._trashRoot = None # The handle of the trash root folder
|
||||||
self._archRoot = None # The handle of the archive root folder
|
self._archRoot = None # The handle of the archive root folder
|
||||||
self._theIndex = 0 # The current iterator index
|
self._theIndex = 0 # The current iterator index
|
||||||
@@ -67,7 +69,7 @@ class NWTree():
|
|||||||
"""
|
"""
|
||||||
self._projTree = {}
|
self._projTree = {}
|
||||||
self._treeOrder = []
|
self._treeOrder = []
|
||||||
self._treeRoots = []
|
self._treeRoots = {}
|
||||||
self._trashRoot = None
|
self._trashRoot = None
|
||||||
self._archRoot = None
|
self._archRoot = None
|
||||||
self._theIndex = 0
|
self._theIndex = 0
|
||||||
@@ -98,7 +100,7 @@ class NWTree():
|
|||||||
|
|
||||||
if nwItem.itemType == nwItemType.ROOT:
|
if nwItem.itemType == nwItemType.ROOT:
|
||||||
logger.verbose("Item '%s' is a root item", str(tHandle))
|
logger.verbose("Item '%s' is a root item", str(tHandle))
|
||||||
self._treeRoots.append(tHandle)
|
self._treeRoots[tHandle] = nwItem
|
||||||
if nwItem.itemClass == nwItemClass.ARCHIVE:
|
if nwItem.itemClass == nwItemClass.ARCHIVE:
|
||||||
logger.verbose("Item '%s' is the archive folder", str(tHandle))
|
logger.verbose("Item '%s' is the archive folder", str(tHandle))
|
||||||
self._archRoot = tHandle
|
self._archRoot = tHandle
|
||||||
@@ -207,9 +209,30 @@ class NWTree():
|
|||||||
return novelWords, noteWords
|
return novelWords, noteWords
|
||||||
|
|
||||||
##
|
##
|
||||||
# Tree Structure Methods
|
# Tree Item Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
|
def updateItemData(self, tHandle):
|
||||||
|
"""Update the root item handle of a given item. Returns True if
|
||||||
|
a root was found and data updated, otherwise False.
|
||||||
|
"""
|
||||||
|
tItem = self.__getitem__(tHandle)
|
||||||
|
if tItem is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
iItem = tItem
|
||||||
|
for _ in range(self.MAX_DEPTH):
|
||||||
|
if iItem.itemParent is None:
|
||||||
|
tItem.setRoot(iItem.itemHandle)
|
||||||
|
tItem.setClassDefaults(iItem.itemClass)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
iItem = self.__getitem__(iItem.itemParent)
|
||||||
|
if iItem is None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
raise RecursionError("Critical internal error")
|
||||||
|
|
||||||
def checkType(self, tHandle, itemType):
|
def checkType(self, tHandle, itemType):
|
||||||
"""Return true of item exists and is of the specified item type.
|
"""Return true of item exists and is of the specified item type.
|
||||||
"""
|
"""
|
||||||
@@ -218,71 +241,6 @@ class NWTree():
|
|||||||
return False
|
return False
|
||||||
return tItem.itemType == itemType
|
return tItem.itemType == itemType
|
||||||
|
|
||||||
def trashRoot(self):
|
|
||||||
"""Returns the handle of the trash folder, or None if there
|
|
||||||
isn't one.
|
|
||||||
"""
|
|
||||||
if self._trashRoot:
|
|
||||||
return self._trashRoot
|
|
||||||
return None
|
|
||||||
|
|
||||||
def isTrashRoot(self, tHandle):
|
|
||||||
"""Check if a handle is the trash folder.
|
|
||||||
"""
|
|
||||||
if self._trashRoot is None:
|
|
||||||
return False
|
|
||||||
return tHandle == self._trashRoot
|
|
||||||
|
|
||||||
def archiveRoot(self):
|
|
||||||
"""Returns the handle of the archive folder, or None if there
|
|
||||||
isn't one.
|
|
||||||
"""
|
|
||||||
if self._archRoot:
|
|
||||||
return self._archRoot
|
|
||||||
return None
|
|
||||||
|
|
||||||
def findRoot(self, theClass):
|
|
||||||
"""Find the root item for a given class.
|
|
||||||
Note: This returns the first item for class CUSTOM.
|
|
||||||
"""
|
|
||||||
for aRoot in self._treeRoots:
|
|
||||||
tItem = self.__getitem__(aRoot)
|
|
||||||
if tItem is None:
|
|
||||||
continue
|
|
||||||
if theClass == tItem.itemClass:
|
|
||||||
return tItem.itemHandle
|
|
||||||
return None
|
|
||||||
|
|
||||||
def checkRootUnique(self, theClass):
|
|
||||||
"""Checks if there already is a root entry of class 'theClass'
|
|
||||||
in the root of the project tree. CUSTOM class is skipped as it
|
|
||||||
is not required to be unique.
|
|
||||||
"""
|
|
||||||
if theClass == nwItemClass.CUSTOM:
|
|
||||||
return True
|
|
||||||
for aRoot in self._treeRoots:
|
|
||||||
tItem = self.__getitem__(aRoot)
|
|
||||||
if tItem is None:
|
|
||||||
continue
|
|
||||||
if theClass == tItem.itemClass:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def getRootItem(self, tHandle):
|
|
||||||
"""Iterate upwards in the tree until we find the item with
|
|
||||||
parent None, the root item. We do this with a for loop with a
|
|
||||||
maximum depth to make infinite loops impossible.
|
|
||||||
"""
|
|
||||||
tItem = self.__getitem__(tHandle)
|
|
||||||
if tItem is not None:
|
|
||||||
for i in range(nwConst.MAX_DEPTH + 1):
|
|
||||||
if tItem.itemParent is None:
|
|
||||||
return tItem
|
|
||||||
else:
|
|
||||||
tHandle = tItem.itemParent
|
|
||||||
tItem = self.__getitem__(tHandle)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getItemPath(self, tHandle):
|
def getItemPath(self, tHandle):
|
||||||
"""Iterate upwards in the tree until we find the item with
|
"""Iterate upwards in the tree until we find the item with
|
||||||
parent None, the root item, and return the list of handles.
|
parent None, the root item, and return the list of handles.
|
||||||
@@ -293,7 +251,7 @@ class NWTree():
|
|||||||
tItem = self.__getitem__(tHandle)
|
tItem = self.__getitem__(tHandle)
|
||||||
if tItem is not None:
|
if tItem is not None:
|
||||||
tTree.append(tHandle)
|
tTree.append(tHandle)
|
||||||
for _ in range(nwConst.MAX_DEPTH + 1):
|
for _ in range(self.MAX_DEPTH):
|
||||||
if tItem.itemParent is None:
|
if tItem.itemParent is None:
|
||||||
return tTree
|
return tTree
|
||||||
else:
|
else:
|
||||||
@@ -303,8 +261,56 @@ class NWTree():
|
|||||||
return tTree
|
return tTree
|
||||||
else:
|
else:
|
||||||
tTree.append(tHandle)
|
tTree.append(tHandle)
|
||||||
|
else:
|
||||||
|
raise RecursionError("Critical internal error")
|
||||||
|
|
||||||
return tTree
|
return tTree
|
||||||
|
|
||||||
|
##
|
||||||
|
# Tree Root Methods
|
||||||
|
##
|
||||||
|
|
||||||
|
def isRoot(self, tHandle):
|
||||||
|
"""Check if a handle is a root item.
|
||||||
|
"""
|
||||||
|
return tHandle in self._treeRoots
|
||||||
|
|
||||||
|
def isTrash(self, tHandle):
|
||||||
|
"""Check if an item is in or is the trash folder.
|
||||||
|
"""
|
||||||
|
tItem = self.__getitem__(tHandle)
|
||||||
|
if tItem is None:
|
||||||
|
return True
|
||||||
|
if tItem.itemClass == nwItemClass.TRASH:
|
||||||
|
return True
|
||||||
|
if self._trashRoot is not None:
|
||||||
|
if tHandle == self._trashRoot:
|
||||||
|
return True
|
||||||
|
elif tItem.itemParent == self._trashRoot:
|
||||||
|
return True
|
||||||
|
elif tItem.itemRoot == self._trashRoot:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def trashRoot(self):
|
||||||
|
"""Returns the handle of the trash folder, or None if there
|
||||||
|
isn't one.
|
||||||
|
"""
|
||||||
|
if self._trashRoot:
|
||||||
|
return self._trashRoot
|
||||||
|
return None
|
||||||
|
|
||||||
|
def findRoot(self, theClass):
|
||||||
|
"""Find the first root item for a given class.
|
||||||
|
"""
|
||||||
|
for aRoot in self._treeRoots:
|
||||||
|
tItem = self.__getitem__(aRoot)
|
||||||
|
if tItem is None:
|
||||||
|
continue
|
||||||
|
if theClass == tItem.itemClass:
|
||||||
|
return tItem.itemHandle
|
||||||
|
return None
|
||||||
|
|
||||||
##
|
##
|
||||||
# Setters
|
# Setters
|
||||||
##
|
##
|
||||||
@@ -420,7 +426,7 @@ class NWTree():
|
|||||||
return
|
return
|
||||||
|
|
||||||
if tHandle in self._treeRoots:
|
if tHandle in self._treeRoots:
|
||||||
self._treeRoots.remove(tHandle)
|
del self._treeRoots[tHandle]
|
||||||
if tHandle == self._trashRoot:
|
if tHandle == self._trashRoot:
|
||||||
self._trashRoot = None
|
self._trashRoot = None
|
||||||
if tHandle == self._archRoot:
|
if tHandle == self._archRoot:
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class GuiDocMerge(QDialog):
|
|||||||
self.theParent.makeAlert(self.tr("Internal error."), nwAlert.ERROR)
|
self.theParent.makeAlert(self.tr("Internal error."), nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemClass, srcItem.itemParent)
|
nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemParent)
|
||||||
newItem = self.theProject.projTree[nHandle]
|
newItem = self.theProject.projTree[nHandle]
|
||||||
newItem.setStatus(srcItem.itemStatus)
|
newItem.setStatus(srcItem.itemStatus)
|
||||||
newItem.setImport(srcItem.itemImport)
|
newItem.setImport(srcItem.itemImport)
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.core import NWDoc
|
from novelwriter.core import NWDoc
|
||||||
from novelwriter.enum import nwAlert, nwItemType, nwItemClass, nwItemLayout
|
from novelwriter.enum import nwAlert, nwItemType
|
||||||
from novelwriter.constants import nwConst
|
|
||||||
from novelwriter.gui.custom import QHelpLabel
|
from novelwriter.gui.custom import QHelpLabel
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -160,16 +159,6 @@ class GuiDocSplit(QDialog):
|
|||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check that another folder can be created
|
|
||||||
parTree = self.theProject.projTree.getItemPath(srcItem.itemParent)
|
|
||||||
if len(parTree) >= nwConst.MAX_DEPTH - 1:
|
|
||||||
self.theParent.makeAlert(self.tr(
|
|
||||||
"Cannot add new folder for the document split. "
|
|
||||||
"Maximum folder depth has been reached. "
|
|
||||||
"Please move the file to another level in the project tree."
|
|
||||||
), nwAlert.ERROR)
|
|
||||||
return False
|
|
||||||
|
|
||||||
msgYes = self.theParent.askQuestion(
|
msgYes = self.theParent.askQuestion(
|
||||||
self.tr("Split Document"),
|
self.tr("Split Document"),
|
||||||
"{0}<br><br>{1}".format(
|
"{0}<br><br>{1}".format(
|
||||||
@@ -186,22 +175,16 @@ class GuiDocSplit(QDialog):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Create the folder
|
# Create the folder
|
||||||
fHandle = self.theProject.newFolder(
|
fHandle = self.theProject.newFolder(srcItem.itemName, srcItem.itemParent)
|
||||||
srcItem.itemName, srcItem.itemClass, srcItem.itemParent
|
|
||||||
)
|
|
||||||
self.theParent.treeView.revealNewTreeItem(fHandle)
|
self.theParent.treeView.revealNewTreeItem(fHandle)
|
||||||
logger.verbose("Creating folder '%s'", fHandle)
|
logger.verbose("Creating folder '%s'", fHandle)
|
||||||
|
|
||||||
# Loop through, and create the files
|
# Loop through, and create the files
|
||||||
for wTitle, iStart, iEnd in finalOrder:
|
for wTitle, iStart, iEnd in finalOrder:
|
||||||
|
|
||||||
isNovel = srcItem.itemClass == nwItemClass.NOVEL
|
|
||||||
itemLayout = nwItemLayout.DOCUMENT if isNovel else nwItemLayout.NOTE
|
|
||||||
|
|
||||||
wTitle = wTitle.lstrip("#").strip()
|
wTitle = wTitle.lstrip("#").strip()
|
||||||
nHandle = self.theProject.newFile(wTitle, srcItem.itemClass, fHandle)
|
nHandle = self.theProject.newFile(wTitle, fHandle)
|
||||||
newItem = self.theProject.projTree[nHandle]
|
newItem = self.theProject.projTree[nHandle]
|
||||||
newItem.setLayout(itemLayout)
|
|
||||||
newItem.setStatus(srcItem.itemStatus)
|
newItem.setStatus(srcItem.itemStatus)
|
||||||
newItem.setImport(srcItem.itemImport)
|
newItem.setImport(srcItem.itemImport)
|
||||||
logger.verbose(
|
logger.verbose(
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.enum import nwItemLayout, nwItemType
|
from novelwriter.enum import nwItemLayout, nwItemType
|
||||||
from novelwriter.constants import trConst, nwLists, nwLabels
|
from novelwriter.constants import trConst, nwLabels
|
||||||
from novelwriter.gui.custom import QSwitch
|
from novelwriter.gui.custom import QSwitch
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -74,7 +74,7 @@ class GuiItemEditor(QDialog):
|
|||||||
# Item Status
|
# Item Status
|
||||||
self.editStatus = QComboBox()
|
self.editStatus = QComboBox()
|
||||||
self.editStatus.setMinimumWidth(mVd)
|
self.editStatus.setMinimumWidth(mVd)
|
||||||
if self.theItem.itemClass in nwLists.CLS_NOVEL:
|
if self.theItem.isNovelLike():
|
||||||
for key, entry in self.theProject.statusItems.items():
|
for key, entry in self.theProject.statusItems.items():
|
||||||
self.editStatus.addItem(entry["icon"], entry["name"], key)
|
self.editStatus.addItem(entry["icon"], entry["name"], key)
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ class GuiItemEditor(QDialog):
|
|||||||
self.editLayout.setMinimumWidth(mVd)
|
self.editLayout.setMinimumWidth(mVd)
|
||||||
validLayouts = []
|
validLayouts = []
|
||||||
if self.theItem.itemType == nwItemType.FILE:
|
if self.theItem.itemType == nwItemType.FILE:
|
||||||
if self.theItem.itemClass in nwLists.CLS_NOVEL:
|
if self.theItem.documentAllowed():
|
||||||
validLayouts.append(nwItemLayout.DOCUMENT)
|
validLayouts.append(nwItemLayout.DOCUMENT)
|
||||||
validLayouts.append(nwItemLayout.NOTE)
|
validLayouts.append(nwItemLayout.NOTE)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -71,24 +71,6 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
|
||||||
# Methods
|
|
||||||
##
|
|
||||||
|
|
||||||
def setAvailableRoot(self):
|
|
||||||
"""Update the list of available root folders and set the ones
|
|
||||||
that are active.
|
|
||||||
"""
|
|
||||||
for itemClass in nwItemClass:
|
|
||||||
if itemClass == nwItemClass.NO_CLASS:
|
|
||||||
continue
|
|
||||||
if itemClass == nwItemClass.TRASH:
|
|
||||||
continue
|
|
||||||
self.rootItems[itemClass].setVisible(
|
|
||||||
self.theProject.projTree.checkRootUnique(itemClass)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Update Menu on Settings Changed
|
# Update Menu on Settings Changed
|
||||||
##
|
##
|
||||||
@@ -215,7 +197,7 @@ class GuiMainMenu(QMenuBar):
|
|||||||
# Project > New Folder
|
# Project > New Folder
|
||||||
self.aCreateFolder = QAction(self.tr("Create Folder"), self)
|
self.aCreateFolder = QAction(self.tr("Create Folder"), self)
|
||||||
self.aCreateFolder.setShortcut("Ctrl+Shift+N")
|
self.aCreateFolder.setShortcut("Ctrl+Shift+N")
|
||||||
self.aCreateFolder.triggered.connect(lambda: self._newTreeItem(nwItemType.FOLDER, None))
|
self.aCreateFolder.triggered.connect(lambda: self._newTreeItem(nwItemType.FOLDER))
|
||||||
self.projMenu.addAction(self.aCreateFolder)
|
self.projMenu.addAction(self.aCreateFolder)
|
||||||
|
|
||||||
# Project > Separator
|
# Project > Separator
|
||||||
@@ -277,7 +259,7 @@ class GuiMainMenu(QMenuBar):
|
|||||||
# Document > New
|
# Document > New
|
||||||
self.aNewDoc = QAction(self.tr("New Document"), self)
|
self.aNewDoc = QAction(self.tr("New Document"), self)
|
||||||
self.aNewDoc.setShortcut("Ctrl+N")
|
self.aNewDoc.setShortcut("Ctrl+N")
|
||||||
self.aNewDoc.triggered.connect(lambda: self._newTreeItem(nwItemType.FILE, None))
|
self.aNewDoc.triggered.connect(lambda: self._newTreeItem(nwItemType.FILE))
|
||||||
self.docuMenu.addAction(self.aNewDoc)
|
self.docuMenu.addAction(self.aNewDoc)
|
||||||
|
|
||||||
# Document > Open
|
# Document > Open
|
||||||
|
|||||||
+88
-163
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
|
|||||||
|
|
||||||
from novelwriter.core import NWDoc
|
from novelwriter.core import NWDoc
|
||||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
|
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||||
from novelwriter.constants import nwConst, trConst, nwLists, nwLabels
|
from novelwriter.constants import trConst, nwLabels
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -161,114 +161,64 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
self._timeChanged = 0
|
self._timeChanged = 0
|
||||||
return
|
return
|
||||||
|
|
||||||
def newTreeItem(self, itemType, itemClass):
|
def newTreeItem(self, itemType, itemClass=None):
|
||||||
"""Add new item to the tree, with a given itemType and
|
"""Add new item to the tree, with a given itemType (and
|
||||||
itemClass, and attach it to the selected handle. Also make sure
|
itemClass if Root), and attach it to the selected handle. Also make
|
||||||
the item is added in a place it can be added, and that other
|
sure the item is added in a place it can be added, and that other
|
||||||
meta data is set correctly to ensure a valid project tree.
|
meta data is set correctly to ensure a valid project tree.
|
||||||
"""
|
"""
|
||||||
pHandle = self.getSelectedHandle()
|
|
||||||
nHandle = None
|
|
||||||
|
|
||||||
if not self.theParent.hasProject:
|
if not self.theParent.hasProject:
|
||||||
logger.error("No project open")
|
logger.error("No project open")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not isinstance(itemType, nwItemType):
|
nHandle = None
|
||||||
# This would indicate an internal bug
|
tHandle = None
|
||||||
logger.error("No itemType provided")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# The item needs to be assigned an item class, so one must be
|
if itemType == nwItemType.ROOT and isinstance(itemClass, nwItemClass):
|
||||||
# provided, or it must be possible to extract it from the parent
|
|
||||||
# item of the new item.
|
|
||||||
if itemClass is None and pHandle is not None:
|
|
||||||
pItem = self.theProject.projTree[pHandle]
|
|
||||||
if pItem is not None:
|
|
||||||
itemClass = pItem.itemClass
|
|
||||||
|
|
||||||
# If class is still not set, alert the user and exit
|
|
||||||
if itemClass is None:
|
|
||||||
if itemType == nwItemType.FILE:
|
|
||||||
self.theParent.makeAlert(self.tr(
|
|
||||||
"Please select a valid location in the tree to add the document."
|
|
||||||
), nwAlert.ERROR)
|
|
||||||
else:
|
|
||||||
self.theParent.makeAlert(self.tr(
|
|
||||||
"Please select a valid location in the tree to add the folder."
|
|
||||||
), nwAlert.ERROR)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Everything is fine, we have what we need, so we proceed
|
|
||||||
logger.verbose(
|
|
||||||
"Adding new item of type '%s' and class '%s' to handle '%s'",
|
|
||||||
itemType.name, itemClass.name, str(pHandle)
|
|
||||||
)
|
|
||||||
|
|
||||||
if itemType == nwItemType.ROOT:
|
|
||||||
tHandle = self.theProject.newRoot(
|
tHandle = self.theProject.newRoot(
|
||||||
trConst(nwLabels.CLASS_NAME[itemClass]), itemClass
|
trConst(nwLabels.CLASS_NAME[itemClass]), itemClass
|
||||||
)
|
)
|
||||||
if tHandle is None:
|
|
||||||
logger.error("No root item added")
|
|
||||||
return False
|
|
||||||
|
|
||||||
else:
|
elif itemType in (nwItemType.FILE, nwItemType.FOLDER):
|
||||||
# If no parent has been selected, make the new file under
|
|
||||||
# the root NOVEL item.
|
|
||||||
if pHandle is None:
|
|
||||||
pHandle = self.theProject.projTree.findRoot(nwItemClass.NOVEL)
|
|
||||||
|
|
||||||
# If still nothing, give up
|
sHandle = self.getSelectedHandle()
|
||||||
if pHandle is None:
|
if sHandle is None or sHandle not in self.theProject.projTree:
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
"Did not find anywhere to add the file or folder!"
|
"Did not find anywhere to add the file or folder!"
|
||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Now check if the selected item is a file, in which case
|
# If the selected item is a file, the new item will be a sibling
|
||||||
# the new file will be a sibling
|
pItem = self.theProject.projTree[sHandle]
|
||||||
pItem = self.theProject.projTree[pHandle]
|
|
||||||
if pItem.itemType == nwItemType.FILE:
|
if pItem.itemType == nwItemType.FILE:
|
||||||
nHandle = pHandle
|
nHandle = sHandle
|
||||||
pHandle = pItem.itemParent
|
sHandle = pItem.itemParent
|
||||||
|
if sHandle is None:
|
||||||
|
logger.error("Internal error") # Bug
|
||||||
|
return False
|
||||||
|
|
||||||
# If we again have no home, give up
|
if self.theProject.projTree.isTrash(sHandle):
|
||||||
if pHandle is None:
|
|
||||||
self.theParent.makeAlert(self.tr(
|
|
||||||
"Did not find anywhere to add the file or folder!"
|
|
||||||
), nwAlert.ERROR)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self.theProject.projTree.isTrashRoot(pHandle):
|
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
"Cannot add new files or folders to the Trash folder."
|
"Cannot add new files or folders to the Trash folder."
|
||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
parTree = self.theProject.projTree.getItemPath(pHandle)
|
# Add the file or folder
|
||||||
|
|
||||||
# If we're still here, add the file or folder
|
|
||||||
if itemType == nwItemType.FILE:
|
if itemType == nwItemType.FILE:
|
||||||
tHandle = self.theProject.newFile(self.tr("New File"), itemClass, pHandle)
|
if pItem.isNovelLike():
|
||||||
|
tHandle = self.theProject.newFile(self.tr("New Document"), sHandle)
|
||||||
|
else:
|
||||||
|
tHandle = self.theProject.newFile(self.tr("New Note"), sHandle)
|
||||||
elif itemType == nwItemType.FOLDER:
|
elif itemType == nwItemType.FOLDER:
|
||||||
if len(parTree) >= nwConst.MAX_DEPTH - 1:
|
tHandle = self.theProject.newFolder(self.tr("New Folder"), sHandle)
|
||||||
# Folders cannot be deeper than MAX_DEPTH - 1, leaving room
|
|
||||||
# for one more level of files.
|
|
||||||
self.theParent.makeAlert(self.tr(
|
|
||||||
"Cannot add new folder to this item. "
|
|
||||||
"Maximum folder depth has been reached."
|
|
||||||
), nwAlert.ERROR)
|
|
||||||
return False
|
|
||||||
tHandle = self.theProject.newFolder(self.tr("New Folder"), itemClass, pHandle)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.error("Failed to add new item")
|
logger.error("Failed to add new item")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If there is no handle set, return here
|
# If there is no handle set, return here. This is a bug
|
||||||
if tHandle is None:
|
if tHandle is None: # pragma: no cover
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Add the new item to the tree
|
# Add the new item to the tree
|
||||||
@@ -280,13 +230,9 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if nwItem.itemType != nwItemType.FILE:
|
if nwItem.itemType != nwItemType.FILE:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# This is a new files, so let's add some content
|
# This is a new file, so let's add some content
|
||||||
newDoc = NWDoc(self.theProject, tHandle)
|
newDoc = NWDoc(self.theProject, tHandle)
|
||||||
curTxt = newDoc.readDocument()
|
if not newDoc.readDocument():
|
||||||
if curTxt is None:
|
|
||||||
curTxt = ""
|
|
||||||
|
|
||||||
if curTxt == "":
|
|
||||||
if nwItem.itemLayout == nwItemLayout.DOCUMENT:
|
if nwItem.itemLayout == nwItemLayout.DOCUMENT:
|
||||||
newText = f"### {nwItem.itemName}\n\n"
|
newText = f"### {nwItem.itemName}\n\n"
|
||||||
else:
|
else:
|
||||||
@@ -310,6 +256,9 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
"""Reveal a newly added project item in the project tree.
|
"""Reveal a newly added project item in the project tree.
|
||||||
"""
|
"""
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.projTree[tHandle]
|
||||||
|
if nwItem is None:
|
||||||
|
return False
|
||||||
|
|
||||||
trItem = self._addTreeItem(nwItem, nHandle)
|
trItem = self._addTreeItem(nwItem, nHandle)
|
||||||
if trItem is None:
|
if trItem is None:
|
||||||
return False
|
return False
|
||||||
@@ -495,8 +444,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
logger.error("Could not delete item")
|
logger.error("Could not delete item")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
pHandle = nwItemS.itemParent
|
if self.theProject.projTree.isTrash(tHandle):
|
||||||
if self.theProject.projTree.isTrashRoot(pHandle):
|
|
||||||
# If the file is in the trash folder already, as the
|
# If the file is in the trash folder already, as the
|
||||||
# user if they want to permanently delete the file.
|
# user if they want to permanently delete the file.
|
||||||
doPermanent = False
|
doPermanent = False
|
||||||
@@ -513,13 +461,6 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if doPermanent:
|
if doPermanent:
|
||||||
logger.debug("Permanently deleting file with handle '%s'", tHandle)
|
logger.debug("Permanently deleting file with handle '%s'", tHandle)
|
||||||
|
|
||||||
self.propagateCount(tHandle, 0)
|
|
||||||
tIndex = trItemP.indexOfChild(trItemS)
|
|
||||||
trItemC = trItemP.takeChild(tIndex)
|
|
||||||
|
|
||||||
if self.theParent.docEditor.docHandle() == tHandle:
|
|
||||||
self.theParent.closeDocument()
|
|
||||||
|
|
||||||
delDoc = NWDoc(self.theProject, tHandle)
|
delDoc = NWDoc(self.theProject, tHandle)
|
||||||
if not delDoc.deleteDocument():
|
if not delDoc.deleteDocument():
|
||||||
self.theParent.makeAlert([
|
self.theParent.makeAlert([
|
||||||
@@ -527,6 +468,13 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
], nwAlert.ERROR)
|
], nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
self.propagateCount(tHandle, 0)
|
||||||
|
tIndex = trItemP.indexOfChild(trItemS)
|
||||||
|
trItemC = trItemP.takeChild(tIndex)
|
||||||
|
|
||||||
|
if self.theParent.docEditor.docHandle() == tHandle:
|
||||||
|
self.theParent.closeDocument()
|
||||||
|
|
||||||
self.theIndex.deleteHandle(tHandle)
|
self.theIndex.deleteHandle(tHandle)
|
||||||
self._deleteTreeItem(tHandle)
|
self._deleteTreeItem(tHandle)
|
||||||
self._setTreeChanged(True)
|
self._setTreeChanged(True)
|
||||||
@@ -540,19 +488,13 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
self.tr("Move file '{0}' to Trash?").format(nwItemS.itemName),
|
self.tr("Move file '{0}' to Trash?").format(nwItemS.itemName),
|
||||||
)
|
)
|
||||||
if msgYes:
|
if msgYes:
|
||||||
if pHandle is None:
|
|
||||||
logger.warning("File has no parent item")
|
|
||||||
|
|
||||||
logger.debug("Moving file '%s' to trash", tHandle)
|
logger.debug("Moving file '%s' to trash", tHandle)
|
||||||
|
|
||||||
self.propagateCount(tHandle, 0)
|
self.propagateCount(tHandle, 0)
|
||||||
tIndex = trItemP.indexOfChild(trItemS)
|
tIndex = trItemP.indexOfChild(trItemS)
|
||||||
trItemC = trItemP.takeChild(tIndex)
|
trItemC = trItemP.takeChild(tIndex)
|
||||||
trItemT.addChild(trItemC)
|
trItemT.addChild(trItemC)
|
||||||
self._updateItemParent(tHandle)
|
self._postItemMove(tHandle, wCount)
|
||||||
self.propagateCount(tHandle, wCount)
|
|
||||||
|
|
||||||
self.theIndex.deleteHandle(tHandle)
|
|
||||||
self._recordLastMove(trItemS, trItemP, tIndex)
|
self._recordLastMove(trItemS, trItemP, tIndex)
|
||||||
self._setTreeChanged(True)
|
self._setTreeChanged(True)
|
||||||
|
|
||||||
@@ -562,6 +504,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if trItemP is None:
|
if trItemP is None:
|
||||||
logger.error("Could not delete folder")
|
logger.error("Could not delete folder")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
tIndex = trItemP.indexOfChild(trItemS)
|
tIndex = trItemP.indexOfChild(trItemS)
|
||||||
if trItemS.childCount() == 0:
|
if trItemS.childCount() == 0:
|
||||||
trItemP.takeChild(tIndex)
|
trItemP.takeChild(tIndex)
|
||||||
@@ -581,7 +524,6 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if trItemS.childCount() == 0:
|
if trItemS.childCount() == 0:
|
||||||
self.takeTopLevelItem(tIndex)
|
self.takeTopLevelItem(tIndex)
|
||||||
self._deleteTreeItem(tHandle)
|
self._deleteTreeItem(tHandle)
|
||||||
self.theParent.mainMenu.setAvailableRoot()
|
|
||||||
self._setTreeChanged(True)
|
self._setTreeChanged(True)
|
||||||
else:
|
else:
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
@@ -634,7 +576,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def propagateCount(self, tHandle, theCount, nDepth=0):
|
def propagateCount(self, tHandle, theCount):
|
||||||
"""Recursive function setting the word count for a given item,
|
"""Recursive function setting the word count for a given item,
|
||||||
and propagating that count upwards in the tree until reaching a
|
and propagating that count upwards in the tree until reaching a
|
||||||
root item. This function is more efficient than recalculating
|
root item. This function is more efficient than recalculating
|
||||||
@@ -654,12 +596,13 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
pCount = 0
|
pCount = 0
|
||||||
|
pHandle = None
|
||||||
for i in range(pItem.childCount()):
|
for i in range(pItem.childCount()):
|
||||||
pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole))
|
pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole))
|
||||||
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
|
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
|
||||||
|
|
||||||
if not nDepth > nwConst.MAX_DEPTH + 1 and pHandle != "":
|
if pHandle:
|
||||||
self.propagateCount(pHandle, pCount, nDepth+1)
|
self.propagateCount(pHandle, pCount)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -714,9 +657,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
movItem = parItem.takeChild(srcIndex)
|
movItem = parItem.takeChild(srcIndex)
|
||||||
dstItem.insertChild(dstIndex, movItem)
|
dstItem.insertChild(dstIndex, movItem)
|
||||||
|
|
||||||
snItem = self.theProject.projTree[sHandle]
|
self._postItemMove(sHandle, wCount)
|
||||||
dnItem = self.theProject.projTree[dHandle]
|
|
||||||
self._postItemMove(sHandle, snItem, dnItem, wCount)
|
|
||||||
|
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
movItem.setSelected(True)
|
movItem.setSelected(True)
|
||||||
@@ -848,29 +789,34 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if pItem is not None:
|
if pItem is not None:
|
||||||
pIndex = pItem.indexOfChild(sItem)
|
pIndex = pItem.indexOfChild(sItem)
|
||||||
|
|
||||||
wCount = int(sItem.data(self.C_COUNT, Qt.UserRole))
|
# Determine if the drag and drop is allowed:
|
||||||
|
# - Files can be moved anywhere
|
||||||
|
# - Folders can only be moved within the same root folder
|
||||||
|
# - Root folders cannot be moved at all
|
||||||
|
# - Items cannot be dropped on top of a file (moved inside)
|
||||||
|
|
||||||
isFile = snItem.itemType == nwItemType.FILE
|
isFile = snItem.itemType == nwItemType.FILE
|
||||||
isRoot = snItem.itemType == nwItemType.ROOT
|
isRoot = snItem.itemType == nwItemType.ROOT
|
||||||
onFile = dnItem.itemType == nwItemType.FILE
|
onFile = dnItem.itemType == nwItemType.FILE
|
||||||
|
inSame = snItem.itemRoot == dnItem.itemRoot
|
||||||
|
|
||||||
isSame = snItem.itemClass == dnItem.itemClass
|
allowDrop = inSame or isFile
|
||||||
isNone = snItem.itemClass == nwItemClass.NO_CLASS
|
|
||||||
isNote = snItem.itemLayout == nwItemLayout.NOTE
|
|
||||||
onFree = dnItem.itemClass in nwLists.FREE_CLASS and isFile
|
|
||||||
|
|
||||||
allowDrop = isSame or isNone or isNote or onFree
|
|
||||||
allowDrop &= not (self.dropIndicatorPosition() == QAbstractItemView.OnItem and onFile)
|
allowDrop &= not (self.dropIndicatorPosition() == QAbstractItemView.OnItem and onFile)
|
||||||
|
|
||||||
if allowDrop and not isRoot:
|
if allowDrop and not isRoot:
|
||||||
logger.debug("Drag'n'drop of item '%s' accepted", sHandle)
|
logger.debug("Drag'n'drop of item '%s' accepted", sHandle)
|
||||||
|
|
||||||
|
wCount = int(sItem.data(self.C_COUNT, Qt.UserRole))
|
||||||
self.propagateCount(sHandle, 0)
|
self.propagateCount(sHandle, 0)
|
||||||
|
|
||||||
QTreeWidget.dropEvent(self, theEvent)
|
QTreeWidget.dropEvent(self, theEvent)
|
||||||
self._postItemMove(sHandle, snItem, dnItem, wCount)
|
self._postItemMove(sHandle, wCount)
|
||||||
self._recordLastMove(sItem, pItem, pIndex)
|
self._recordLastMove(sItem, pItem, pIndex)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
theEvent.ignore()
|
|
||||||
logger.debug("Drag'n'drop of item '%s' not accepted", sHandle)
|
logger.debug("Drag'n'drop of item '%s' not accepted", sHandle)
|
||||||
|
|
||||||
|
theEvent.ignore()
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
"The item cannot be moved to that location."
|
"The item cannot be moved to that location."
|
||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
@@ -881,40 +827,39 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
|
|
||||||
def _postItemMove(self, sHandle, snItem, dnItem, wCount):
|
def _postItemMove(self, tHandle, wCount):
|
||||||
"""Run various maintenance tasks for a moved item.
|
"""Run various maintenance tasks for a moved item.
|
||||||
"""
|
"""
|
||||||
isFile = snItem.itemType == nwItemType.FILE
|
trItemS = self._getTreeItem(tHandle)
|
||||||
isSame = snItem.itemClass == dnItem.itemClass
|
nwItemS = self.theProject.projTree[tHandle]
|
||||||
onFree = dnItem.itemClass in nwLists.FREE_CLASS and isFile
|
trItemP = trItemS.parent()
|
||||||
|
if trItemP is None:
|
||||||
|
logger.error("Failed to find new parent item of '%s'", tHandle)
|
||||||
|
return False
|
||||||
|
|
||||||
self._updateItemParent(sHandle)
|
# Update item parent handle in the project, make sure meta data
|
||||||
|
# is updated accordingly, and update word count
|
||||||
|
pHandle = trItemP.data(self.C_NAME, Qt.UserRole)
|
||||||
|
nwItemS.setParent(pHandle)
|
||||||
|
self.theProject.projTree.updateItemData(tHandle)
|
||||||
|
self.setTreeItemValues(tHandle)
|
||||||
|
self.propagateCount(tHandle, wCount)
|
||||||
|
|
||||||
# If the item does not have the same class as the target,
|
logger.debug("The parent of item '%s' has been changed to '%s'", tHandle, pHandle)
|
||||||
# and the target is not a free root folder, update its class
|
|
||||||
if not (isSame or onFree):
|
|
||||||
logger.debug(
|
|
||||||
"Item '%s' class has been changed from '%s' to '%s'",
|
|
||||||
sHandle, snItem.itemClass.name, dnItem.itemClass.name
|
|
||||||
)
|
|
||||||
snItem.setClass(dnItem.itemClass)
|
|
||||||
self.setTreeItemValues(sHandle)
|
|
||||||
|
|
||||||
self.propagateCount(sHandle, wCount)
|
|
||||||
|
|
||||||
# The items dropped into archive or trash should be removed
|
# The items dropped into archive or trash should be removed
|
||||||
# from the project index, for all other items, we rescan the
|
# from the project index, for all other items, we rescan the
|
||||||
# file to ensure the index is up to date.
|
# file to ensure the index is up to date.
|
||||||
if onFree:
|
if nwItemS.isInactive():
|
||||||
self.theIndex.deleteHandle(sHandle)
|
self.theIndex.deleteHandle(tHandle)
|
||||||
else:
|
else:
|
||||||
self.theIndex.reIndexHandle(sHandle)
|
self.theIndex.reIndexHandle(tHandle)
|
||||||
|
|
||||||
# Trigger dependent updates
|
# Trigger dependent updates
|
||||||
self._setTreeChanged(True)
|
self._setTreeChanged(True)
|
||||||
self._emitItemChange(sHandle)
|
self._emitItemChange(tHandle)
|
||||||
|
|
||||||
return
|
return True
|
||||||
|
|
||||||
def _getTreeItem(self, tHandle):
|
def _getTreeItem(self, tHandle):
|
||||||
"""Returns the QTreeWidgetItem of a given item handle.
|
"""Returns the QTreeWidgetItem of a given item handle.
|
||||||
@@ -966,7 +911,6 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if pHandle is None:
|
if pHandle is None:
|
||||||
if nwItem.itemType == nwItemType.ROOT:
|
if nwItem.itemType == nwItemType.ROOT:
|
||||||
self.addTopLevelItem(newItem)
|
self.addTopLevelItem(newItem)
|
||||||
self.theParent.mainMenu.setAvailableRoot()
|
|
||||||
elif nwItem.itemType == nwItemType.TRASH:
|
elif nwItem.itemType == nwItemType.TRASH:
|
||||||
self.addTopLevelItem(newItem)
|
self.addTopLevelItem(newItem)
|
||||||
else:
|
else:
|
||||||
@@ -1015,25 +959,6 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
|
|
||||||
return trItem
|
return trItem
|
||||||
|
|
||||||
def _updateItemParent(self, tHandle):
|
|
||||||
"""Update the parent handle of an item so that the information
|
|
||||||
in the project is consistent with the treeView.
|
|
||||||
"""
|
|
||||||
trItemS = self._getTreeItem(tHandle)
|
|
||||||
nwItemS = self.theProject.projTree[tHandle]
|
|
||||||
trItemP = trItemS.parent()
|
|
||||||
if trItemP is None:
|
|
||||||
logger.error("Failed to find new parent item of '%s'", tHandle)
|
|
||||||
return False
|
|
||||||
|
|
||||||
pHandle = trItemP.data(self.C_NAME, Qt.UserRole)
|
|
||||||
nwItemS.setParent(pHandle)
|
|
||||||
self.setTreeItemValues(tHandle)
|
|
||||||
|
|
||||||
logger.debug("The parent of item '%s' has been changed to '%s'", tHandle, pHandle)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _setTreeChanged(self, theState):
|
def _setTreeChanged(self, theState):
|
||||||
"""Set the tree change flag, and propagate to the project.
|
"""Set the tree change flag, and propagate to the project.
|
||||||
"""
|
"""
|
||||||
@@ -1048,7 +973,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
"""
|
"""
|
||||||
if self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
|
if self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.projTree[tHandle]
|
||||||
if nwItem.itemClass == nwItemClass.NOVEL:
|
if nwItem.isNovelLike():
|
||||||
self.novelItemChanged.emit()
|
self.novelItemChanged.emit()
|
||||||
else:
|
else:
|
||||||
self.noteItemChanged.emit()
|
self.noteItemChanged.emit()
|
||||||
@@ -1182,7 +1107,7 @@ class GuiProjectTreeMenu(QMenu):
|
|||||||
"""Forward the new file call to the project tree.
|
"""Forward the new file call to the project tree.
|
||||||
"""
|
"""
|
||||||
if self.theItem is not None:
|
if self.theItem is not None:
|
||||||
self.theTree.newTreeItem(nwItemType.FILE, None)
|
self.theTree.newTreeItem(nwItemType.FILE)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@@ -1190,7 +1115,7 @@ class GuiProjectTreeMenu(QMenu):
|
|||||||
"""Forward the new folder call to the project tree.
|
"""Forward the new folder call to the project tree.
|
||||||
"""
|
"""
|
||||||
if self.theItem is not None:
|
if self.theItem is not None:
|
||||||
self.theTree.newTreeItem(nwItemType.FOLDER, None)
|
self.theTree.newTreeItem(nwItemType.FOLDER)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ from novelwriter.enum import (
|
|||||||
nwItemType, nwItemClass, nwAlert, nwWidget, nwState
|
nwItemType, nwItemClass, nwAlert, nwWidget, nwState
|
||||||
)
|
)
|
||||||
from novelwriter.common import getGuiItem, hexToInt
|
from novelwriter.common import getGuiItem, hexToInt
|
||||||
from novelwriter.constants import nwLists
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -848,7 +847,7 @@ class GuiMain(QMainWindow):
|
|||||||
tItem = self.theProject.projTree[tHandle]
|
tItem = self.theProject.projTree[tHandle]
|
||||||
if tItem is None:
|
if tItem is None:
|
||||||
return False
|
return False
|
||||||
if tItem.itemType not in nwLists.REG_TYPES:
|
if tItem.itemType == nwItemType.NO_TYPE:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.verbose("Requesting change to item '%s'", tHandle)
|
logger.verbose("Requesting change to item '%s'", tHandle)
|
||||||
|
|||||||
@@ -780,14 +780,12 @@ class GuiBuildNovel(QDialog):
|
|||||||
if theItem is None:
|
if theItem is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not theItem.isExported and not ignoreFlag:
|
if not (theItem.isExported or ignoreFlag):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
isNone = theItem.itemType != nwItemType.FILE
|
isNone = theItem.itemType != nwItemType.FILE
|
||||||
isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT
|
isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT
|
||||||
isNone |= theItem.itemClass == nwItemClass.NO_CLASS
|
isNone |= theItem.isInactive()
|
||||||
isNone |= theItem.itemClass == nwItemClass.TRASH
|
|
||||||
isNone |= theItem.itemParent == self.theProject.projTree.trashRoot()
|
|
||||||
isNone |= theItem.itemParent is None
|
isNone |= theItem.itemParent is None
|
||||||
isNote = theItem.itemLayout == nwItemLayout.NOTE
|
isNote = theItem.itemLayout == nwItemLayout.NOTE
|
||||||
isNovel = not isNone and not isNote
|
isNovel = not isNone and not isNote
|
||||||
@@ -799,10 +797,6 @@ class GuiBuildNovel(QDialog):
|
|||||||
if isNovel and not novelFiles:
|
if isNovel and not novelFiles:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
rootItem = self.theProject.projTree.getRootItem(theItem.itemHandle)
|
|
||||||
if rootItem.itemClass == nwItemClass.ARCHIVE:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _saveDocument(self, theFmt):
|
def _saveDocument(self, theFmt):
|
||||||
|
|||||||
+30
-30
@@ -1,13 +1,13 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-05 23:14:56">
|
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-17 19:05:24">
|
||||||
<project>
|
<project>
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<title>Sample Project</title>
|
<title>Sample Project</title>
|
||||||
<author>Jane Smith</author>
|
<author>Jane Smith</author>
|
||||||
<author>Jay Doh</author>
|
<author>Jay Doh</author>
|
||||||
<saveCount>1303</saveCount>
|
<saveCount>1306</saveCount>
|
||||||
<autoCount>199</autoCount>
|
<autoCount>199</autoCount>
|
||||||
<editTime>65049</editTime>
|
<editTime>65149</editTime>
|
||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<doBackup>False</doBackup>
|
<doBackup>False</doBackup>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<section></section>
|
<section></section>
|
||||||
</titleFormat>
|
</titleFormat>
|
||||||
<status>
|
<status>
|
||||||
<entry key="sf12341" count="6" red="100" green="100" blue="100">New</entry>
|
<entry key="sf12341" count="5" red="100" green="100" blue="100">New</entry>
|
||||||
<entry key="sf24ce6" count="1" red="200" green="50" blue="0">Notes</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="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="s90e6c9" count="6" red="193" green="129" blue="0">1st Draft</entry>
|
||||||
@@ -42,110 +42,110 @@
|
|||||||
<entry key="s78ea90" count="0" red="58" green="180" blue="58">Finished</entry>
|
<entry key="s78ea90" count="0" red="58" green="180" blue="58">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry key="ia857f0" count="4" red="100" green="100" blue="100">None</entry>
|
<entry key="ia857f0" count="5" red="100" green="100" blue="100">None</entry>
|
||||||
<entry key="icfb3a5" count="2" red="0" green="122" blue="188">Minor</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="i2d7a54" count="2" red="21" green="0" blue="180">Major</entry>
|
||||||
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
|
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="25">
|
<content count="25">
|
||||||
<item handle="7031beac91f75" parent="None" order="0" type="ROOT" class="NOVEL">
|
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sc24b8f" import="ia857f0">Novel</name>
|
<name status="sc24b8f" import="ia857f0">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="53b69b83cdafc" parent="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="93" wordCount="19" paraCount="2" cursorPos="2"/>
|
<meta charCount="93" wordCount="19" paraCount="2" cursorPos="2"/>
|
||||||
<name status="sc24b8f" import="ia857f0" exported="True">Title Page</name>
|
<name status="sc24b8f" import="ia857f0" exported="True">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="974e400180a99" parent="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="186" wordCount="39" paraCount="2" cursorPos="212"/>
|
<meta charCount="186" wordCount="39" paraCount="2" cursorPos="212"/>
|
||||||
<name status="sf12341" import="ia857f0" exported="True">Page</name>
|
<name status="sf12341" import="ia857f0" exported="True">Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="edca4be2fcaf8" parent="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="26" wordCount="6" paraCount="1" cursorPos="33"/>
|
<meta charCount="26" wordCount="6" paraCount="1" cursorPos="33"/>
|
||||||
<name status="sf12341" import="ia857f0" exported="True">Part One</name>
|
<name status="sf12341" import="ia857f0" exported="True">Part One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="e7ded148d6e4a" parent="7031beac91f75" order="3" type="FOLDER" class="NOVEL">
|
<item handle="e7ded148d6e4a" parent="7031beac91f75" root="7031beac91f75" order="3" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="s90e6c9" import="ia857f0">A Folder</name>
|
<name status="s90e6c9" import="ia857f0">A Folder</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
|
<meta charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
|
||||||
<name status="sf24ce6" import="ia857f0" exported="True">Chapter One</name>
|
<name status="sf24ce6" import="ia857f0" exported="True">Chapter One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="2429" wordCount="432" paraCount="14" cursorPos="219"/>
|
<meta charCount="2429" wordCount="432" paraCount="14" cursorPos="219"/>
|
||||||
<name status="s90e6c9" import="ia857f0" exported="True">Making a Scene</name>
|
<name status="s90e6c9" import="ia857f0" exported="True">Making a Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="476" wordCount="93" paraCount="3" cursorPos="577"/>
|
<meta charCount="476" wordCount="93" paraCount="3" cursorPos="577"/>
|
||||||
<name status="s90e6c9" import="ia857f0" exported="True">Another Scene</name>
|
<name status="s90e6c9" import="ia857f0" exported="True">Another Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="ba8a28a246524" parent="e7ded148d6e4a" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="7031beac91f75" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="617" wordCount="101" paraCount="3" cursorPos="4"/>
|
<meta charCount="617" wordCount="101" paraCount="3" cursorPos="4"/>
|
||||||
<name status="sf12341" import="ia857f0" exported="True">Interlude</name>
|
<name status="sf12341" import="ia857f0" exported="True">Interlude</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" order="4" type="FILE" class="NOVEL" layout="NOTE">
|
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="NOTE">
|
||||||
<meta charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/>
|
<meta charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/>
|
||||||
<name status="sd51c5b" import="ia857f0" exported="False">A Note on Structure</name>
|
<name status="sd51c5b" import="ia857f0" exported="False">A Note on Structure</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
|
<meta charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
|
||||||
<name status="s90e6c9" import="ia857f0" exported="True">Chapter Two</name>
|
<name status="s90e6c9" import="ia857f0" exported="True">Chapter Two</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="ae7339df26ded" parent="e7ded148d6e4a" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
|
<meta charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
|
||||||
<name status="s90e6c9" import="ia857f0" exported="True">We Found John!</name>
|
<name status="s90e6c9" import="ia857f0" exported="True">We Found John!</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f6622b4617424" parent="None" order="1" type="ROOT" class="CHARACTER">
|
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sf12341" import="ia857f0">Characters</name>
|
<name status="sf12341" import="ia857f0">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f7e2d9f330615" parent="f6622b4617424" order="0" type="FOLDER" class="CHARACTER">
|
<item handle="f7e2d9f330615" parent="f6622b4617424" root="f6622b4617424" order="0" type="FOLDER" class="CHARACTER">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sf12341" import="ia857f0">Main Characters</name>
|
<name status="sf12341" import="ia857f0">Main Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="14298de4d9524" parent="f7e2d9f330615" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
|
<meta charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
|
||||||
<name status="sf12341" import="icfb3a5" exported="True">John Smith</name>
|
<name status="sf12341" import="icfb3a5" exported="True">John Smith</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" order="1" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
|
<meta charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
|
||||||
<name status="sf12341" import="i2d7a54" exported="True">Jane Smith</name>
|
<name status="sf12341" import="i2d7a54" exported="True">Jane Smith</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="15c4492bd5107" parent="None" order="2" type="ROOT" class="WORLD">
|
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sf12341" import="ia857f0">Locations</name>
|
<name status="sf12341" import="ia857f0">Locations</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="b3e74dbc1f584" parent="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
|
<meta charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
|
||||||
<name status="sf12341" import="i56be10" exported="True">Earth</name>
|
<name status="sf12341" import="i56be10" exported="True">Earth</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f1471bef9f2ae" parent="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
|
<meta charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
|
||||||
<name status="sf12341" import="icfb3a5" exported="True">Space</name>
|
<name status="sf12341" import="icfb3a5" exported="True">Space</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
|
<meta charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
|
||||||
<name status="sf12341" import="i2d7a54" exported="True">Mars</name>
|
<name status="sf12341" import="i2d7a54" exported="True">Mars</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="6827118336ac1" parent="None" order="3" type="ROOT" class="ARCHIVE">
|
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="3" type="ROOT" class="ARCHIVE">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sf12341" import="ia857f0">Archive</name>
|
<name status="sf12341" import="ia857f0">Archive</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="ae9bf3c3ea159" parent="6827118336ac1" order="0" type="FOLDER" class="ARCHIVE">
|
<item handle="ae9bf3c3ea159" parent="6827118336ac1" root="6827118336ac1" order="0" type="FOLDER" class="ARCHIVE">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sf12341" import="ia857f0">Scenes</name>
|
<name status="sf12341" import="ia857f0">Scenes</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="6827118336ac1" order="0" type="FILE" class="ARCHIVE" layout="DOCUMENT">
|
||||||
<meta charCount="315" wordCount="55" paraCount="1" cursorPos="322"/>
|
<meta charCount="315" wordCount="55" paraCount="1" cursorPos="322"/>
|
||||||
<name status="s90e6c9" import="ia857f0" exported="True">Old File</name>
|
<name status="s90e6c9" import="ia857f0" exported="True">Old File</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98acd8c76c93a" parent="None" order="4" type="TRASH" class="TRASH">
|
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="4" type="TRASH" class="TRASH">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sf12341" import="ia857f0">Trash</name>
|
<name status="sf12341" import="ia857f0">Trash</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="b8136a5a774a0" parent="98acd8c76c93a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="98acd8c76c93a" order="0" type="FILE" class="TRASH" layout="DOCUMENT">
|
||||||
<meta charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
|
<meta charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
|
||||||
<name status="sf12341" import="ia857f0" exported="True">Delete Me!</name>
|
<name status="sf12341" import="ia857f0" exported="True">Delete Me!</name>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
+3
-1
@@ -166,7 +166,9 @@ def nwGUI(qtbot, monkeypatch, fncDir, fncConf):
|
|||||||
"""
|
"""
|
||||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||||
monkeypatch.setattr("novelwriter.CONFIG", fncConf)
|
monkeypatch.setattr("novelwriter.CONFIG", fncConf)
|
||||||
nwGUI = novelwriter.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir])
|
nwGUI = novelwriter.main(
|
||||||
|
["--testmode", "--info", "--config=%s" % fncDir, "--data=%s" % fncDir]
|
||||||
|
)
|
||||||
qtbot.addWidget(nwGUI)
|
qtbot.addWidget(nwGUI)
|
||||||
nwGUI.show()
|
nwGUI.show()
|
||||||
qtbot.wait(20)
|
qtbot.wait(20)
|
||||||
|
|||||||
+21
-21
@@ -44,87 +44,87 @@
|
|||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="21">
|
<content count="21">
|
||||||
<item handle="b3643d0f92e32" parent="None" order="0" type="ROOT" class="NOVEL">
|
<item handle="b3643d0f92e32" parent="None" root="b3643d0f92e32" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sbaa94f" import="i613591">Novel</name>
|
<name status="sbaa94f" import="i613591">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="7a992350f3eb6" parent="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="7a992350f3eb6" parent="b3643d0f92e32" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="230" wordCount="40" paraCount="3" cursorPos="148"/>
|
<meta charCount="230" wordCount="40" paraCount="3" cursorPos="148"/>
|
||||||
<name status="sedd043" import="i613591" exported="True">Lorem Ipsum</name>
|
<name status="sedd043" import="i613591" exported="True">Lorem Ipsum</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="8c58a65414c23" parent="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="8c58a65414c23" parent="b3643d0f92e32" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="1058" wordCount="176" paraCount="2" cursorPos="43"/>
|
<meta charCount="1058" wordCount="176" paraCount="2" cursorPos="43"/>
|
||||||
<name status="sedd043" import="i613591" exported="True">Front Matter</name>
|
<name status="sedd043" import="i613591" exported="True">Front Matter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="88d59a277361b" parent="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="88d59a277361b" parent="b3643d0f92e32" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="584" wordCount="92" paraCount="1" cursorPos="4"/>
|
<meta charCount="584" wordCount="92" paraCount="1" cursorPos="4"/>
|
||||||
<name status="s92a87b" import="i613591" exported="True">Prologue</name>
|
<name status="s92a87b" import="i613591" exported="True">Prologue</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="db7e733775d4d" parent="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="db7e733775d4d" parent="b3643d0f92e32" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="35" wordCount="6" paraCount="1" cursorPos="42"/>
|
<meta charCount="35" wordCount="6" paraCount="1" cursorPos="42"/>
|
||||||
<name status="sbaa94f" import="i613591" exported="True">Act One</name>
|
<name status="sbaa94f" import="i613591" exported="True">Act One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="45e6b01ca35c1" parent="b3643d0f92e32" order="4" type="FOLDER" class="NOVEL">
|
<item handle="45e6b01ca35c1" parent="b3643d0f92e32" root="b3643d0f92e32" order="4" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="s92a87b" import="i613591">Chapter One</name>
|
<name status="s92a87b" import="i613591">Chapter One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="fb609cd8319dc" parent="45e6b01ca35c1" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="fb609cd8319dc" parent="45e6b01ca35c1" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="419" wordCount="67" paraCount="1" cursorPos="56"/>
|
<meta charCount="419" wordCount="67" paraCount="1" cursorPos="56"/>
|
||||||
<name status="s92a87b" import="i613591" exported="True">Chapter One</name>
|
<name status="s92a87b" import="i613591" exported="True">Chapter One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="88243afbe5ed8" parent="45e6b01ca35c1" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="88243afbe5ed8" parent="45e6b01ca35c1" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="2758" wordCount="404" paraCount="4" cursorPos="1528"/>
|
<meta charCount="2758" wordCount="404" paraCount="4" cursorPos="1528"/>
|
||||||
<name status="sedd043" import="i613591" exported="True">Scene One</name>
|
<name status="sedd043" import="i613591" exported="True">Scene One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f96ec11c6a3da" parent="45e6b01ca35c1" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="f96ec11c6a3da" parent="45e6b01ca35c1" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="4043" wordCount="600" paraCount="6" cursorPos="2335"/>
|
<meta charCount="4043" wordCount="600" paraCount="6" cursorPos="2335"/>
|
||||||
<name status="sedd043" import="i613591" exported="True">Scene Two</name>
|
<name status="sedd043" import="i613591" exported="True">Scene Two</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="846352075de7d" parent="b3643d0f92e32" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="846352075de7d" parent="b3643d0f92e32" root="b3643d0f92e32" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="631" wordCount="109" paraCount="3" cursorPos="376"/>
|
<meta charCount="631" wordCount="109" paraCount="3" cursorPos="376"/>
|
||||||
<name status="sbaa94f" import="i613591" exported="False">Interlude</name>
|
<name status="sbaa94f" import="i613591" exported="False">Interlude</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="6bd935d2490cd" parent="b3643d0f92e32" order="6" type="FOLDER" class="NOVEL">
|
<item handle="6bd935d2490cd" parent="b3643d0f92e32" root="b3643d0f92e32" order="6" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="s92a87b" import="i613591">Chapter Two</name>
|
<name status="s92a87b" import="i613591">Chapter Two</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="441420a886d82" parent="6bd935d2490cd" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="441420a886d82" parent="6bd935d2490cd" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="477" wordCount="70" paraCount="1" cursorPos="56"/>
|
<meta charCount="477" wordCount="70" paraCount="1" cursorPos="56"/>
|
||||||
<name status="s92a87b" import="i613591" exported="True">Chapter Two</name>
|
<name status="s92a87b" import="i613591" exported="True">Chapter Two</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="eb103bc70c90c" parent="6bd935d2490cd" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="eb103bc70c90c" parent="6bd935d2490cd" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="3006" wordCount="439" paraCount="4" cursorPos="57"/>
|
<meta charCount="3006" wordCount="439" paraCount="4" cursorPos="57"/>
|
||||||
<name status="sedd043" import="i613591" exported="True">Scene Three</name>
|
<name status="sedd043" import="i613591" exported="True">Scene Three</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f8c0562e50f1b" parent="6bd935d2490cd" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="f8c0562e50f1b" parent="6bd935d2490cd" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="3839" wordCount="563" paraCount="6" cursorPos="56"/>
|
<meta charCount="3839" wordCount="563" paraCount="6" cursorPos="56"/>
|
||||||
<name status="sedd043" import="i613591" exported="True">Scene Four</name>
|
<name status="sedd043" import="i613591" exported="True">Scene Four</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="47666c91c7ccf" parent="6bd935d2490cd" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="47666c91c7ccf" parent="6bd935d2490cd" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="3644" wordCount="543" paraCount="5" cursorPos="351"/>
|
<meta charCount="3644" wordCount="543" paraCount="5" cursorPos="351"/>
|
||||||
<name status="sedd043" import="i613591" exported="True">Scene Five</name>
|
<name status="sedd043" import="i613591" exported="True">Scene Five</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="67a8707f2f249" parent="None" order="1" type="ROOT" class="CHARACTER">
|
<item handle="67a8707f2f249" parent="None" root="67a8707f2f249" order="1" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sbaa94f" import="i613591">Characters</name>
|
<name status="sbaa94f" import="i613591">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="4c4f28287af27" parent="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="4c4f28287af27" parent="67a8707f2f249" root="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta charCount="1864" wordCount="284" paraCount="3" cursorPos="1883"/>
|
<meta charCount="1864" wordCount="284" paraCount="3" cursorPos="1883"/>
|
||||||
<name status="sbaa94f" import="i613591" exported="True">Mr. Nobody</name>
|
<name status="sbaa94f" import="i613591" exported="True">Mr. Nobody</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="6c6afb1247750" parent="None" order="2" type="ROOT" class="PLOT">
|
<item handle="6c6afb1247750" parent="None" root="6c6afb1247750" order="2" type="ROOT" class="PLOT">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sbaa94f" import="i613591">Plot</name>
|
<name status="sbaa94f" import="i613591">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="2426c6f0ca922" parent="6c6afb1247750" order="0" type="FILE" class="PLOT" layout="NOTE">
|
<item handle="2426c6f0ca922" parent="6c6afb1247750" root="6c6afb1247750" order="0" type="FILE" class="PLOT" layout="NOTE">
|
||||||
<meta charCount="1369" wordCount="195" paraCount="2" cursorPos="1387"/>
|
<meta charCount="1369" wordCount="195" paraCount="2" cursorPos="1387"/>
|
||||||
<name status="sbaa94f" import="i613591" exported="True">Main</name>
|
<name status="sbaa94f" import="i613591" exported="True">Main</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="60bdf227455cc" parent="None" order="3" type="ROOT" class="WORLD">
|
<item handle="60bdf227455cc" parent="None" root="60bdf227455cc" order="3" type="ROOT" class="WORLD">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sbaa94f" import="i613591">World</name>
|
<name status="sbaa94f" import="i613591">World</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="04468803b92e1" parent="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="04468803b92e1" parent="60bdf227455cc" root="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta charCount="1770" wordCount="259" paraCount="3" cursorPos="1792"/>
|
<meta charCount="1770" wordCount="259" paraCount="3" cursorPos="1792"/>
|
||||||
<name status="sbaa94f" import="i613591" exported="True">Ancient Europe</name>
|
<name status="sbaa94f" import="i613591" exported="True">Ancient Europe</name>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -42,35 +42,35 @@
|
|||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="8">
|
<content count="8">
|
||||||
<item handle="a508bb932959c" parent="None" order="0" type="ROOT" class="NOVEL">
|
<item handle="a508bb932959c" parent="None" root="a508bb932959c" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="s72322d" import="iffcacb">Novel</name>
|
<name status="s72322d" import="iffcacb">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="a35baf2e93843" parent="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="a35baf2e93843" parent="a508bb932959c" root="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="28" wordCount="6" paraCount="1" cursorPos="33"/>
|
<meta charCount="28" wordCount="6" paraCount="1" cursorPos="33"/>
|
||||||
<name status="s72322d" import="iffcacb" exported="True">Title Page</name>
|
<name status="s72322d" import="iffcacb" exported="True">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="a6d311a93600a" parent="a508bb932959c" order="1" type="FOLDER" class="NOVEL">
|
<item handle="a6d311a93600a" parent="a508bb932959c" root="a508bb932959c" order="1" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="s72322d" import="iffcacb">New Chapter</name>
|
<name status="s72322d" import="iffcacb">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f5ab3e30151e1" parent="a6d311a93600a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="f5ab3e30151e1" parent="a6d311a93600a" root="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="16"/>
|
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="16"/>
|
||||||
<name status="s72322d" import="iffcacb" exported="True">New Chapter</name>
|
<name status="s72322d" import="iffcacb" exported="True">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="8c659a11cd429" parent="a6d311a93600a" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="8c659a11cd429" parent="a6d311a93600a" root="a508bb932959c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="9" wordCount="2" paraCount="0" cursorPos="15"/>
|
<meta charCount="9" wordCount="2" paraCount="0" cursorPos="15"/>
|
||||||
<name status="s72322d" import="iffcacb" exported="True">New Scene</name>
|
<name status="s72322d" import="iffcacb" exported="True">New Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="7695ce551d265" parent="None" order="1" type="ROOT" class="PLOT">
|
<item handle="7695ce551d265" parent="None" root="7695ce551d265" order="1" type="ROOT" class="PLOT">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="s72322d" import="iffcacb">Plot</name>
|
<name status="s72322d" import="iffcacb">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="afb3043c7b2b3" parent="None" order="2" type="ROOT" class="CHARACTER">
|
<item handle="afb3043c7b2b3" parent="None" root="afb3043c7b2b3" order="2" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="s72322d" import="iffcacb">Characters</name>
|
<name status="s72322d" import="iffcacb">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="9d5247ab588e0" parent="None" order="3" type="ROOT" class="WORLD">
|
<item handle="9d5247ab588e0" parent="None" root="9d5247ab588e0" order="3" type="ROOT" class="WORLD">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="s72322d" import="iffcacb">World</name>
|
<name status="s72322d" import="iffcacb">World</name>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 14:14:40">
|
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 20:40:13">
|
||||||
<project>
|
<project>
|
||||||
<name>Test Custom</name>
|
<name>Test Custom</name>
|
||||||
<title>Test Novel</title>
|
<title>Test Novel</title>
|
||||||
@@ -35,104 +35,104 @@
|
|||||||
<entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
|
<entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry key="ie465e1" count="0" red="100" green="100" blue="100">New</entry>
|
<entry key="ie465e1" count="6" red="100" green="100" blue="100">New</entry>
|
||||||
<entry key="i8b9d24" count="0" red="200" green="50" blue="0">Minor</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="i16419f" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
|
<entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="23">
|
<content count="23">
|
||||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Novel</name>
|
<name status="sbc8960" import="ie465e1">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="44cb730c42048" parent="None" order="0" type="ROOT" class="PLOT">
|
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Plot</name>
|
<name status="sbc8960" import="ie465e1">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="71ee45a3c0db9" parent="None" order="0" type="ROOT" class="CHARACTER">
|
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Characters</name>
|
<name status="sbc8960" import="ie465e1">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="811786ad1ae74" parent="None" order="0" type="ROOT" class="WORLD">
|
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Locations</name>
|
<name status="sbc8960" import="ie465e1">Locations</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="25fc0e7096fc6" parent="None" order="0" type="ROOT" class="TIMELINE">
|
<item handle="25fc0e7096fc6" parent="None" root="25fc0e7096fc6" order="0" type="ROOT" class="TIMELINE">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Timeline</name>
|
<name status="sbc8960" import="ie465e1">Timeline</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="31489056e0916" parent="None" order="0" type="ROOT" class="OBJECT">
|
<item handle="31489056e0916" parent="None" root="31489056e0916" order="0" type="ROOT" class="OBJECT">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Objects</name>
|
<name status="sbc8960" import="ie465e1">Objects</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98010bd9270f9" parent="None" order="0" type="ROOT" class="ENTITY">
|
<item handle="98010bd9270f9" parent="None" root="98010bd9270f9" order="0" type="ROOT" class="ENTITY">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Entities</name>
|
<name status="sbc8960" import="ie465e1">Entities</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0e17daca5f3e1" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0e17daca5f3e1" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Title Page</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="1a6562590ef19" parent="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
<item handle="1a6562590ef19" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Chapter 1</name>
|
<name status="sbc8960" import="ie465e1">Chapter 1</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="031b4af5197ec" parent="1a6562590ef19" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="031b4af5197ec" parent="1a6562590ef19" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Chapter 1</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Chapter 1</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="41cfc0d1f2d12" parent="1a6562590ef19" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="41cfc0d1f2d12" parent="1a6562590ef19" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 1.1</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 1.1</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="2858dcd1057d3" parent="1a6562590ef19" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="2858dcd1057d3" parent="1a6562590ef19" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 1.2</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 1.2</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="2fca346db6561" parent="1a6562590ef19" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="2fca346db6561" parent="1a6562590ef19" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 1.3</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 1.3</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="02d20bbd7e394" parent="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
<item handle="02d20bbd7e394" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Chapter 2</name>
|
<name status="sbc8960" import="ie465e1">Chapter 2</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="7688b6ef52555" parent="02d20bbd7e394" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="7688b6ef52555" parent="02d20bbd7e394" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Chapter 2</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Chapter 2</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="c837649cce43f" parent="02d20bbd7e394" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="c837649cce43f" parent="02d20bbd7e394" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 2.1</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 2.1</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="6208ef0f7750c" parent="02d20bbd7e394" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="6208ef0f7750c" parent="02d20bbd7e394" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 2.2</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 2.2</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="3e1e967e9b793" parent="02d20bbd7e394" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="3e1e967e9b793" parent="02d20bbd7e394" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 2.3</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 2.3</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="39fa9ec190eee" parent="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
<item handle="39fa9ec190eee" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Chapter 3</name>
|
<name status="sbc8960" import="ie465e1">Chapter 3</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="d029fa3a95e17" parent="39fa9ec190eee" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="d029fa3a95e17" parent="39fa9ec190eee" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Chapter 3</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Chapter 3</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="81b8a03f97e87" parent="39fa9ec190eee" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="81b8a03f97e87" parent="39fa9ec190eee" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 3.1</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 3.1</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="da4ea2a5506f2" parent="39fa9ec190eee" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="da4ea2a5506f2" parent="39fa9ec190eee" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 3.2</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 3.2</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="a68b412c42825" parent="39fa9ec190eee" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="a68b412c42825" parent="39fa9ec190eee" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 3.3</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 3.3</name>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 14:15:44">
|
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 20:39:10">
|
||||||
<project>
|
<project>
|
||||||
<name>Test Custom</name>
|
<name>Test Custom</name>
|
||||||
<title>Test Novel</title>
|
<title>Test Novel</title>
|
||||||
@@ -35,68 +35,68 @@
|
|||||||
<entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
|
<entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry key="ie465e1" count="0" red="100" green="100" blue="100">New</entry>
|
<entry key="ie465e1" count="6" red="100" green="100" blue="100">New</entry>
|
||||||
<entry key="i8b9d24" count="0" red="200" green="50" blue="0">Minor</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="i16419f" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
|
<entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="14">
|
<content count="14">
|
||||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Novel</name>
|
<name status="sbc8960" import="ie465e1">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="44cb730c42048" parent="None" order="0" type="ROOT" class="PLOT">
|
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Plot</name>
|
<name status="sbc8960" import="ie465e1">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="71ee45a3c0db9" parent="None" order="0" type="ROOT" class="CHARACTER">
|
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Characters</name>
|
<name status="sbc8960" import="ie465e1">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="811786ad1ae74" parent="None" order="0" type="ROOT" class="WORLD">
|
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Locations</name>
|
<name status="sbc8960" import="ie465e1">Locations</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="25fc0e7096fc6" parent="None" order="0" type="ROOT" class="TIMELINE">
|
<item handle="25fc0e7096fc6" parent="None" root="25fc0e7096fc6" order="0" type="ROOT" class="TIMELINE">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Timeline</name>
|
<name status="sbc8960" import="ie465e1">Timeline</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="31489056e0916" parent="None" order="0" type="ROOT" class="OBJECT">
|
<item handle="31489056e0916" parent="None" root="31489056e0916" order="0" type="ROOT" class="OBJECT">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Objects</name>
|
<name status="sbc8960" import="ie465e1">Objects</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98010bd9270f9" parent="None" order="0" type="ROOT" class="ENTITY">
|
<item handle="98010bd9270f9" parent="None" root="98010bd9270f9" order="0" type="ROOT" class="ENTITY">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Entities</name>
|
<name status="sbc8960" import="ie465e1">Entities</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0e17daca5f3e1" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0e17daca5f3e1" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Title Page</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="1a6562590ef19" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="1a6562590ef19" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 1</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 1</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="031b4af5197ec" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="031b4af5197ec" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 2</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 2</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="41cfc0d1f2d12" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="41cfc0d1f2d12" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 3</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 3</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="2858dcd1057d3" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="2858dcd1057d3" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 4</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 4</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="2fca346db6561" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="2fca346db6561" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 5</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 5</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="02d20bbd7e394" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="02d20bbd7e394" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Scene 6</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Scene 6</name>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 14:17:50">
|
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 20:37:42">
|
||||||
<project>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title></title>
|
||||||
@@ -33,52 +33,52 @@
|
|||||||
<entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
|
<entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry key="ie465e1" count="3" red="100" green="100" blue="100">New</entry>
|
<entry key="ie465e1" count="4" red="100" green="100" blue="100">New</entry>
|
||||||
<entry key="i8b9d24" count="0" red="200" green="50" blue="0">Minor</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="i16419f" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
|
<entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="10">
|
<content count="10">
|
||||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">Novel</name>
|
<name status="sbc8960" import="ie465e1">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="44cb730c42048" parent="None" order="0" type="ROOT" class="PLOT">
|
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">Plot</name>
|
<name status="sbc8960" import="ie465e1">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="71ee45a3c0db9" parent="None" order="0" type="ROOT" class="CHARACTER">
|
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">Characters</name>
|
<name status="sbc8960" import="ie465e1">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="811786ad1ae74" parent="None" order="0" type="ROOT" class="WORLD">
|
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">World</name>
|
<name status="sbc8960" import="ie465e1">World</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="25fc0e7096fc6" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="ie465e1" exported="True">Title Page</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="31489056e0916" parent="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
<item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">New Chapter</name>
|
<name status="sbc8960" import="ie465e1">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98010bd9270f9" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="ie465e1" exported="True">New Chapter</name>
|
<name status="sbc8960" import="ie465e1" exported="True">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0e17daca5f3e1" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="ie465e1" exported="True">New Scene</name>
|
<name status="sbc8960" import="ie465e1" exported="True">New Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="1a6562590ef19" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="1a6562590ef19" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Hello</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Hello</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="031b4af5197ec" parent="71ee45a3c0db9" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="031b4af5197ec" parent="71ee45a3c0db9" root="71ee45a3c0db9" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="None" exported="True">Jane</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Jane</name>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -40,35 +40,35 @@
|
|||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="8">
|
<content count="8">
|
||||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">Novel</name>
|
<name status="sbc8960" import="ie465e1">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="44cb730c42048" parent="None" order="0" type="ROOT" class="PLOT">
|
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">Plot</name>
|
<name status="sbc8960" import="ie465e1">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="71ee45a3c0db9" parent="None" order="0" type="ROOT" class="CHARACTER">
|
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">Characters</name>
|
<name status="sbc8960" import="ie465e1">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="811786ad1ae74" parent="None" order="0" type="ROOT" class="WORLD">
|
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">World</name>
|
<name status="sbc8960" import="ie465e1">World</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="25fc0e7096fc6" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="ie465e1" exported="True">Title Page</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="31489056e0916" parent="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
<item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">New Chapter</name>
|
<name status="sbc8960" import="ie465e1">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98010bd9270f9" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="ie465e1" exported="True">New Chapter</name>
|
<name status="sbc8960" import="ie465e1" exported="True">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0e17daca5f3e1" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="ie465e1" exported="True">New Scene</name>
|
<name status="sbc8960" import="ie465e1" exported="True">New Scene</name>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 14:16:49">
|
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 20:38:35">
|
||||||
<project>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title></title>
|
||||||
@@ -27,66 +27,82 @@
|
|||||||
<section></section>
|
<section></section>
|
||||||
</titleFormat>
|
</titleFormat>
|
||||||
<status>
|
<status>
|
||||||
<entry key="sbc8960" count="5" red="100" green="100" blue="100">New</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="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="sad3c2d" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||||
<entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
|
<entry key="sbd9c66" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry key="ie465e1" count="3" red="100" green="100" blue="100">New</entry>
|
<entry key="ie465e1" count="10" red="100" green="100" blue="100">New</entry>
|
||||||
<entry key="i8b9d24" count="0" red="200" green="50" blue="0">Minor</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="i16419f" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
|
<entry key="i972a84" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="12">
|
<content count="16">
|
||||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">Novel</name>
|
<name status="sbc8960" import="ie465e1">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="44cb730c42048" parent="None" order="0" type="ROOT" class="PLOT">
|
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="0" type="ROOT" class="PLOT">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">Plot</name>
|
<name status="sbc8960" import="ie465e1">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="71ee45a3c0db9" parent="None" order="0" type="ROOT" class="CHARACTER">
|
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="0" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">Characters</name>
|
<name status="sbc8960" import="ie465e1">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="811786ad1ae74" parent="None" order="0" type="ROOT" class="WORLD">
|
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="0" type="ROOT" class="WORLD">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">World</name>
|
<name status="sbc8960" import="ie465e1">World</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="25fc0e7096fc6" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="ie465e1" exported="True">Title Page</name>
|
<name status="sbc8960" import="ie465e1" exported="True">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="31489056e0916" parent="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
<item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="ie465e1">New Chapter</name>
|
<name status="sbc8960" import="ie465e1">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98010bd9270f9" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="ie465e1" exported="True">New Chapter</name>
|
<name status="sbc8960" import="ie465e1" exported="True">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0e17daca5f3e1" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sbc8960" import="ie465e1" exported="True">New Scene</name>
|
<name status="sbc8960" import="ie465e1" exported="True">New Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="1a6562590ef19" parent="None" order="0" type="ROOT" class="TIMELINE">
|
<item handle="1a6562590ef19" parent="None" root="1a6562590ef19" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Timeline</name>
|
<name status="sbc8960" import="ie465e1">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="031b4af5197ec" parent="None" order="0" type="ROOT" class="OBJECT">
|
<item handle="031b4af5197ec" parent="None" root="031b4af5197ec" order="0" type="ROOT" class="PLOT">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Object</name>
|
<name status="sbc8960" import="ie465e1">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="41cfc0d1f2d12" parent="None" order="0" type="ROOT" class="CUSTOM">
|
<item handle="41cfc0d1f2d12" parent="None" root="41cfc0d1f2d12" order="0" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Custom1</name>
|
<name status="sbc8960" import="ie465e1">Character</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="2858dcd1057d3" parent="None" order="0" type="ROOT" class="CUSTOM">
|
<item handle="2858dcd1057d3" parent="None" root="2858dcd1057d3" order="0" type="ROOT" class="WORLD">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sbc8960" import="None">Custom2</name>
|
<name status="sbc8960" import="ie465e1">World</name>
|
||||||
|
</item>
|
||||||
|
<item handle="2fca346db6561" parent="None" root="2fca346db6561" order="0" type="ROOT" class="TIMELINE">
|
||||||
|
<meta expanded="False"/>
|
||||||
|
<name status="sbc8960" import="ie465e1">Timeline</name>
|
||||||
|
</item>
|
||||||
|
<item handle="02d20bbd7e394" parent="None" root="02d20bbd7e394" order="0" type="ROOT" class="OBJECT">
|
||||||
|
<meta expanded="False"/>
|
||||||
|
<name status="sbc8960" import="ie465e1">Object</name>
|
||||||
|
</item>
|
||||||
|
<item handle="7688b6ef52555" parent="None" root="7688b6ef52555" order="0" type="ROOT" class="CUSTOM">
|
||||||
|
<meta expanded="False"/>
|
||||||
|
<name status="sbc8960" import="ie465e1">Custom1</name>
|
||||||
|
</item>
|
||||||
|
<item handle="c837649cce43f" parent="None" root="c837649cce43f" order="0" type="ROOT" class="CUSTOM">
|
||||||
|
<meta expanded="False"/>
|
||||||
|
<name status="sbc8960" import="ie465e1">Custom2</name>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
%%~name: New File
|
%%~name: New Note
|
||||||
%%~path: 44cb730c42048/031b4af5197ec
|
%%~path: 44cb730c42048/031b4af5197ec
|
||||||
%%~kind: PLOT/NOTE
|
%%~kind: PLOT/NOTE
|
||||||
# Main Plot
|
# Main Plot
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
%%~name: New File
|
%%~name: New Note
|
||||||
%%~path: 71ee45a3c0db9/1a6562590ef19
|
%%~path: 71ee45a3c0db9/1a6562590ef19
|
||||||
%%~kind: CHARACTER/NOTE
|
%%~kind: CHARACTER/NOTE
|
||||||
# Jane Doe
|
# Jane Doe
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
%%~name: New File
|
%%~name: New Note
|
||||||
%%~path: 811786ad1ae74/41cfc0d1f2d12
|
%%~path: 811786ad1ae74/41cfc0d1f2d12
|
||||||
%%~kind: WORLD/NOTE
|
%%~kind: WORLD/NOTE
|
||||||
# Main Location
|
# Main Location
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 12:58:48">
|
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-17 14:18:28">
|
||||||
<project>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title></title>
|
||||||
@@ -33,60 +33,60 @@
|
|||||||
<entry key="sbdd640" count="0" red="50" green="200" blue="0">Finished</entry>
|
<entry key="sbdd640" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry key="i466852" count="3" red="100" green="100" blue="100">New</entry>
|
<entry key="i466852" count="7" red="100" green="100" blue="100">New</entry>
|
||||||
<entry key="i3eb13b" count="0" red="200" green="50" blue="0">Minor</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="i392456" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry key="i23b8c1" count="0" red="50" green="200" blue="0">Main</entry>
|
<entry key="i23b8c1" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="12">
|
<content count="12">
|
||||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sa3b179" import="i466852">Novel</name>
|
<name status="sa3b179" import="i466852">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="25fc0e7096fc6" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sa3b179" import="i466852" exported="True">Title Page</name>
|
<name status="sa3b179" import="i466852" exported="True">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="31489056e0916" parent="73475cb40a568" order="1" type="FOLDER" class="NOVEL">
|
<item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="1" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sa3b179" import="i466852">New Chapter</name>
|
<name status="sa3b179" import="i466852">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98010bd9270f9" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sa3b179" import="i466852" exported="True">New Chapter</name>
|
<name status="sa3b179" import="i466852" exported="True">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0e17daca5f3e1" parent="31489056e0916" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="612" wordCount="95" paraCount="10" cursorPos="768"/>
|
<meta charCount="612" wordCount="95" paraCount="10" cursorPos="768"/>
|
||||||
<name status="sa3b179" import="i466852" exported="True">New Scene</name>
|
<name status="sa3b179" import="i466852" exported="True">New Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="44cb730c42048" parent="None" order="1" type="ROOT" class="PLOT">
|
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="1" type="ROOT" class="PLOT">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sa3b179" import="i466852">Plot</name>
|
<name status="sa3b179" import="i466852">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="031b4af5197ec" parent="44cb730c42048" order="0" type="FILE" class="PLOT" layout="NOTE">
|
<item handle="031b4af5197ec" parent="44cb730c42048" root="44cb730c42048" order="0" type="FILE" class="PLOT" layout="NOTE">
|
||||||
<meta charCount="48" wordCount="10" paraCount="1" cursorPos="69"/>
|
<meta charCount="48" wordCount="10" paraCount="1" cursorPos="69"/>
|
||||||
<name status="sa3b179" import="None" exported="True">New File</name>
|
<name status="sa3b179" import="i466852" exported="True">New Note</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="71ee45a3c0db9" parent="None" order="2" type="ROOT" class="CHARACTER">
|
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="2" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sa3b179" import="i466852">Characters</name>
|
<name status="sa3b179" import="i466852">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="1a6562590ef19" parent="71ee45a3c0db9" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="1a6562590ef19" parent="71ee45a3c0db9" root="71ee45a3c0db9" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta charCount="34" wordCount="8" paraCount="1" cursorPos="51"/>
|
<meta charCount="34" wordCount="8" paraCount="1" cursorPos="51"/>
|
||||||
<name status="sa3b179" import="None" exported="True">New File</name>
|
<name status="sa3b179" import="i466852" exported="True">New Note</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="811786ad1ae74" parent="None" order="3" type="ROOT" class="WORLD">
|
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="3" type="ROOT" class="WORLD">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="sa3b179" import="i466852">World</name>
|
<name status="sa3b179" import="i466852">World</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="41cfc0d1f2d12" parent="811786ad1ae74" order="0" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="41cfc0d1f2d12" parent="811786ad1ae74" root="811786ad1ae74" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta charCount="51" wordCount="9" paraCount="1" cursorPos="68"/>
|
<meta charCount="51" wordCount="9" paraCount="1" cursorPos="68"/>
|
||||||
<name status="sa3b179" import="None" exported="True">New File</name>
|
<name status="sa3b179" import="i466852" exported="True">New Note</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="2fca346db6561" parent="None" order="4" type="TRASH" class="TRASH">
|
<item handle="2fca346db6561" parent="None" root="2fca346db6561" order="4" type="TRASH" class="TRASH">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="None" import="None">Trash</name>
|
<name status="sa3b179" import="i466852">Trash</name>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -33,44 +33,44 @@
|
|||||||
<entry key="sbdd640" count="0" red="50" green="200" blue="0">Finished</entry>
|
<entry key="sbdd640" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry key="i466852" count="0" red="100" green="100" blue="100">New</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="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="i392456" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry key="i23b8c1" count="0" red="50" green="200" blue="0">Main</entry>
|
<entry key="i23b8c1" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="8">
|
<content count="8">
|
||||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sa3b179" import="None">Novel</name>
|
<name status="sa3b179" import="i466852">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="25fc0e7096fc6" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sa3b179" import="None" exported="True">Title Page</name>
|
<name status="sa3b179" import="i466852" exported="True">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="31489056e0916" parent="73475cb40a568" order="1" type="FOLDER" class="NOVEL">
|
<item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="1" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sa3b179" import="None">New Chapter</name>
|
<name status="sa3b179" import="i466852">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98010bd9270f9" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sa3b179" import="None" exported="True">New Chapter</name>
|
<name status="sa3b179" import="i466852" exported="True">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0e17daca5f3e1" parent="31489056e0916" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
|
<meta charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sa3b179" import="None" exported="True">New Scene</name>
|
<name status="sa3b179" import="i466852" exported="True">New Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="44cb730c42048" parent="None" order="1" type="ROOT" class="PLOT">
|
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="1" type="ROOT" class="PLOT">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sa3b179" import="None">Plot</name>
|
<name status="sa3b179" import="i466852">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="71ee45a3c0db9" parent="None" order="2" type="ROOT" class="CHARACTER">
|
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="2" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sa3b179" import="None">Characters</name>
|
<name status="sa3b179" import="i466852">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="811786ad1ae74" parent="None" order="3" type="ROOT" class="WORLD">
|
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="3" type="ROOT" class="WORLD">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sa3b179" import="None">World</name>
|
<name status="sa3b179" import="i466852">World</name>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 18:15:12">
|
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-16 20:52:19">
|
||||||
<project>
|
<project>
|
||||||
<name>Project Name</name>
|
<name>Project Name</name>
|
||||||
<title>Project Title</title>
|
<title>Project Title</title>
|
||||||
@@ -39,44 +39,44 @@
|
|||||||
<entry key="sbc8960" count="0" red="20" green="30" blue="40">Final</entry>
|
<entry key="sbc8960" count="0" red="20" green="30" blue="40">Final</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry key="i466852" count="0" red="100" green="100" blue="100">New</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="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="i392456" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry key="i1a3d1f" count="0" red="100" green="100" blue="100">Final</entry>
|
<entry key="i1a3d1f" count="0" red="100" green="100" blue="100">Final</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="8">
|
<content count="8">
|
||||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
<item handle="73475cb40a568" parent="None" root="73475cb40a568" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sa3b179" import="None">Novel</name>
|
<name status="sa3b179" import="i466852">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="25fc0e7096fc6" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="25fc0e7096fc6" parent="73475cb40a568" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sa3b179" import="None" exported="True">Title Page</name>
|
<name status="sa3b179" import="i466852" exported="True">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="31489056e0916" parent="73475cb40a568" order="1" type="FOLDER" class="NOVEL">
|
<item handle="31489056e0916" parent="73475cb40a568" root="73475cb40a568" order="1" type="FOLDER" class="NOVEL">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sa3b179" import="None">New Chapter</name>
|
<name status="sa3b179" import="i466852">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98010bd9270f9" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="98010bd9270f9" parent="31489056e0916" root="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sa3b179" import="None" exported="True">New Chapter</name>
|
<name status="sa3b179" import="i466852" exported="True">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0e17daca5f3e1" parent="31489056e0916" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0e17daca5f3e1" parent="31489056e0916" root="73475cb40a568" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
|
<meta charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
|
||||||
<name status="sa3b179" import="None" exported="True">New Scene</name>
|
<name status="sa3b179" import="i466852" exported="True">New Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="44cb730c42048" parent="None" order="1" type="ROOT" class="PLOT">
|
<item handle="44cb730c42048" parent="None" root="44cb730c42048" order="1" type="ROOT" class="PLOT">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sa3b179" import="None">Plot</name>
|
<name status="sa3b179" import="i466852">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="71ee45a3c0db9" parent="None" order="2" type="ROOT" class="CHARACTER">
|
<item handle="71ee45a3c0db9" parent="None" root="71ee45a3c0db9" order="2" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sa3b179" import="None">Characters</name>
|
<name status="sa3b179" import="i466852">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="811786ad1ae74" parent="None" order="3" type="ROOT" class="WORLD">
|
<item handle="811786ad1ae74" parent="None" root="811786ad1ae74" order="3" type="ROOT" class="WORLD">
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="sa3b179" import="None">World</name>
|
<name status="sa3b179" import="i466852">World</name>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
|
|||||||
# Try to open a new (non-existent) file
|
# Try to open a new (non-existent) file
|
||||||
nHandle = theProject.projTree.findRoot(nwItemClass.NOVEL)
|
nHandle = theProject.projTree.findRoot(nwItemClass.NOVEL)
|
||||||
assert nHandle is not None
|
assert nHandle is not None
|
||||||
xHandle = theProject.newFile("New File", nwItemClass.NOVEL, nHandle)
|
xHandle = theProject.newFile("New File", nHandle)
|
||||||
theDoc = NWDoc(theProject, xHandle)
|
theDoc = NWDoc(theProject, xHandle)
|
||||||
assert bool(theDoc) is True
|
assert bool(theDoc) is True
|
||||||
assert repr(theDoc) == f"<NWDoc handle={xHandle}>"
|
assert repr(theDoc) == f"<NWDoc handle={xHandle}>"
|
||||||
|
|||||||
@@ -181,8 +181,8 @@ def testCoreIndex_CheckThese(nwMinimal, mockGUI):
|
|||||||
assert theProject.openProject(nwMinimal) is True
|
assert theProject.openProject(nwMinimal) is True
|
||||||
|
|
||||||
theIndex = NWIndex(theProject)
|
theIndex = NWIndex(theProject)
|
||||||
nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c")
|
nHandle = theProject.newFile("Hello", "a508bb932959c")
|
||||||
cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3")
|
cHandle = theProject.newFile("Jane", "afb3043c7b2b3")
|
||||||
nItem = theProject.projTree[nHandle]
|
nItem = theProject.projTree[nHandle]
|
||||||
cItem = theProject.projTree[cHandle]
|
cItem = theProject.projTree[cHandle]
|
||||||
|
|
||||||
@@ -260,8 +260,8 @@ def testCoreIndex_ScanText(nwMinimal, mockGUI):
|
|||||||
theIndex = NWIndex(theProject)
|
theIndex = NWIndex(theProject)
|
||||||
|
|
||||||
# Some items for fail to scan tests
|
# Some items for fail to scan tests
|
||||||
dHandle = theProject.newFolder("Folder", nwItemClass.NOVEL, "a508bb932959c")
|
dHandle = theProject.newFolder("Folder", "a508bb932959c")
|
||||||
xHandle = theProject.newFile("No Layout", nwItemClass.NOVEL, "a508bb932959c")
|
xHandle = theProject.newFile("No Layout", "a508bb932959c")
|
||||||
xItem = theProject.projTree[xHandle]
|
xItem = theProject.projTree[xHandle]
|
||||||
xItem.setLayout(nwItemLayout.NO_LAYOUT)
|
xItem.setLayout(nwItemLayout.NO_LAYOUT)
|
||||||
|
|
||||||
@@ -278,20 +278,24 @@ def testCoreIndex_ScanText(nwMinimal, mockGUI):
|
|||||||
tHandle = theProject.trashFolder()
|
tHandle = theProject.trashFolder()
|
||||||
assert theProject.projTree[tHandle] is not None
|
assert theProject.projTree[tHandle] is not None
|
||||||
xItem.setParent(tHandle)
|
xItem.setParent(tHandle)
|
||||||
|
theProject.projTree.updateItemData(xItem.itemHandle)
|
||||||
|
assert xItem.itemRoot == tHandle
|
||||||
|
assert xItem.itemClass == nwItemClass.TRASH
|
||||||
assert theIndex.scanText(xHandle, "Hello World!") is False
|
assert theIndex.scanText(xHandle, "Hello World!") is False
|
||||||
|
|
||||||
# Create the archive root
|
# Create the archive root
|
||||||
aHandle = theProject.newRoot("Archive", nwItemClass.ARCHIVE)
|
aHandle = theProject.newRoot("Archive", nwItemClass.ARCHIVE)
|
||||||
assert theProject.projTree[aHandle] is not None
|
assert theProject.projTree[aHandle] is not None
|
||||||
xItem.setParent(aHandle)
|
xItem.setParent(aHandle)
|
||||||
|
theProject.projTree.updateItemData(xItem.itemHandle)
|
||||||
assert theIndex.scanText(xHandle, "Hello World!") is False
|
assert theIndex.scanText(xHandle, "Hello World!") is False
|
||||||
|
|
||||||
# Make some usable items
|
# Make some usable items
|
||||||
tHandle = theProject.newFile("Title", nwItemClass.NOVEL, "a508bb932959c")
|
tHandle = theProject.newFile("Title", "a508bb932959c")
|
||||||
pHandle = theProject.newFile("Page", nwItemClass.NOVEL, "a508bb932959c")
|
pHandle = theProject.newFile("Page", "a508bb932959c")
|
||||||
nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c")
|
nHandle = theProject.newFile("Hello", "a508bb932959c")
|
||||||
cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3")
|
cHandle = theProject.newFile("Jane", "afb3043c7b2b3")
|
||||||
sHandle = theProject.newFile("Scene", nwItemClass.NOVEL, "a508bb932959c")
|
sHandle = theProject.newFile("Scene", "a508bb932959c")
|
||||||
|
|
||||||
# Text Indexing
|
# Text Indexing
|
||||||
# =============
|
# =============
|
||||||
@@ -473,8 +477,8 @@ def testCoreIndex_ExtractData(nwMinimal, mockGUI):
|
|||||||
assert theProject.openProject(nwMinimal) is True
|
assert theProject.openProject(nwMinimal) is True
|
||||||
|
|
||||||
theIndex = NWIndex(theProject)
|
theIndex = NWIndex(theProject)
|
||||||
nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c")
|
nHandle = theProject.newFile("Hello", "a508bb932959c")
|
||||||
cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3")
|
cHandle = theProject.newFile("Jane", "afb3043c7b2b3")
|
||||||
|
|
||||||
assert theIndex.getNovelData("", "") is None
|
assert theIndex.getNovelData("", "") is None
|
||||||
assert theIndex.getNovelData("a508bb932959c", "") is None
|
assert theIndex.getNovelData("a508bb932959c", "") is None
|
||||||
@@ -628,9 +632,9 @@ def testCoreIndex_ExtractData(nwMinimal, mockGUI):
|
|||||||
# Novel Stats
|
# Novel Stats
|
||||||
# ===========
|
# ===========
|
||||||
|
|
||||||
hHandle = theProject.newFile("Chapter", nwItemClass.NOVEL, "a508bb932959c")
|
hHandle = theProject.newFile("Chapter", "a508bb932959c")
|
||||||
sHandle = theProject.newFile("Scene One", nwItemClass.NOVEL, "a508bb932959c")
|
sHandle = theProject.newFile("Scene One", "a508bb932959c")
|
||||||
tHandle = theProject.newFile("Scene Two", nwItemClass.NOVEL, "a508bb932959c")
|
tHandle = theProject.newFile("Scene Two", "a508bb932959c")
|
||||||
|
|
||||||
theProject.projTree[hHandle].itemLayout == nwItemLayout.DOCUMENT
|
theProject.projTree[hHandle].itemLayout == nwItemLayout.DOCUMENT
|
||||||
theProject.projTree[sHandle].itemLayout == nwItemLayout.DOCUMENT
|
theProject.projTree[sHandle].itemLayout == nwItemLayout.DOCUMENT
|
||||||
|
|||||||
@@ -71,6 +71,18 @@ def testCoreItem_Setters(mockGUI, constData):
|
|||||||
theItem.setParent("0123456789abc")
|
theItem.setParent("0123456789abc")
|
||||||
assert theItem.itemParent == "0123456789abc"
|
assert theItem.itemParent == "0123456789abc"
|
||||||
|
|
||||||
|
# Root
|
||||||
|
theItem.setRoot(None)
|
||||||
|
assert theItem.itemRoot is None
|
||||||
|
theItem.setRoot(123)
|
||||||
|
assert theItem.itemRoot is None
|
||||||
|
theItem.setRoot("0123456789abcdef")
|
||||||
|
assert theItem.itemRoot is None
|
||||||
|
theItem.setRoot("0123456789abg")
|
||||||
|
assert theItem.itemRoot is None
|
||||||
|
theItem.setRoot("0123456789abc")
|
||||||
|
assert theItem.itemRoot == "0123456789abc"
|
||||||
|
|
||||||
# Order
|
# Order
|
||||||
theItem.setOrder(None)
|
theItem.setOrder(None)
|
||||||
assert theItem.itemOrder == 0
|
assert theItem.itemOrder == 0
|
||||||
@@ -208,6 +220,7 @@ def testCoreItem_Methods(mockGUI):
|
|||||||
|
|
||||||
# Status + Icon
|
# Status + Icon
|
||||||
# =============
|
# =============
|
||||||
|
|
||||||
theItem.setType("FILE")
|
theItem.setType("FILE")
|
||||||
theItem.setStatus("Note")
|
theItem.setStatus("Note")
|
||||||
theItem.setImport("Minor")
|
theItem.setImport("Minor")
|
||||||
@@ -217,11 +230,19 @@ def testCoreItem_Methods(mockGUI):
|
|||||||
assert stT == "Note"
|
assert stT == "Note"
|
||||||
assert isinstance(stI, QIcon)
|
assert isinstance(stI, QIcon)
|
||||||
|
|
||||||
|
theItem.setImportStatus("Draft")
|
||||||
|
stT, stI = theItem.getImportStatus()
|
||||||
|
assert stT == "Draft"
|
||||||
|
|
||||||
theItem.setClass("CHARACTER")
|
theItem.setClass("CHARACTER")
|
||||||
stT, stI = theItem.getImportStatus()
|
stT, stI = theItem.getImportStatus()
|
||||||
assert stT == "Minor"
|
assert stT == "Minor"
|
||||||
assert isinstance(stI, QIcon)
|
assert isinstance(stI, QIcon)
|
||||||
|
|
||||||
|
theItem.setImportStatus("Major")
|
||||||
|
stT, stI = theItem.getImportStatus()
|
||||||
|
assert stT == "Major"
|
||||||
|
|
||||||
# Representation
|
# Representation
|
||||||
# ==============
|
# ==============
|
||||||
|
|
||||||
@@ -263,6 +284,8 @@ def testCoreItem_TypeSetter(mockGUI):
|
|||||||
assert theItem.itemType == nwItemType.FILE
|
assert theItem.itemType == nwItemType.FILE
|
||||||
theItem.setType("TRASH")
|
theItem.setType("TRASH")
|
||||||
assert theItem.itemType == nwItemType.TRASH
|
assert theItem.itemType == nwItemType.TRASH
|
||||||
|
|
||||||
|
# Alternative
|
||||||
theItem.setType(nwItemType.ROOT)
|
theItem.setType(nwItemType.ROOT)
|
||||||
assert theItem.itemType == nwItemType.ROOT
|
assert theItem.itemType == nwItemType.ROOT
|
||||||
|
|
||||||
@@ -282,28 +305,74 @@ def testCoreItem_ClassSetter(mockGUI):
|
|||||||
assert theItem.itemClass == nwItemClass.NO_CLASS
|
assert theItem.itemClass == nwItemClass.NO_CLASS
|
||||||
theItem.setClass("NONSENSE")
|
theItem.setClass("NONSENSE")
|
||||||
assert theItem.itemClass == nwItemClass.NO_CLASS
|
assert theItem.itemClass == nwItemClass.NO_CLASS
|
||||||
|
|
||||||
theItem.setClass("NO_CLASS")
|
theItem.setClass("NO_CLASS")
|
||||||
assert theItem.itemClass == nwItemClass.NO_CLASS
|
assert theItem.itemClass == nwItemClass.NO_CLASS
|
||||||
|
assert theItem.isNovelLike() is False
|
||||||
|
assert theItem.documentAllowed() is False
|
||||||
|
assert theItem.isInactive() is True
|
||||||
|
|
||||||
theItem.setClass("NOVEL")
|
theItem.setClass("NOVEL")
|
||||||
assert theItem.itemClass == nwItemClass.NOVEL
|
assert theItem.itemClass == nwItemClass.NOVEL
|
||||||
|
assert theItem.isNovelLike() is True
|
||||||
|
assert theItem.documentAllowed() is True
|
||||||
|
assert theItem.isInactive() is False
|
||||||
|
|
||||||
theItem.setClass("PLOT")
|
theItem.setClass("PLOT")
|
||||||
assert theItem.itemClass == nwItemClass.PLOT
|
assert theItem.itemClass == nwItemClass.PLOT
|
||||||
|
assert theItem.isNovelLike() is False
|
||||||
|
assert theItem.documentAllowed() is False
|
||||||
|
assert theItem.isInactive() is False
|
||||||
|
|
||||||
theItem.setClass("CHARACTER")
|
theItem.setClass("CHARACTER")
|
||||||
assert theItem.itemClass == nwItemClass.CHARACTER
|
assert theItem.itemClass == nwItemClass.CHARACTER
|
||||||
|
assert theItem.isNovelLike() is False
|
||||||
|
assert theItem.documentAllowed() is False
|
||||||
|
assert theItem.isInactive() is False
|
||||||
|
|
||||||
theItem.setClass("WORLD")
|
theItem.setClass("WORLD")
|
||||||
assert theItem.itemClass == nwItemClass.WORLD
|
assert theItem.itemClass == nwItemClass.WORLD
|
||||||
|
assert theItem.isNovelLike() is False
|
||||||
|
assert theItem.documentAllowed() is False
|
||||||
|
assert theItem.isInactive() is False
|
||||||
|
|
||||||
theItem.setClass("TIMELINE")
|
theItem.setClass("TIMELINE")
|
||||||
assert theItem.itemClass == nwItemClass.TIMELINE
|
assert theItem.itemClass == nwItemClass.TIMELINE
|
||||||
|
assert theItem.isNovelLike() is False
|
||||||
|
assert theItem.documentAllowed() is False
|
||||||
|
assert theItem.isInactive() is False
|
||||||
|
|
||||||
theItem.setClass("OBJECT")
|
theItem.setClass("OBJECT")
|
||||||
assert theItem.itemClass == nwItemClass.OBJECT
|
assert theItem.itemClass == nwItemClass.OBJECT
|
||||||
|
assert theItem.isNovelLike() is False
|
||||||
|
assert theItem.documentAllowed() is False
|
||||||
|
assert theItem.isInactive() is False
|
||||||
|
|
||||||
theItem.setClass("ENTITY")
|
theItem.setClass("ENTITY")
|
||||||
assert theItem.itemClass == nwItemClass.ENTITY
|
assert theItem.itemClass == nwItemClass.ENTITY
|
||||||
|
assert theItem.isNovelLike() is False
|
||||||
|
assert theItem.documentAllowed() is False
|
||||||
|
assert theItem.isInactive() is False
|
||||||
|
|
||||||
theItem.setClass("CUSTOM")
|
theItem.setClass("CUSTOM")
|
||||||
assert theItem.itemClass == nwItemClass.CUSTOM
|
assert theItem.itemClass == nwItemClass.CUSTOM
|
||||||
|
assert theItem.isNovelLike() is False
|
||||||
|
assert theItem.documentAllowed() is False
|
||||||
|
assert theItem.isInactive() is False
|
||||||
|
|
||||||
theItem.setClass("ARCHIVE")
|
theItem.setClass("ARCHIVE")
|
||||||
assert theItem.itemClass == nwItemClass.ARCHIVE
|
assert theItem.itemClass == nwItemClass.ARCHIVE
|
||||||
|
assert theItem.isNovelLike() is True
|
||||||
|
assert theItem.documentAllowed() is True
|
||||||
|
assert theItem.isInactive() is True
|
||||||
|
|
||||||
theItem.setClass("TRASH")
|
theItem.setClass("TRASH")
|
||||||
assert theItem.itemClass == nwItemClass.TRASH
|
assert theItem.itemClass == nwItemClass.TRASH
|
||||||
|
assert theItem.isNovelLike() is False
|
||||||
|
assert theItem.documentAllowed() is True
|
||||||
|
assert theItem.isInactive() is True
|
||||||
|
|
||||||
|
# Alternative
|
||||||
theItem.setClass(nwItemClass.NOVEL)
|
theItem.setClass(nwItemClass.NOVEL)
|
||||||
assert theItem.itemClass == nwItemClass.NOVEL
|
assert theItem.itemClass == nwItemClass.NOVEL
|
||||||
|
|
||||||
@@ -332,13 +401,69 @@ def testCoreItem_LayoutSetter(mockGUI):
|
|||||||
theItem.setLayout("NOTE")
|
theItem.setLayout("NOTE")
|
||||||
assert theItem.itemLayout == nwItemLayout.NOTE
|
assert theItem.itemLayout == nwItemLayout.NOTE
|
||||||
|
|
||||||
# Alternatives
|
# Alternative
|
||||||
theItem.setLayout(nwItemLayout.NOTE)
|
theItem.setLayout(nwItemLayout.NOTE)
|
||||||
assert theItem.itemLayout == nwItemLayout.NOTE
|
assert theItem.itemLayout == nwItemLayout.NOTE
|
||||||
|
|
||||||
# END Test testCoreItem_LayoutSetter
|
# END Test testCoreItem_LayoutSetter
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testCoreItem_ClassDefaults(mockGUI):
|
||||||
|
"""Test the setter for the default values.
|
||||||
|
"""
|
||||||
|
theProject = NWProject(mockGUI)
|
||||||
|
theItem = NWItem(theProject)
|
||||||
|
|
||||||
|
# Root items should not have their class updated
|
||||||
|
theItem.setParent(None)
|
||||||
|
theItem.setClass(nwItemClass.NO_CLASS)
|
||||||
|
assert theItem.itemClass == nwItemClass.NO_CLASS
|
||||||
|
|
||||||
|
theItem.setClassDefaults(nwItemClass.NOVEL)
|
||||||
|
assert theItem.itemClass == nwItemClass.NO_CLASS
|
||||||
|
|
||||||
|
# Non-root items should have their class updated
|
||||||
|
theItem.setParent("0123456789abc")
|
||||||
|
theItem.setClass(nwItemClass.NO_CLASS)
|
||||||
|
assert theItem.itemClass == nwItemClass.NO_CLASS
|
||||||
|
|
||||||
|
theItem.setClassDefaults(nwItemClass.NOVEL)
|
||||||
|
assert theItem.itemClass == nwItemClass.NOVEL
|
||||||
|
|
||||||
|
# Non-layout items should have their layout set based on class
|
||||||
|
theItem.setParent("0123456789abc")
|
||||||
|
theItem.setClass(nwItemClass.NO_CLASS)
|
||||||
|
theItem.setLayout(nwItemLayout.NO_LAYOUT)
|
||||||
|
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
|
||||||
|
|
||||||
|
theItem.setClassDefaults(nwItemClass.NOVEL)
|
||||||
|
assert theItem.itemLayout == nwItemLayout.DOCUMENT
|
||||||
|
|
||||||
|
theItem.setParent("0123456789abc")
|
||||||
|
theItem.setClass(nwItemClass.NO_CLASS)
|
||||||
|
theItem.setLayout(nwItemLayout.NO_LAYOUT)
|
||||||
|
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
|
||||||
|
|
||||||
|
theItem.setClassDefaults(nwItemClass.PLOT)
|
||||||
|
assert theItem.itemLayout == nwItemLayout.NOTE
|
||||||
|
|
||||||
|
# If documents are not allowed in that class, the layout should be changed
|
||||||
|
theItem.setParent("0123456789abc")
|
||||||
|
theItem.setClass(nwItemClass.NO_CLASS)
|
||||||
|
theItem.setLayout(nwItemLayout.DOCUMENT)
|
||||||
|
assert theItem.itemLayout == nwItemLayout.DOCUMENT
|
||||||
|
|
||||||
|
theItem.setClassDefaults(nwItemClass.PLOT)
|
||||||
|
assert theItem.itemLayout == nwItemLayout.NOTE
|
||||||
|
|
||||||
|
# In all cases, status and importance should no longer be None
|
||||||
|
assert theItem.itemStatus is not None
|
||||||
|
assert theItem.itemImport is not None
|
||||||
|
|
||||||
|
# END Test testCoreItem_ClassDefaults
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testCoreItem_XMLPackUnpack(mockGUI, caplog, constData):
|
def testCoreItem_XMLPackUnpack(mockGUI, caplog, constData):
|
||||||
"""Test packing and unpacking XML objects for the NWItem class.
|
"""Test packing and unpacking XML objects for the NWItem class.
|
||||||
@@ -353,6 +478,7 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog, constData):
|
|||||||
theItem = NWItem(theProject)
|
theItem = NWItem(theProject)
|
||||||
theItem.setHandle("0123456789abc")
|
theItem.setHandle("0123456789abc")
|
||||||
theItem.setParent("0123456789abc")
|
theItem.setParent("0123456789abc")
|
||||||
|
theItem.setRoot("0123456789abc")
|
||||||
theItem.setOrder(1)
|
theItem.setOrder(1)
|
||||||
theItem.setName("A Name")
|
theItem.setName("A Name")
|
||||||
theItem.setClass("NOVEL")
|
theItem.setClass("NOVEL")
|
||||||
@@ -370,9 +496,9 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog, constData):
|
|||||||
theItem.packXML(xContent)
|
theItem.packXML(xContent)
|
||||||
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
|
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
|
||||||
b'<content>'
|
b'<content>'
|
||||||
b'<item handle="0123456789abc" parent="0123456789abc" order="1" type="FILE" class="NOVEL" '
|
b'<item handle="0123456789abc" parent="0123456789abc" root="0123456789abc" order="1" '
|
||||||
b'layout="NOTE"><meta charCount="7" wordCount="5" paraCount="3" cursorPos="11"/>'
|
b'type="FILE" class="NOVEL" layout="NOTE"><meta charCount="7" wordCount="5" paraCount="3" '
|
||||||
b'<name status="None" import="%s" exported="False">A Name</name></item>'
|
b'cursorPos="11"/><name status="None" import="%s" exported="False">A Name</name></item>'
|
||||||
b'</content>'
|
b'</content>'
|
||||||
) % bytes(constData.importKeys[3], encoding="utf8")
|
) % bytes(constData.importKeys[3], encoding="utf8")
|
||||||
|
|
||||||
@@ -381,6 +507,7 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog, constData):
|
|||||||
assert theItem.unpackXML(xContent[0])
|
assert theItem.unpackXML(xContent[0])
|
||||||
assert theItem.itemHandle == "0123456789abc"
|
assert theItem.itemHandle == "0123456789abc"
|
||||||
assert theItem.itemParent == "0123456789abc"
|
assert theItem.itemParent == "0123456789abc"
|
||||||
|
assert theItem.itemRoot == "0123456789abc"
|
||||||
assert theItem.itemOrder == 1
|
assert theItem.itemOrder == 1
|
||||||
assert theItem.isExported is False
|
assert theItem.isExported is False
|
||||||
assert theItem.paraCount == 3
|
assert theItem.paraCount == 3
|
||||||
@@ -399,6 +526,7 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog, constData):
|
|||||||
theItem = NWItem(theProject)
|
theItem = NWItem(theProject)
|
||||||
theItem.setHandle("0123456789abc")
|
theItem.setHandle("0123456789abc")
|
||||||
theItem.setParent("0123456789abc")
|
theItem.setParent("0123456789abc")
|
||||||
|
theItem.setRoot("0123456789abc")
|
||||||
theItem.setOrder(1)
|
theItem.setOrder(1)
|
||||||
theItem.setName("A Name")
|
theItem.setName("A Name")
|
||||||
theItem.setClass("NOVEL")
|
theItem.setClass("NOVEL")
|
||||||
@@ -417,9 +545,9 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog, constData):
|
|||||||
theItem.packXML(xContent)
|
theItem.packXML(xContent)
|
||||||
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
|
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
|
||||||
b'<content>'
|
b'<content>'
|
||||||
b'<item handle="0123456789abc" parent="0123456789abc" order="1" type="FOLDER" '
|
b'<item handle="0123456789abc" parent="0123456789abc" root="0123456789abc" order="1" '
|
||||||
b'class="NOVEL"><meta expanded="True"/><name status="%s" import="None">A Name</name>'
|
b'type="FOLDER" class="NOVEL"><meta expanded="True"/><name status="%s" '
|
||||||
b'</item>'
|
b'import="None">A Name</name></item>'
|
||||||
b'</content>'
|
b'</content>'
|
||||||
) % bytes(constData.statusKeys[1], encoding="utf8")
|
) % bytes(constData.statusKeys[1], encoding="utf8")
|
||||||
|
|
||||||
@@ -428,6 +556,7 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog, constData):
|
|||||||
assert theItem.unpackXML(xContent[0])
|
assert theItem.unpackXML(xContent[0])
|
||||||
assert theItem.itemHandle == "0123456789abc"
|
assert theItem.itemHandle == "0123456789abc"
|
||||||
assert theItem.itemParent == "0123456789abc"
|
assert theItem.itemParent == "0123456789abc"
|
||||||
|
assert theItem.itemRoot == "0123456789abc"
|
||||||
assert theItem.itemOrder == 1
|
assert theItem.itemOrder == 1
|
||||||
assert theItem.isExpanded is True
|
assert theItem.isExpanded is True
|
||||||
assert theItem.isExported is True
|
assert theItem.isExported is True
|
||||||
|
|||||||
@@ -276,10 +276,10 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI):
|
|||||||
assert theProject.closeProject() is True
|
assert theProject.closeProject() is True
|
||||||
assert theProject.openProject(projFile) is True
|
assert theProject.openProject(projFile) is True
|
||||||
|
|
||||||
assert isinstance(theProject.newRoot("Novel", nwItemClass.NOVEL), type(None))
|
assert isinstance(theProject.newRoot("Novel", nwItemClass.NOVEL), str)
|
||||||
assert isinstance(theProject.newRoot("Plot", nwItemClass.PLOT), type(None))
|
assert isinstance(theProject.newRoot("Plot", nwItemClass.PLOT), str)
|
||||||
assert isinstance(theProject.newRoot("Character", nwItemClass.CHARACTER), type(None))
|
assert isinstance(theProject.newRoot("Character", nwItemClass.CHARACTER), str)
|
||||||
assert isinstance(theProject.newRoot("World", nwItemClass.WORLD), type(None))
|
assert isinstance(theProject.newRoot("World", nwItemClass.WORLD), str)
|
||||||
assert isinstance(theProject.newRoot("Timeline", nwItemClass.TIMELINE), str)
|
assert isinstance(theProject.newRoot("Timeline", nwItemClass.TIMELINE), str)
|
||||||
assert isinstance(theProject.newRoot("Object", nwItemClass.OBJECT), str)
|
assert isinstance(theProject.newRoot("Object", nwItemClass.OBJECT), str)
|
||||||
assert isinstance(theProject.newRoot("Custom1", nwItemClass.CUSTOM), str)
|
assert isinstance(theProject.newRoot("Custom1", nwItemClass.CUSTOM), str)
|
||||||
@@ -314,8 +314,8 @@ def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI):
|
|||||||
assert theProject.closeProject() is True
|
assert theProject.closeProject() is True
|
||||||
assert theProject.openProject(projFile) is True
|
assert theProject.openProject(projFile) is True
|
||||||
|
|
||||||
assert isinstance(theProject.newFile("Hello", nwItemClass.NOVEL, "31489056e0916"), str)
|
assert isinstance(theProject.newFile("Hello", "31489056e0916"), str)
|
||||||
assert isinstance(theProject.newFile("Jane", nwItemClass.CHARACTER, "71ee45a3c0db9"), str)
|
assert isinstance(theProject.newFile("Jane", "71ee45a3c0db9"), str)
|
||||||
assert theProject.projChanged
|
assert theProject.projChanged
|
||||||
assert theProject.saveProject() is True
|
assert theProject.saveProject() is True
|
||||||
assert theProject.closeProject() is True
|
assert theProject.closeProject() is True
|
||||||
@@ -661,7 +661,7 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
|
|||||||
theProject.projTree._treeOrder.append("01234567789abc")
|
theProject.projTree._treeOrder.append("01234567789abc")
|
||||||
|
|
||||||
# Add an item with a non-existent parent
|
# Add an item with a non-existent parent
|
||||||
nHandle = theProject.newFile("Test File", nwItemClass.NOVEL, "a6d311a93600a")
|
nHandle = theProject.newFile("Test File", "a6d311a93600a")
|
||||||
theProject.projTree[nHandle].setParent("cba9876543210")
|
theProject.projTree[nHandle].setParent("cba9876543210")
|
||||||
assert theProject.projTree[nHandle].itemParent == "cba9876543210"
|
assert theProject.projTree[nHandle].itemParent == "cba9876543210"
|
||||||
|
|
||||||
@@ -740,7 +740,7 @@ def testCoreProject_StatusImport(mockGUI, fncDir, constData):
|
|||||||
# Change Importance
|
# Change Importance
|
||||||
# =================
|
# =================
|
||||||
|
|
||||||
fHandle = theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "73475cb40a568")
|
fHandle = theProject.newFile("Jane Doe", "73475cb40a568")
|
||||||
theProject.projTree[fHandle].setImport("Main")
|
theProject.projTree[fHandle].setImport("Main")
|
||||||
|
|
||||||
assert theProject.projTree[fHandle].itemImport == constData.importKeys[3]
|
assert theProject.projTree[fHandle].itemImport == constData.importKeys[3]
|
||||||
@@ -1016,9 +1016,16 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
|
|||||||
"""
|
"""
|
||||||
theProject = NWProject(mockGUI)
|
theProject = NWProject(mockGUI)
|
||||||
|
|
||||||
assert theProject.openProject(nwLipsum)
|
assert theProject.openProject(nwLipsum) is True
|
||||||
assert theProject.projTree["636b6aa9b697b"] is None
|
assert theProject.projTree["636b6aa9b697b"] is None
|
||||||
assert theProject.closeProject()
|
|
||||||
|
# Add a file with non-existent parent
|
||||||
|
# This file will be renoved from the project on open
|
||||||
|
assert theProject.newFile("Oops", "0000000000000")
|
||||||
|
|
||||||
|
# Save and close
|
||||||
|
assert theProject.saveProject() is True
|
||||||
|
assert theProject.closeProject() is True
|
||||||
|
|
||||||
# First Item with Meta Data
|
# First Item with Meta Data
|
||||||
orphPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.nwd")
|
orphPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.nwd")
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import pytest
|
import pytest
|
||||||
|
import random
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
@@ -36,6 +37,7 @@ from novelwriter.constants import nwFiles
|
|||||||
def mockItems(mockGUI):
|
def mockItems(mockGUI):
|
||||||
"""Create a list of mock items.
|
"""Create a list of mock items.
|
||||||
"""
|
"""
|
||||||
|
random.seed(42)
|
||||||
theProject = NWProject(mockGUI)
|
theProject = NWProject(mockGUI)
|
||||||
|
|
||||||
itemA = NWItem(theProject)
|
itemA = NWItem(theProject)
|
||||||
@@ -103,7 +105,7 @@ def mockItems(mockGUI):
|
|||||||
("a000000000002", None, itemE),
|
("a000000000002", None, itemE),
|
||||||
("a000000000003", None, itemF),
|
("a000000000003", None, itemF),
|
||||||
("a000000000004", None, itemG),
|
("a000000000004", None, itemG),
|
||||||
("b000000000002", "a000000000002", itemH),
|
("b000000000002", "a000000000004", itemH),
|
||||||
]
|
]
|
||||||
|
|
||||||
return theItems
|
return theItems
|
||||||
@@ -120,22 +122,21 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
|
|||||||
assert theTree._handleSeed == 42
|
assert theTree._handleSeed == 42
|
||||||
|
|
||||||
# Check that tree is empty (calls NWTree.__bool__)
|
# Check that tree is empty (calls NWTree.__bool__)
|
||||||
assert not theTree
|
assert bool(theTree) is False
|
||||||
|
|
||||||
# Check for archive and trash folders
|
# Check for archive and trash folders
|
||||||
assert theTree.trashRoot() is None
|
assert theTree.trashRoot() is None
|
||||||
assert theTree.archiveRoot() is None
|
|
||||||
assert not theTree.isTrashRoot("a000000000003")
|
|
||||||
|
|
||||||
aHandles = []
|
aHandles = []
|
||||||
for tHandle, pHandle, nwItem in mockItems:
|
for tHandle, pHandle, nwItem in mockItems:
|
||||||
aHandles.append(tHandle)
|
aHandles.append(tHandle)
|
||||||
assert theTree.append(tHandle, pHandle, nwItem)
|
assert theTree.append(tHandle, pHandle, nwItem) is True
|
||||||
|
assert theTree.updateItemData(tHandle) is True
|
||||||
|
|
||||||
assert theTree._treeChanged
|
assert theTree._treeChanged is True
|
||||||
|
|
||||||
# Check that tree is not empty (calls __bool__)
|
# Check that tree is not empty (calls __bool__)
|
||||||
assert theTree
|
assert bool(theTree) is True
|
||||||
|
|
||||||
# Check the number of elements (calls __len__)
|
# Check the number of elements (calls __len__)
|
||||||
assert len(theTree) == len(mockItems)
|
assert len(theTree) == len(mockItems)
|
||||||
@@ -149,8 +150,29 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
|
|||||||
|
|
||||||
# Check that we have the correct archive and trash folders
|
# Check that we have the correct archive and trash folders
|
||||||
assert theTree.trashRoot() == "a000000000003"
|
assert theTree.trashRoot() == "a000000000003"
|
||||||
assert theTree.archiveRoot() == "a000000000002"
|
assert theTree.findRoot(nwItemClass.ARCHIVE) == "a000000000002"
|
||||||
assert theTree.isTrashRoot("a000000000003")
|
assert theTree.isTrash("a000000000003") is True
|
||||||
|
assert theTree.isRoot("a000000000002") is True
|
||||||
|
|
||||||
|
# Check the isTrash function
|
||||||
|
assert theTree.isTrash("0000000000000") is True # Doesn't exist
|
||||||
|
assert theTree.isTrash("a000000000003") is True # This the trash folder
|
||||||
|
|
||||||
|
theTree["a000000000003"].setClass(nwItemClass.NO_CLASS)
|
||||||
|
assert theTree.isTrash("a000000000003") is True # This is still trash
|
||||||
|
theTree["a000000000003"].setClass(nwItemClass.TRASH)
|
||||||
|
|
||||||
|
assert theTree.isTrash("b000000000002") is False # This is not trash
|
||||||
|
|
||||||
|
value = theTree["b000000000002"].itemParent
|
||||||
|
theTree["b000000000002"].setParent("a000000000003")
|
||||||
|
assert theTree.isTrash("b000000000002") is True # This is in trash
|
||||||
|
theTree["b000000000002"].setParent(value)
|
||||||
|
|
||||||
|
value = theTree["b000000000002"].itemRoot
|
||||||
|
theTree["b000000000002"].setRoot("a000000000003")
|
||||||
|
assert theTree.isTrash("b000000000002") is True # This is in trash
|
||||||
|
theTree["b000000000002"].setRoot(value)
|
||||||
|
|
||||||
# Try to add another trash folder
|
# Try to add another trash folder
|
||||||
itemT = NWItem(theProject)
|
itemT = NWItem(theProject)
|
||||||
@@ -159,7 +181,7 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
|
|||||||
itemT._class = nwItemClass.TRASH
|
itemT._class = nwItemClass.TRASH
|
||||||
itemT._expanded = False
|
itemT._expanded = False
|
||||||
|
|
||||||
assert not theTree.append("1234567890abc", None, itemT)
|
assert theTree.append("1234567890abc", None, itemT) is False
|
||||||
assert len(theTree) == len(mockItems)
|
assert len(theTree) == len(mockItems)
|
||||||
|
|
||||||
# Generate handle automatically
|
# Generate handle automatically
|
||||||
@@ -169,14 +191,15 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
|
|||||||
itemT._class = nwItemClass.NOVEL
|
itemT._class = nwItemClass.NOVEL
|
||||||
itemT._layout = nwItemLayout.DOCUMENT
|
itemT._layout = nwItemLayout.DOCUMENT
|
||||||
|
|
||||||
assert theTree.append(None, None, itemT)
|
assert theTree.append(None, None, itemT) is True
|
||||||
|
assert theTree.updateItemData(itemT.itemHandle) is True
|
||||||
assert len(theTree) == len(mockItems) + 1
|
assert len(theTree) == len(mockItems) + 1
|
||||||
|
|
||||||
theList = theTree.handles()
|
theList = theTree.handles()
|
||||||
assert theList[-1] == "73475cb40a568"
|
assert theList[-1] == "73475cb40a568"
|
||||||
|
|
||||||
# Try to add existing handle
|
# Try to add existing handle
|
||||||
assert not theTree.append("73475cb40a568", None, itemT)
|
assert theTree.append("73475cb40a568", None, itemT) is False
|
||||||
assert len(theTree) == len(mockItems) + 1
|
assert len(theTree) == len(mockItems) + 1
|
||||||
|
|
||||||
# Delete a non-existing item
|
# Delete a non-existing item
|
||||||
@@ -196,7 +219,6 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
|
|||||||
del theTree["a000000000002"]
|
del theTree["a000000000002"]
|
||||||
assert len(theTree) == len(mockItems) - 2
|
assert len(theTree) == len(mockItems) - 2
|
||||||
assert "a000000000002" not in theTree
|
assert "a000000000002" not in theTree
|
||||||
assert theTree.archiveRoot() is None
|
|
||||||
|
|
||||||
del theTree["a000000000003"]
|
del theTree["a000000000003"]
|
||||||
assert len(theTree) == len(mockItems) - 3
|
assert len(theTree) == len(mockItems) - 3
|
||||||
@@ -215,31 +237,43 @@ def testCoreTree_Methods(mockGUI, mockItems):
|
|||||||
|
|
||||||
for tHandle, pHandle, nwItem in mockItems:
|
for tHandle, pHandle, nwItem in mockItems:
|
||||||
theTree.append(tHandle, pHandle, nwItem)
|
theTree.append(tHandle, pHandle, nwItem)
|
||||||
|
theTree.updateItemData(tHandle)
|
||||||
|
|
||||||
assert len(theTree) == len(mockItems)
|
assert len(theTree) == len(mockItems)
|
||||||
|
|
||||||
|
# Update item data, nonsense handle
|
||||||
|
assert theTree.updateItemData("stuff") is False
|
||||||
|
|
||||||
|
# Update item data, invalid item parent
|
||||||
|
corrParent = theTree["b000000000001"].itemParent
|
||||||
|
theTree["b000000000001"].setParent("0000000000000")
|
||||||
|
assert theTree.updateItemData("b000000000001") is False
|
||||||
|
|
||||||
|
# Update item data, valid item parent
|
||||||
|
theTree["b000000000001"].setParent(corrParent)
|
||||||
|
assert theTree.updateItemData("b000000000001") is True
|
||||||
|
|
||||||
|
# Update item data, root is unreachable
|
||||||
|
maxDepth = theTree.MAX_DEPTH
|
||||||
|
theTree.MAX_DEPTH = 0
|
||||||
|
with pytest.raises(RecursionError):
|
||||||
|
theTree.updateItemData("b000000000001")
|
||||||
|
theTree.MAX_DEPTH = maxDepth
|
||||||
|
|
||||||
# Chech type
|
# Chech type
|
||||||
assert theTree.checkType("blabla", nwItemType.FILE) is False
|
assert theTree.checkType("blabla", nwItemType.FILE) is False
|
||||||
assert theTree.checkType("b000000000001", nwItemType.FILE) is False
|
assert theTree.checkType("b000000000001", nwItemType.FILE) is False
|
||||||
assert theTree.checkType("c000000000001", nwItemType.FILE) is True
|
assert theTree.checkType("c000000000001", nwItemType.FILE) is True
|
||||||
|
|
||||||
# Root item lookup
|
# Root item lookup
|
||||||
theTree._treeRoots.append("stuff")
|
|
||||||
assert theTree.findRoot(nwItemClass.WORLD) is None
|
assert theTree.findRoot(nwItemClass.WORLD) is None
|
||||||
assert theTree.findRoot(nwItemClass.NOVEL) == "a000000000001"
|
assert theTree.findRoot(nwItemClass.NOVEL) == "a000000000001"
|
||||||
assert theTree.findRoot(nwItemClass.CHARACTER) == "a000000000004"
|
assert theTree.findRoot(nwItemClass.CHARACTER) == "a000000000004"
|
||||||
|
|
||||||
# Check for root uniqueness
|
# Add a fake item to root and check that it can handle it
|
||||||
assert theTree.checkRootUnique(nwItemClass.CUSTOM)
|
theTree._treeRoots["0000000000000"] = NWItem(theProject)
|
||||||
assert theTree.checkRootUnique(nwItemClass.WORLD)
|
assert theTree.findRoot(nwItemClass.WORLD) is None
|
||||||
assert not theTree.checkRootUnique(nwItemClass.NOVEL)
|
del theTree._treeRoots["0000000000000"]
|
||||||
assert not theTree.checkRootUnique(nwItemClass.CHARACTER)
|
|
||||||
|
|
||||||
# Find root item of child item
|
|
||||||
assert theTree.getRootItem("b000000000001").itemHandle == "a000000000001"
|
|
||||||
assert theTree.getRootItem("c000000000001").itemHandle == "a000000000001"
|
|
||||||
assert theTree.getRootItem("c000000000002").itemHandle == "a000000000001"
|
|
||||||
assert theTree.getRootItem("stuff") is None
|
|
||||||
|
|
||||||
# Get item path
|
# Get item path
|
||||||
assert theTree.getItemPath("stuff") == []
|
assert theTree.getItemPath("stuff") == []
|
||||||
@@ -247,6 +281,13 @@ def testCoreTree_Methods(mockGUI, mockItems):
|
|||||||
"c000000000001", "b000000000001", "a000000000001"
|
"c000000000001", "b000000000001", "a000000000001"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Cause recursion error
|
||||||
|
maxDepth = theTree.MAX_DEPTH
|
||||||
|
theTree.MAX_DEPTH = 0
|
||||||
|
with pytest.raises(RecursionError):
|
||||||
|
theTree.getItemPath("c000000000001")
|
||||||
|
theTree.MAX_DEPTH = maxDepth
|
||||||
|
|
||||||
# Break the folder parent handle
|
# Break the folder parent handle
|
||||||
theTree["b000000000001"]._parent = "stuff"
|
theTree["b000000000001"]._parent = "stuff"
|
||||||
assert theTree.getItemPath("c000000000001") == [
|
assert theTree.getItemPath("c000000000001") == [
|
||||||
@@ -371,7 +412,7 @@ def testCoreTree_Reorder(mockGUI, mockItems):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testCoreTree_XMLPackUnpack(mockGUI, mockItems):
|
def testCoreTree_XMLPackUnpack(mockGUI, mockItems, constData):
|
||||||
"""Test packing and unpacking the tree to and from XML.
|
"""Test packing and unpacking the tree to and from XML.
|
||||||
"""
|
"""
|
||||||
theProject = NWProject(mockGUI)
|
theProject = NWProject(mockGUI)
|
||||||
@@ -379,37 +420,47 @@ def testCoreTree_XMLPackUnpack(mockGUI, mockItems):
|
|||||||
|
|
||||||
for tHandle, pHandle, nwItem in mockItems:
|
for tHandle, pHandle, nwItem in mockItems:
|
||||||
theTree.append(tHandle, pHandle, nwItem)
|
theTree.append(tHandle, pHandle, nwItem)
|
||||||
|
theTree.updateItemData(tHandle)
|
||||||
|
|
||||||
assert len(theTree) == len(mockItems)
|
assert len(theTree) == len(mockItems)
|
||||||
|
|
||||||
nwXML = etree.Element("novelWriterXML")
|
nwXML = etree.Element("novelWriterXML")
|
||||||
theTree.packXML(nwXML)
|
theTree.packXML(nwXML)
|
||||||
assert etree.tostring(nwXML, pretty_print=False, encoding="utf-8") == (
|
assert etree.tostring(nwXML, pretty_print=False, encoding="utf-8") == bytes((
|
||||||
b'<novelWriterXML>'
|
'<novelWriterXML>'
|
||||||
b'<content count="8">'
|
'<content count="8">'
|
||||||
b'<item handle="a000000000001" parent="None" order="0" type="ROOT" class="NOVEL"><meta '
|
'<item handle="a000000000001" parent="None" root="a000000000001" order="0" type="ROOT" '
|
||||||
b'expanded="True"/><name status="None" import="None">Novel</name></item>'
|
'class="NOVEL"><meta expanded="True"/><name status="{s0}" '
|
||||||
b'<item handle="b000000000001" parent="a000000000001" order="0" type="FOLDER" '
|
'import="{i0}">Novel</name></item>'
|
||||||
b'class="NOVEL"><meta expanded="True"/><name status="None" import="None">Act One</name>'
|
'<item handle="b000000000001" parent="a000000000001" root="a000000000001" order="0" '
|
||||||
b'</item>'
|
'type="FOLDER" class="NOVEL"><meta expanded="True"/><name status="{s0}" '
|
||||||
b'<item handle="c000000000001" parent="b000000000001" order="0" type="FILE" class="NOVEL" '
|
'import="{i0}">Act One</name></item>'
|
||||||
b'layout="DOCUMENT"><meta charCount="300" wordCount="50" paraCount="2" cursorPos="0"/>'
|
'<item handle="c000000000001" parent="b000000000001" root="a000000000001" order="0" '
|
||||||
b'<name status="None" import="None" exported="True">Chapter One</name></item>'
|
'type="FILE" class="NOVEL" layout="DOCUMENT"><meta charCount="300" wordCount="50" '
|
||||||
b'<item handle="c000000000002" parent="b000000000001" order="0" type="FILE" class="NOVEL" '
|
'paraCount="2" cursorPos="0"/><name status="{s0}" import="{i0}" '
|
||||||
b'layout="DOCUMENT"><meta charCount="3000" wordCount="500" paraCount="20" cursorPos="0"/>'
|
'exported="True">Chapter One</name></item>'
|
||||||
b'<name status="None" import="None" exported="True">Scene One</name></item>'
|
'<item handle="c000000000002" parent="b000000000001" root="a000000000001" order="0" '
|
||||||
b'<item handle="a000000000002" parent="None" order="0" type="ROOT" class="ARCHIVE"><meta '
|
'type="FILE" class="NOVEL" layout="DOCUMENT"><meta charCount="3000" wordCount="500" '
|
||||||
b'expanded="False"/><name status="None" import="None">Outtakes</name></item>'
|
'paraCount="20" cursorPos="0"/><name status="{s0}" import="{i0}" '
|
||||||
b'<item handle="a000000000003" parent="None" order="0" type="TRASH" class="TRASH"><meta '
|
'exported="True">Scene One</name></item>'
|
||||||
b'expanded="False"/><name status="None" import="None">Trash</name></item>'
|
'<item handle="a000000000002" parent="None" root="a000000000002" order="0" type="ROOT" '
|
||||||
b'<item handle="a000000000004" parent="None" order="0" type="ROOT" class="CHARACTER">'
|
'class="ARCHIVE"><meta expanded="False"/><name status="{s0}" '
|
||||||
b'<meta expanded="True"/><name status="None" import="None">Characters</name></item>'
|
'import="{i0}">Outtakes</name></item>'
|
||||||
b'<item handle="b000000000002" parent="a000000000002" order="0" type="FILE" '
|
'<item handle="a000000000003" parent="None" root="a000000000003" order="0" type="TRASH" '
|
||||||
b'class="CHARACTER" layout="NOTE"><meta charCount="2000" wordCount="400" paraCount="16" '
|
'class="TRASH"><meta expanded="False"/><name status="{s0}" '
|
||||||
b'cursorPos="0"/><name status="None" import="None" exported="True">Jane Doe</name></item>'
|
'import="{i0}">Trash</name></item>'
|
||||||
b'</content>'
|
'<item handle="a000000000004" parent="None" root="a000000000004" order="0" type="ROOT" '
|
||||||
b'</novelWriterXML>'
|
'class="CHARACTER"><meta expanded="True"/><name status="{s0}" '
|
||||||
)
|
'import="{i0}">Characters</name></item>'
|
||||||
|
'<item handle="b000000000002" parent="a000000000004" root="a000000000004" order="0" '
|
||||||
|
'type="FILE" class="CHARACTER" layout="NOTE"><meta charCount="2000" wordCount="400" '
|
||||||
|
'paraCount="16" cursorPos="0"/><name status="{s0}" import="{i0}" '
|
||||||
|
'exported="True">Jane Doe</name></item>'
|
||||||
|
'</content>'
|
||||||
|
'</novelWriterXML>'
|
||||||
|
).format(
|
||||||
|
s0=constData.statusKeys[0], i0=constData.importKeys[0]
|
||||||
|
), encoding="utf8")
|
||||||
|
|
||||||
theTree.clear()
|
theTree.clear()
|
||||||
assert len(theTree) == 0
|
assert len(theTree) == 0
|
||||||
@@ -429,6 +480,7 @@ def testCoreTree_ToCFile(monkeypatch, mockGUI, mockItems, tmpDir):
|
|||||||
|
|
||||||
for tHandle, pHandle, nwItem in mockItems:
|
for tHandle, pHandle, nwItem in mockItems:
|
||||||
theTree.append(tHandle, pHandle, nwItem)
|
theTree.append(tHandle, pHandle, nwItem)
|
||||||
|
theTree.updateItemData(tHandle)
|
||||||
|
|
||||||
assert len(theTree) == len(mockItems)
|
assert len(theTree) == len(mockItems)
|
||||||
theTree._treeOrder.append("stuff")
|
theTree._treeOrder.append("stuff")
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj):
|
|||||||
nwGUI.switchFocus(nwWidget.TREE)
|
nwGUI.switchFocus(nwWidget.TREE)
|
||||||
nwGUI.treeView.clearSelection()
|
nwGUI.treeView.clearSelection()
|
||||||
nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
|
nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
|
||||||
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
|
nwGUI.treeView.newTreeItem(nwItemType.FILE)
|
||||||
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
|
nwGUI.treeView.newTreeItem(nwItemType.FILE)
|
||||||
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
|
nwGUI.treeView.newTreeItem(nwItemType.FILE)
|
||||||
|
|
||||||
assert nwGUI.saveProject() is True
|
assert nwGUI.saveProject() is True
|
||||||
assert nwGUI.closeProject() is True
|
assert nwGUI.closeProject() is True
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj):
|
|||||||
nwGUI.switchFocus(nwWidget.TREE)
|
nwGUI.switchFocus(nwWidget.TREE)
|
||||||
nwGUI.treeView.clearSelection()
|
nwGUI.treeView.clearSelection()
|
||||||
nwGUI.treeView._getTreeItem(hNovelRoot).setSelected(True)
|
nwGUI.treeView._getTreeItem(hNovelRoot).setSelected(True)
|
||||||
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
|
nwGUI.treeView.newTreeItem(nwItemType.FILE)
|
||||||
|
|
||||||
assert nwGUI.saveProject() is True
|
assert nwGUI.saveProject() is True
|
||||||
assert nwGUI.closeProject() is True
|
assert nwGUI.closeProject() is True
|
||||||
@@ -230,12 +230,6 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj):
|
|||||||
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
|
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
|
||||||
assert nwSplit._doSplit() is False
|
assert nwSplit._doSplit() is False
|
||||||
|
|
||||||
# Block folder creation by returning that the folder has a depth
|
|
||||||
# of 50 items in the tree
|
|
||||||
with monkeypatch.context() as mp:
|
|
||||||
mp.setattr(NWTree, "getItemPath", lambda *a: [""]*50)
|
|
||||||
assert nwSplit._doSplit() is False
|
|
||||||
|
|
||||||
# Clear the list
|
# Clear the list
|
||||||
nwSplit.listBox.clear()
|
nwSplit.listBox.clear()
|
||||||
assert nwSplit._doSplit() is False
|
assert nwSplit._doSplit() is False
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj, constData):
|
|||||||
itemEdit.show()
|
itemEdit.show()
|
||||||
|
|
||||||
# Check Existing Settings
|
# Check Existing Settings
|
||||||
assert itemEdit.editName.text() == "New File"
|
assert itemEdit.editName.text() == "New Note"
|
||||||
assert itemEdit.editStatus.currentData() == constData.importKeys[0]
|
assert itemEdit.editStatus.currentData() == constData.importKeys[0]
|
||||||
assert itemEdit.editLayout.currentData() == nwItemLayout.NOTE
|
assert itemEdit.editLayout.currentData() == nwItemLayout.NOTE
|
||||||
assert itemEdit.editExport.isChecked() is True
|
assert itemEdit.editExport.isChecked() is True
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from PyQt5.QtWidgets import QAction, QMessageBox, qApp
|
|||||||
|
|
||||||
from novelwriter.gui.doceditor import GuiDocEditor
|
from novelwriter.gui.doceditor import GuiDocEditor
|
||||||
from novelwriter.core import countWords
|
from novelwriter.core import countWords
|
||||||
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout
|
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemLayout
|
||||||
from novelwriter.constants import nwKeyWords, nwUnicode
|
from novelwriter.constants import nwKeyWords, nwUnicode
|
||||||
|
|
||||||
keyDelay = 2
|
keyDelay = 2
|
||||||
@@ -1143,7 +1143,7 @@ def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
|||||||
|
|
||||||
# Create Character
|
# Create Character
|
||||||
theText = "### Jane Doe\n\n@tag: Jane\n\n" + ipsumText[1] + "\n\n"
|
theText = "### Jane Doe\n\n@tag: Jane\n\n" + ipsumText[1] + "\n\n"
|
||||||
cHandle = nwGUI.theProject.newFile("Jane Doe", nwItemClass.CHARACTER, "afb3043c7b2b3")
|
cHandle = nwGUI.theProject.newFile("Jane Doe", "afb3043c7b2b3")
|
||||||
assert nwGUI.openDocument(cHandle) is True
|
assert nwGUI.openDocument(cHandle) is True
|
||||||
assert nwGUI.docEditor.replaceText(theText) is True
|
assert nwGUI.docEditor.replaceText(theText) is True
|
||||||
assert nwGUI.saveDocument() is True
|
assert nwGUI.saveDocument() is True
|
||||||
|
|||||||
+376
-180
@@ -22,9 +22,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
import pytest
|
import pytest
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from tools import writeFile
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QItemSelectionModel
|
|
||||||
from PyQt5.QtWidgets import QAction, QMessageBox
|
from PyQt5.QtWidgets import QAction, QMessageBox
|
||||||
|
|
||||||
from novelwriter.guimain import GuiMain
|
from novelwriter.guimain import GuiMain
|
||||||
@@ -33,207 +30,406 @@ from novelwriter.enum import nwItemType, nwItemClass
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
|
def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir):
|
||||||
"""Test adding and removing items from the project tree.
|
"""Test adding and removing items from the project tree.
|
||||||
"""
|
"""
|
||||||
# Block message box
|
# Block message box
|
||||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
|
||||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
|
||||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||||
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
|
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
|
||||||
|
|
||||||
nwGUI.theProject.projTree.setSeed(42)
|
|
||||||
nwTree = nwGUI.treeView
|
nwTree = nwGUI.treeView
|
||||||
|
|
||||||
##
|
# Try to add item with no project
|
||||||
# Add New Items
|
assert nwTree.newTreeItem(nwItemType.FILE) is False
|
||||||
##
|
|
||||||
|
|
||||||
# Try to add and move item with no project
|
# Create a project
|
||||||
assert not nwTree.newTreeItem(nwItemType.FILE, None)
|
nwGUI.theProject.projTree.setSeed(42)
|
||||||
assert not nwTree.moveTreeItem(1)
|
prjDir = os.path.join(fncDir, "project")
|
||||||
|
assert nwGUI.newProject({"projPath": prjDir}) is True
|
||||||
|
|
||||||
# Open a project
|
# No itemType set
|
||||||
assert nwGUI.openProject(nwMinimal)
|
nwTree.clearSelection()
|
||||||
|
assert nwTree.newTreeItem(None) is False
|
||||||
|
|
||||||
|
# Root Items
|
||||||
|
# ==========
|
||||||
|
|
||||||
|
# No class set
|
||||||
|
assert nwTree.newTreeItem(nwItemType.ROOT) is False
|
||||||
|
|
||||||
|
# Create root item
|
||||||
|
assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) is True
|
||||||
|
assert "1a6562590ef19" in nwGUI.theProject.projTree
|
||||||
|
|
||||||
|
# File/Folder Items
|
||||||
|
# =================
|
||||||
|
|
||||||
# No location selected for new item
|
# No location selected for new item
|
||||||
nwTree.clearSelection()
|
nwTree.clearSelection()
|
||||||
assert not nwTree.newTreeItem(nwItemType.FILE, None)
|
caplog.clear()
|
||||||
assert not nwTree.newTreeItem(nwItemType.FOLDER, None)
|
assert nwTree.newTreeItem(nwItemType.FILE) is False
|
||||||
assert nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL)
|
assert nwTree.newTreeItem(nwItemType.FOLDER) is False
|
||||||
|
assert "Did not find anywhere" in caplog.text
|
||||||
|
|
||||||
# No itemType set or ROOT, but no class
|
# Create new folder as child of Novel folder
|
||||||
assert not nwTree.newTreeItem(None, None)
|
nwTree.setSelectedHandle("73475cb40a568")
|
||||||
assert not nwTree.newTreeItem(nwItemType.ROOT, None)
|
assert nwTree.newTreeItem(nwItemType.FOLDER) is True
|
||||||
|
assert nwGUI.theProject.projTree["031b4af5197ec"].itemParent == "73475cb40a568"
|
||||||
|
assert nwGUI.theProject.projTree["031b4af5197ec"].itemRoot == "73475cb40a568"
|
||||||
|
assert nwGUI.theProject.projTree["031b4af5197ec"].itemClass == nwItemClass.NOVEL
|
||||||
|
|
||||||
# Select a location
|
# Add a new file in the new folder
|
||||||
chItem = nwTree._getTreeItem("a6d311a93600a")
|
nwTree.setSelectedHandle("031b4af5197ec")
|
||||||
nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
|
assert nwTree.newTreeItem(nwItemType.FILE) is True
|
||||||
chItem.setExpanded(True)
|
assert nwGUI.theProject.projTree["41cfc0d1f2d12"].itemParent == "031b4af5197ec"
|
||||||
|
assert nwGUI.theProject.projTree["41cfc0d1f2d12"].itemRoot == "73475cb40a568"
|
||||||
|
assert nwGUI.theProject.projTree["41cfc0d1f2d12"].itemClass == nwItemClass.NOVEL
|
||||||
|
|
||||||
# Create new item with no class set (defaults to NOVEL)
|
# Add a new file next to the other new file
|
||||||
assert nwTree.newTreeItem(nwItemType.FILE, None)
|
nwTree.setSelectedHandle("41cfc0d1f2d12")
|
||||||
assert nwTree.newTreeItem(nwItemType.FOLDER, None)
|
assert nwTree.newTreeItem(nwItemType.FILE) is True
|
||||||
|
assert nwGUI.theProject.projTree["2858dcd1057d3"].itemParent == "031b4af5197ec"
|
||||||
|
assert nwGUI.theProject.projTree["2858dcd1057d3"].itemRoot == "73475cb40a568"
|
||||||
|
assert nwGUI.theProject.projTree["2858dcd1057d3"].itemClass == nwItemClass.NOVEL
|
||||||
|
assert nwGUI.openDocument("2858dcd1057d3")
|
||||||
|
assert nwGUI.docEditor.getText() == "### New Document\n\n"
|
||||||
|
|
||||||
# Check that we have the correct tree order
|
# Add a new file to the characters folder
|
||||||
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
|
nwTree.setSelectedHandle("71ee45a3c0db9")
|
||||||
"a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9"
|
assert nwTree.newTreeItem(nwItemType.FILE) is True
|
||||||
]
|
assert nwGUI.theProject.projTree["2fca346db6561"].itemParent == "71ee45a3c0db9"
|
||||||
|
assert nwGUI.theProject.projTree["2fca346db6561"].itemRoot == "71ee45a3c0db9"
|
||||||
|
assert nwGUI.theProject.projTree["2fca346db6561"].itemClass == nwItemClass.CHARACTER
|
||||||
|
assert nwGUI.openDocument("2fca346db6561")
|
||||||
|
assert nwGUI.docEditor.getText() == "# New Note\n\n"
|
||||||
|
|
||||||
# Add roots
|
# Make sure the sibling folder bug trap works
|
||||||
assert not nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) # Duplicate
|
nwTree.setSelectedHandle("2858dcd1057d3")
|
||||||
assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.CUSTOM) # Valid
|
nwGUI.theProject.projTree["2858dcd1057d3"].setParent(None) # This should not happen
|
||||||
|
caplog.clear()
|
||||||
|
assert nwTree.newTreeItem(nwItemType.FILE) is False
|
||||||
|
assert "Internal error" in caplog.text
|
||||||
|
nwGUI.theProject.projTree["2858dcd1057d3"].setParent("031b4af5197ec")
|
||||||
|
|
||||||
# Change max depth and try to add a subfolder that is too deep
|
# Get the trash folder
|
||||||
monkeypatch.setattr("novelwriter.constants.nwConst.MAX_DEPTH", 2)
|
nwTree._addTrashRoot()
|
||||||
chItem = nwTree._getTreeItem("71ee45a3c0db9")
|
trashHandle = nwGUI.theProject.trashFolder()
|
||||||
nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
|
nwTree.setSelectedHandle(trashHandle)
|
||||||
assert not nwTree.newTreeItem(nwItemType.FOLDER, None)
|
assert nwTree.newTreeItem(nwItemType.FILE) is False
|
||||||
|
assert "Cannot add new files or folders to the Trash folder" in caplog.text
|
||||||
|
|
||||||
##
|
# Other Checks
|
||||||
# Move Items
|
# ============
|
||||||
##
|
|
||||||
|
|
||||||
nwTree.setSelectedHandle("8c659a11cd429")
|
# Also check error handling in reveal function
|
||||||
|
assert nwTree.revealNewTreeItem("abc") is False
|
||||||
|
|
||||||
# Shift focus and try to move item
|
# Add an item that cannot be displayed in the tree
|
||||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
|
nHandle = nwGUI.theProject.newFile("Test", None)
|
||||||
assert not nwTree.moveTreeItem(1)
|
assert nwTree.revealNewTreeItem(nHandle) is False
|
||||||
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
|
|
||||||
"a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9"
|
|
||||||
]
|
|
||||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
|
|
||||||
|
|
||||||
# Move second item up twice (should give same result)
|
|
||||||
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
|
|
||||||
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
|
|
||||||
"a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9"
|
|
||||||
]
|
|
||||||
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
|
|
||||||
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
|
|
||||||
"a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9"
|
|
||||||
]
|
|
||||||
|
|
||||||
# Move it back down four times (last two should be the same)
|
|
||||||
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
|
|
||||||
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
|
|
||||||
"a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9"
|
|
||||||
]
|
|
||||||
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
|
|
||||||
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
|
|
||||||
"a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "8c659a11cd429", "71ee45a3c0db9"
|
|
||||||
]
|
|
||||||
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
|
|
||||||
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
|
|
||||||
"a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429"
|
|
||||||
]
|
|
||||||
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
|
|
||||||
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
|
|
||||||
"a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429"
|
|
||||||
]
|
|
||||||
|
|
||||||
# Move up twice, and undo
|
|
||||||
nwTree._lastMove = {}
|
|
||||||
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
|
|
||||||
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
|
|
||||||
nwGUI.mainMenu.aMoveUndo.activate(QAction.Trigger)
|
|
||||||
assert nwTree.getTreeFromHandle("a6d311a93600a") == [
|
|
||||||
"a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429"
|
|
||||||
]
|
|
||||||
|
|
||||||
# Move a root item (top level items are different) twice
|
|
||||||
nwTree.flushTreeOrder()
|
|
||||||
assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 10
|
|
||||||
nwTree.setSelectedHandle("9d5247ab588e0")
|
|
||||||
|
|
||||||
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
|
|
||||||
nwTree.flushTreeOrder()
|
|
||||||
assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 11
|
|
||||||
|
|
||||||
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
|
|
||||||
nwTree.flushTreeOrder()
|
|
||||||
assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 11
|
|
||||||
|
|
||||||
##
|
|
||||||
# Delete and Trash
|
|
||||||
##
|
|
||||||
|
|
||||||
# Add some content to the new file
|
|
||||||
nwGUI.openDocument("73475cb40a568")
|
|
||||||
nwGUI.docEditor.setText("# Hello World\n")
|
|
||||||
nwGUI.saveDocument()
|
|
||||||
nwGUI.saveProject()
|
|
||||||
assert os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
|
|
||||||
|
|
||||||
# Delete the items we added earlier
|
|
||||||
nwTree.clearSelection()
|
|
||||||
assert not nwTree.emptyTrash() # No folder yet
|
|
||||||
assert not nwTree.deleteItem(None)
|
|
||||||
assert not nwTree.deleteItem("1111111111111")
|
|
||||||
assert nwTree.deleteItem("73475cb40a568") # New File
|
|
||||||
assert nwTree.deleteItem("71ee45a3c0db9") # New Folder
|
|
||||||
assert nwTree.deleteItem("811786ad1ae74") # Custom Root
|
|
||||||
assert "73475cb40a568" in nwGUI.theProject.projTree._treeOrder
|
|
||||||
assert "71ee45a3c0db9" not in nwGUI.theProject.projTree._treeOrder
|
|
||||||
assert "811786ad1ae74" not in nwGUI.theProject.projTree._treeOrder
|
|
||||||
|
|
||||||
# The file is in trash, empty it
|
|
||||||
assert os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
|
|
||||||
assert nwTree.emptyTrash()
|
|
||||||
assert not nwTree.emptyTrash() # Already empty
|
|
||||||
assert not os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
|
|
||||||
assert "73475cb40a568" not in nwGUI.theProject.projTree._treeOrder
|
|
||||||
|
|
||||||
# Should not be allowed to add files and folders to Trash
|
|
||||||
trashHandle = nwGUI.theProject.projTree.trashRoot()
|
|
||||||
chItem = nwTree._getTreeItem(trashHandle)
|
|
||||||
nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
|
|
||||||
assert not nwTree.newTreeItem(nwItemType.FILE, None)
|
|
||||||
assert not nwTree.newTreeItem(nwItemType.FOLDER, None)
|
|
||||||
|
|
||||||
# Close the project
|
|
||||||
nwGUI.closeProject()
|
|
||||||
|
|
||||||
##
|
|
||||||
# Orphaned Files
|
|
||||||
##
|
|
||||||
|
|
||||||
# Add an orphaned file
|
|
||||||
orphFile = os.path.join(nwMinimal, "content", "1234567890abc.nwd")
|
|
||||||
writeFile(orphFile, "# Hello World\n")
|
|
||||||
|
|
||||||
# Open the project again
|
|
||||||
nwGUI.openProject(nwMinimal)
|
|
||||||
|
|
||||||
# Check that the orphaned file was found and added to the tree
|
|
||||||
nwTree.flushTreeOrder()
|
|
||||||
assert "1234567890abc" in nwGUI.theProject.projTree._treeOrder
|
|
||||||
orItem = nwTree._getTreeItem("1234567890abc")
|
|
||||||
assert orItem.text(nwTree.C_NAME) == "Recovered File 1"
|
|
||||||
|
|
||||||
##
|
|
||||||
# Unexpected Error Handling
|
|
||||||
##
|
|
||||||
|
|
||||||
# Add an item with an invalid type
|
|
||||||
assert not nwTree.newTreeItem(nwItemType.NO_TYPE, nwItemClass.NOVEL)
|
|
||||||
assert "Failed to add new item" in caplog.messages[-1]
|
|
||||||
|
|
||||||
# Add new file after one that has no parent handle
|
|
||||||
chItem = nwTree._getTreeItem("44cb730c42048")
|
|
||||||
nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
|
|
||||||
nwTree.theProject.projTree["44cb730c42048"]._parent = None
|
|
||||||
assert not nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL)
|
|
||||||
nwTree.clearSelection()
|
|
||||||
|
|
||||||
# Add a file with no parent, and fail to find a suitable parent item
|
|
||||||
monkeypatch.setattr("novelwriter.core.tree.NWTree.findRoot", lambda *a: None)
|
|
||||||
|
|
||||||
assert not nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL)
|
|
||||||
assert not nwTree.newTreeItem(nwItemType.FOLDER, nwItemClass.NOVEL)
|
|
||||||
|
|
||||||
|
# Clean up
|
||||||
# qtbot.stopForInteraction()
|
# qtbot.stopForInteraction()
|
||||||
nwGUI.closeProject()
|
nwGUI.closeProject()
|
||||||
|
|
||||||
# END Test testGuiProjTree_TreeItems
|
# END Test testGuiProjTree_NewItems
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gui
|
||||||
|
def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir):
|
||||||
|
"""Test adding and removing items from the project tree.
|
||||||
|
"""
|
||||||
|
# Block message box
|
||||||
|
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
|
||||||
|
|
||||||
|
nwTree = nwGUI.treeView
|
||||||
|
|
||||||
|
# Try to move item with no project
|
||||||
|
assert nwTree.moveTreeItem(1) is False
|
||||||
|
|
||||||
|
# Create a project
|
||||||
|
nwGUI.theProject.projTree.setSeed(42)
|
||||||
|
prjDir = os.path.join(fncDir, "project")
|
||||||
|
assert nwGUI.newProject({"projPath": prjDir}) is True
|
||||||
|
|
||||||
|
# Move Documents
|
||||||
|
# ==============
|
||||||
|
|
||||||
|
# Add some files
|
||||||
|
nwTree.setSelectedHandle("31489056e0916")
|
||||||
|
assert nwTree.newTreeItem(nwItemType.FILE) is True
|
||||||
|
assert nwTree.newTreeItem(nwItemType.FILE) is True
|
||||||
|
assert nwTree.newTreeItem(nwItemType.FILE) is True
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
|
||||||
|
"1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Move item without focus
|
||||||
|
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
|
||||||
|
assert nwTree.moveTreeItem(1) is False
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
|
||||||
|
"1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
|
||||||
|
]
|
||||||
|
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
|
||||||
|
|
||||||
|
# Move with no selections
|
||||||
|
nwTree.clearSelection()
|
||||||
|
assert nwTree.moveTreeItem(1) is False
|
||||||
|
|
||||||
|
# Move second item up twice (should give same result)
|
||||||
|
nwTree.setSelectedHandle("0e17daca5f3e1")
|
||||||
|
assert nwTree.moveTreeItem(-1) is True
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "0e17daca5f3e1", "98010bd9270f9",
|
||||||
|
"1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
|
||||||
|
]
|
||||||
|
assert nwTree.moveTreeItem(-1) is False
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "0e17daca5f3e1", "98010bd9270f9",
|
||||||
|
"1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Restore via menu entry
|
||||||
|
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
|
||||||
|
"1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Move fifth item down twice (should give same result)
|
||||||
|
nwTree.setSelectedHandle("031b4af5197ec")
|
||||||
|
assert nwTree.moveTreeItem(1) is True
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
|
||||||
|
"1a6562590ef19", "41cfc0d1f2d12", "031b4af5197ec",
|
||||||
|
]
|
||||||
|
assert nwTree.moveTreeItem(1) is False
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
|
||||||
|
"1a6562590ef19", "41cfc0d1f2d12", "031b4af5197ec",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Restore via menu entry
|
||||||
|
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
|
||||||
|
"1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Move down again, and restore via undo
|
||||||
|
nwTree.setSelectedHandle("031b4af5197ec")
|
||||||
|
assert nwTree.moveTreeItem(1) is True
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
|
||||||
|
"1a6562590ef19", "41cfc0d1f2d12", "031b4af5197ec",
|
||||||
|
]
|
||||||
|
nwGUI.mainMenu.aMoveUndo.activate(QAction.Trigger)
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
|
||||||
|
"1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Root Folder
|
||||||
|
# ===========
|
||||||
|
|
||||||
|
nwTree.setSelectedHandle("73475cb40a568")
|
||||||
|
assert nwGUI.theProject.projTree._treeOrder.index("73475cb40a568") == 0
|
||||||
|
|
||||||
|
# Move novel folder up
|
||||||
|
assert nwTree.moveTreeItem(-1) is False
|
||||||
|
nwTree.flushTreeOrder()
|
||||||
|
assert nwGUI.theProject.projTree._treeOrder.index("73475cb40a568") == 0
|
||||||
|
|
||||||
|
# Move novel folder down
|
||||||
|
assert nwTree.moveTreeItem(1) is True
|
||||||
|
nwTree.flushTreeOrder()
|
||||||
|
assert nwGUI.theProject.projTree._treeOrder.index("73475cb40a568") == 1
|
||||||
|
|
||||||
|
# Move novel folder up again
|
||||||
|
assert nwTree.moveTreeItem(-1) is True
|
||||||
|
nwTree.flushTreeOrder()
|
||||||
|
assert nwGUI.theProject.projTree._treeOrder.index("73475cb40a568") == 0
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
# qtbot.stopForInteraction()
|
||||||
|
nwGUI.closeProject()
|
||||||
|
|
||||||
|
# END Test testGuiProjTree_MoveItems
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gui
|
||||||
|
def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir):
|
||||||
|
"""Test adding and removing items from the project tree.
|
||||||
|
"""
|
||||||
|
# Block message box
|
||||||
|
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
|
||||||
|
|
||||||
|
nwTree = nwGUI.treeView
|
||||||
|
|
||||||
|
# Try to run with no project
|
||||||
|
assert nwTree.emptyTrash() is False
|
||||||
|
assert nwTree.deleteItem() is False
|
||||||
|
|
||||||
|
# Create a project
|
||||||
|
nwGUI.theProject.projTree.setSeed(42)
|
||||||
|
prjDir = os.path.join(fncDir, "project")
|
||||||
|
assert nwGUI.newProject({"projPath": prjDir}) is True
|
||||||
|
|
||||||
|
# Try emptying the trash already now, when there is no trash folder
|
||||||
|
assert nwTree.emptyTrash() is False
|
||||||
|
|
||||||
|
# Add some files
|
||||||
|
nwTree.setSelectedHandle("31489056e0916")
|
||||||
|
assert nwTree.newTreeItem(nwItemType.FILE) is True
|
||||||
|
assert nwTree.newTreeItem(nwItemType.FILE) is True
|
||||||
|
assert nwTree.newTreeItem(nwItemType.FILE) is True
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
|
||||||
|
"1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Delete File
|
||||||
|
# ===========
|
||||||
|
|
||||||
|
# Delete item without focus -> blocked
|
||||||
|
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
|
||||||
|
nwTree.setSelectedHandle("41cfc0d1f2d12")
|
||||||
|
caplog.clear()
|
||||||
|
assert nwTree.deleteItem() is False
|
||||||
|
assert "blocked" in caplog.text
|
||||||
|
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
|
||||||
|
|
||||||
|
# No selection made
|
||||||
|
nwTree.clearSelection()
|
||||||
|
caplog.clear()
|
||||||
|
assert nwTree.deleteItem() is False
|
||||||
|
assert "no item to delete" in caplog.text
|
||||||
|
|
||||||
|
# Not a valid handle
|
||||||
|
nwTree.clearSelection()
|
||||||
|
caplog.clear()
|
||||||
|
assert nwTree.deleteItem("0000000000000") is False
|
||||||
|
assert "Could not find tree item" in caplog.text
|
||||||
|
|
||||||
|
# Block adding trash folder
|
||||||
|
funcPointer = nwTree._addTrashRoot
|
||||||
|
nwTree._addTrashRoot = lambda *a: None
|
||||||
|
assert nwTree.deleteItem("41cfc0d1f2d12") is False
|
||||||
|
nwTree._addTrashRoot = funcPointer
|
||||||
|
|
||||||
|
# Delete last two documents, which also adds the trash folder
|
||||||
|
assert nwTree.deleteItem("41cfc0d1f2d12") is True
|
||||||
|
assert nwTree.deleteItem("031b4af5197ec") is True
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
|
||||||
|
"1a6562590ef19"
|
||||||
|
]
|
||||||
|
trashHandle = nwGUI.theProject.projTree.trashRoot()
|
||||||
|
assert nwTree.getTreeFromHandle(trashHandle) == [
|
||||||
|
trashHandle, "41cfc0d1f2d12", "031b4af5197ec"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Delete the first file again (permanent), and ask for permission
|
||||||
|
# Also open the document in the editor, which should trigger a close
|
||||||
|
assert os.path.isfile(os.path.join(prjDir, "content", "41cfc0d1f2d12.nwd"))
|
||||||
|
assert "41cfc0d1f2d12" in nwGUI.theProject.projTree
|
||||||
|
assert nwGUI.docEditor.docHandle() is None
|
||||||
|
assert nwGUI.openDocument("41cfc0d1f2d12") is True
|
||||||
|
assert nwGUI.docEditor.docHandle() == "41cfc0d1f2d12"
|
||||||
|
assert nwTree.deleteItem("41cfc0d1f2d12") is True
|
||||||
|
assert nwGUI.docEditor.docHandle() is None
|
||||||
|
assert not os.path.isfile(os.path.join(prjDir, "content", "41cfc0d1f2d12.nwd"))
|
||||||
|
assert "41cfc0d1f2d12" not in nwGUI.theProject.projTree
|
||||||
|
assert nwTree.getTreeFromHandle(trashHandle) == [
|
||||||
|
trashHandle, "031b4af5197ec"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Try to delete the second document, but block the deletion
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("novelwriter.core.document.NWDoc.deleteDocument", lambda *a: False)
|
||||||
|
assert nwTree.deleteItem("031b4af5197ec") is False
|
||||||
|
|
||||||
|
# Delete proper, and skip asking for permission
|
||||||
|
assert os.path.isfile(os.path.join(prjDir, "content", "031b4af5197ec.nwd"))
|
||||||
|
assert "031b4af5197ec" in nwGUI.theProject.projTree
|
||||||
|
assert nwTree.deleteItem("031b4af5197ec", alreadyAsked=True) is True
|
||||||
|
assert not os.path.isfile(os.path.join(prjDir, "content", "031b4af5197ec.nwd"))
|
||||||
|
assert "031b4af5197ec" not in nwGUI.theProject.projTree
|
||||||
|
assert nwTree.getTreeFromHandle(trashHandle) == [trashHandle]
|
||||||
|
|
||||||
|
# Delete Folder/Root
|
||||||
|
# ==================
|
||||||
|
|
||||||
|
# Deleting non-empty folders is blocked
|
||||||
|
assert nwTree.deleteItem("31489056e0916") is False # Folder
|
||||||
|
assert nwTree.deleteItem("73475cb40a568") is False # Root
|
||||||
|
|
||||||
|
# Add a folder we can delete
|
||||||
|
nwTree.setSelectedHandle("71ee45a3c0db9") # Character Root
|
||||||
|
assert nwTree.newTreeItem(nwItemType.FOLDER) is True
|
||||||
|
assert "2fca346db6561" in nwGUI.theProject.projTree
|
||||||
|
|
||||||
|
# Try to delete, but block parent item lookup
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("PyQt5.QtWidgets.QTreeWidgetItem.parent", lambda *a: None)
|
||||||
|
caplog.clear()
|
||||||
|
assert nwTree.deleteItem("2fca346db6561") is False
|
||||||
|
assert "Could not delete folder" in caplog.text
|
||||||
|
assert "2fca346db6561" in nwGUI.theProject.projTree
|
||||||
|
|
||||||
|
# Delete folder properly
|
||||||
|
assert nwTree.deleteItem("2fca346db6561") is True
|
||||||
|
assert "2fca346db6561" not in nwGUI.theProject.projTree
|
||||||
|
|
||||||
|
# Delete the Character root
|
||||||
|
assert nwTree.deleteItem("71ee45a3c0db9") is True
|
||||||
|
assert "71ee45a3c0db9" not in nwGUI.theProject.projTree
|
||||||
|
|
||||||
|
# Empty Trash
|
||||||
|
# ===========
|
||||||
|
|
||||||
|
# Try to empty trash that is already empty
|
||||||
|
caplog.clear()
|
||||||
|
assert nwTree.getTreeFromHandle(trashHandle) == [trashHandle]
|
||||||
|
assert nwTree.emptyTrash() is False
|
||||||
|
assert "already empty" in caplog.text
|
||||||
|
|
||||||
|
# Move the two remaining scene documents to trash
|
||||||
|
assert nwTree.deleteItem("0e17daca5f3e1") is True
|
||||||
|
assert nwTree.deleteItem("1a6562590ef19") is True
|
||||||
|
assert nwTree.getTreeFromHandle("31489056e0916") == [
|
||||||
|
"31489056e0916", "98010bd9270f9"
|
||||||
|
]
|
||||||
|
assert nwTree.getTreeFromHandle(trashHandle) == [
|
||||||
|
trashHandle, "0e17daca5f3e1", "1a6562590ef19"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Empty trash, but select no on question
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
|
||||||
|
assert nwTree.emptyTrash() is False
|
||||||
|
|
||||||
|
# Empty the trash proper
|
||||||
|
nwTree._setTreeChanged(False)
|
||||||
|
assert nwTree.emptyTrash() is True
|
||||||
|
assert nwTree.getTreeFromHandle(trashHandle) == [trashHandle]
|
||||||
|
assert nwTree._treeChanged is True
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
# qtbot.stopForInteraction()
|
||||||
|
nwGUI.closeProject()
|
||||||
|
|
||||||
|
# END Test testGuiProjTree_DeleteItems
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import pytest
|
|||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from novelwriter.core import NWDoc
|
from novelwriter.core import NWDoc
|
||||||
from novelwriter.enum import nwItemClass, nwState
|
from novelwriter.enum import nwState
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
@@ -36,7 +36,7 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, fncProj):
|
|||||||
|
|
||||||
nwGUI.theProject.projTree.setSeed(42)
|
nwGUI.theProject.projTree.setSeed(42)
|
||||||
assert nwGUI.newProject({"projPath": fncProj}) is True
|
assert nwGUI.newProject({"projPath": fncProj}) is True
|
||||||
cHandle = nwGUI.theProject.newFile("A Note", nwItemClass.CHARACTER, "71ee45a3c0db9")
|
cHandle = nwGUI.theProject.newFile("A Note", "71ee45a3c0db9")
|
||||||
newDoc = NWDoc(nwGUI.theProject, cHandle)
|
newDoc = NWDoc(nwGUI.theProject, cHandle)
|
||||||
newDoc.writeDocument("# A Note\n\n")
|
newDoc.writeDocument("# A Note\n\n")
|
||||||
nwGUI.treeView.revealNewTreeItem(cHandle)
|
nwGUI.treeView.revealNewTreeItem(cHandle)
|
||||||
|
|||||||
Reference in New Issue
Block a user