Added some the tree building code, with the help of 3 G&Ts.
This commit is contained in:
+11
-2
@@ -52,7 +52,8 @@ class Config:
|
|||||||
self.confChanged = False
|
self.confChanged = False
|
||||||
|
|
||||||
## General
|
## General
|
||||||
self.winGeometry = [1100, 650]
|
self.winGeometry = [1100, 650]
|
||||||
|
self.treeColWidth = [120, 30, 50]
|
||||||
|
|
||||||
# Check if config file exists
|
# Check if config file exists
|
||||||
if path.isfile(path.join(self.confPath,self.confFile)):
|
if path.isfile(path.join(self.confPath,self.confFile)):
|
||||||
@@ -83,6 +84,10 @@ class Config:
|
|||||||
self.winGeometry = self.unpackList(
|
self.winGeometry = self.unpackList(
|
||||||
confParser.get(cnfSec,"geometry"), 2, self.winGeometry
|
confParser.get(cnfSec,"geometry"), 2, self.winGeometry
|
||||||
)
|
)
|
||||||
|
if confParser.has_option(cnfSec,"treecols"):
|
||||||
|
self.treeColWidth = self.unpackList(
|
||||||
|
confParser.get(cnfSec,"treecols"), 3, self.treeColWidth
|
||||||
|
)
|
||||||
|
|
||||||
## Path
|
## Path
|
||||||
cnfSec = "Path"
|
cnfSec = "Path"
|
||||||
@@ -105,6 +110,7 @@ class Config:
|
|||||||
confParser.add_section(cnfSec)
|
confParser.add_section(cnfSec)
|
||||||
confParser.set(cnfSec,"timestamp", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
confParser.set(cnfSec,"timestamp", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
confParser.set(cnfSec,"geometry", self.packList(self.winGeometry))
|
confParser.set(cnfSec,"geometry", self.packList(self.winGeometry))
|
||||||
|
confParser.set(cnfSec,"treecols", self.packList(self.treeColWidth))
|
||||||
|
|
||||||
## Path
|
## Path
|
||||||
cnfSec = "Path"
|
cnfSec = "Path"
|
||||||
@@ -142,7 +148,6 @@ class Config:
|
|||||||
self.recentList.insert(0,recentPath)
|
self.recentList.insert(0,recentPath)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def setConfPath(self, newPath):
|
def setConfPath(self, newPath):
|
||||||
if newPath is None: return
|
if newPath is None: return
|
||||||
if not path.isfile(newPath):
|
if not path.isfile(newPath):
|
||||||
@@ -161,4 +166,8 @@ class Config:
|
|||||||
self.confChanged = True
|
self.confChanged = True
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def setTreeColWidths(self, colWidths):
|
||||||
|
self.treeColWidth = colWidths
|
||||||
|
return
|
||||||
|
|
||||||
# End Class Config
|
# End Class Config
|
||||||
|
|||||||
+61
-11
@@ -15,28 +15,78 @@ import nw
|
|||||||
|
|
||||||
from os import path
|
from os import path
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtWidgets import QTreeView
|
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QTreeWidgetItemIterator, QAbstractItemView
|
||||||
from PyQt5.QtGui import QStandardItemModel
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class GuiDocTree(QTreeView):
|
class GuiDocTree(QTreeWidget):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, theProject):
|
||||||
QTreeView.__init__(self)
|
QTreeWidget.__init__(self)
|
||||||
|
|
||||||
logger.debug("Initialising DocTree ...")
|
logger.debug("Initialising DocTree ...")
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
|
self.debugGUI = self.mainConf.debugGUI
|
||||||
|
self.theProject = theProject
|
||||||
|
self.theMap = {}
|
||||||
|
|
||||||
self.treeModel = QStandardItemModel(0, 3, self)
|
self.setColumnCount(4)
|
||||||
self.treeModel.setHeaderData(0, Qt.Horizontal, "Name")
|
self.setHeaderLabels(["Name","","","Handle"])
|
||||||
self.treeModel.setHeaderData(1, Qt.Horizontal, "Words")
|
if not self.debugGUI:
|
||||||
self.treeModel.setHeaderData(2, Qt.Horizontal, "Status")
|
self.hideColumn(3)
|
||||||
|
|
||||||
self.setModel(self.treeModel)
|
# Allow Move by Drag & Drop
|
||||||
|
self.setDragEnabled(True)
|
||||||
|
self.setDragDropMode(QAbstractItemView.InternalMove)
|
||||||
|
|
||||||
|
for colN in range(len(self.mainConf.treeColWidth)):
|
||||||
|
self.setColumnWidth(colN,self.mainConf.treeColWidth[colN])
|
||||||
|
|
||||||
logger.debug("DocTree initialisation complete")
|
logger.debug("DocTree initialisation complete")
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def saveTreeOrder(self):
|
||||||
|
|
||||||
|
theList = []
|
||||||
|
for i in range(self.topLevelItemCount()):
|
||||||
|
theList = self._scanChildren(theList, self.topLevelItem(i))
|
||||||
|
|
||||||
|
print(theList)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _scanChildren(self, theList, theItem):
|
||||||
|
theList.append(theItem.text(3))
|
||||||
|
for i in range(theItem.childCount()):
|
||||||
|
self._scanChildren(theList, theItem.child(i))
|
||||||
|
return theList
|
||||||
|
|
||||||
|
def buildTree(self):
|
||||||
|
|
||||||
|
for tHandle in self.theProject.projTree:
|
||||||
|
nwItem = self.theProject.projTree[tHandle]
|
||||||
|
tName = nwItem.itemName
|
||||||
|
tStatus = 0
|
||||||
|
wCount = 0
|
||||||
|
pHandle = nwItem.parHandle
|
||||||
|
newItem = QTreeWidgetItem([
|
||||||
|
tName, str(tStatus), str(wCount), tHandle
|
||||||
|
])
|
||||||
|
self.theMap[tHandle] = newItem
|
||||||
|
if pHandle is None:
|
||||||
|
self.addTopLevelItem(newItem)
|
||||||
|
else:
|
||||||
|
self.theMap[pHandle].addChild(newItem)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def getColumnSizes(self):
|
||||||
|
retVals = [
|
||||||
|
self.columnWidth(0),
|
||||||
|
self.columnWidth(1),
|
||||||
|
self.columnWidth(2),
|
||||||
|
]
|
||||||
|
return retVals
|
||||||
|
|
||||||
# END Class GuiDocTree
|
# END Class GuiDocTree
|
||||||
|
|||||||
+7
-5
@@ -46,8 +46,8 @@ class GuiMain(QMainWindow):
|
|||||||
self.splitMain.addWidget(self.treePane)
|
self.splitMain.addWidget(self.treePane)
|
||||||
self.splitMain.addWidget(self.docEditor)
|
self.splitMain.addWidget(self.docEditor)
|
||||||
|
|
||||||
self.treeBox = QVBoxLayout()
|
self.treeBox = QVBoxLayout()
|
||||||
self.treeView = GuiDocTree()
|
self.treeView = GuiDocTree(self.theProject)
|
||||||
self.treeBox.addWidget(self.treeView)
|
self.treeBox.addWidget(self.treeView)
|
||||||
self.treePane.setLayout(self.treeBox)
|
self.treePane.setLayout(self.treeBox)
|
||||||
|
|
||||||
@@ -58,8 +58,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.setCentralWidget(self.splitMain)
|
self.setCentralWidget(self.splitMain)
|
||||||
|
|
||||||
self._buildMenu()
|
self._buildMenu()
|
||||||
|
self.treeView.buildTree()
|
||||||
# self.docTabs.createTab()
|
|
||||||
|
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
@@ -76,7 +75,8 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
def newProject(self):
|
def newProject(self):
|
||||||
logger.info("Creating new project")
|
logger.info("Creating new project")
|
||||||
self.docTabs.createTab(tabType=nw.DOCTYPE_PROJECT)
|
self.theProject.newProject()
|
||||||
|
self.treeView.buildTree()
|
||||||
return
|
return
|
||||||
|
|
||||||
def openProject(self):
|
def openProject(self):
|
||||||
@@ -101,6 +101,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.theProject.setProjectPath(projPath)
|
self.theProject.setProjectPath(projPath)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
self.treeView.saveTreeOrder()
|
||||||
self.theProject.saveProject()
|
self.theProject.saveProject()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -130,6 +131,7 @@ class GuiMain(QMainWindow):
|
|||||||
def _closeMain(self):
|
def _closeMain(self):
|
||||||
logger.info("Exiting %s" % nw.__package__)
|
logger.info("Exiting %s" % nw.__package__)
|
||||||
self.mainConf.setWinSize(self.width(), self.height())
|
self.mainConf.setWinSize(self.width(), self.height())
|
||||||
|
self.mainConf.setTreeColWidths(self.treeView.getColumnSizes())
|
||||||
self.mainConf.saveConfig()
|
self.mainConf.saveConfig()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# -*- coding: utf-8 -*
|
||||||
|
"""novelWriter Project Item
|
||||||
|
|
||||||
|
novelWriter – Project Item
|
||||||
|
============================
|
||||||
|
Class holding a project item
|
||||||
|
|
||||||
|
File History:
|
||||||
|
Created: 2018-10-27 [0.1.0]
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import nw
|
||||||
|
|
||||||
|
from os import path, mkdir
|
||||||
|
from lxml import etree
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class NWItem():
|
||||||
|
|
||||||
|
TYPE_ROOT = 0
|
||||||
|
TYPE_FOLDER = 1
|
||||||
|
TYPE_FILE = 2
|
||||||
|
|
||||||
|
CLASS_NONE = 0
|
||||||
|
CLASS_NOVEL = 1
|
||||||
|
CLASS_CHARACTER = 2
|
||||||
|
CLASS_WORLD = 3
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.itemName = ""
|
||||||
|
self.itemHandle = None
|
||||||
|
self.parHandle = None
|
||||||
|
self.itemOrder = None
|
||||||
|
self.itemType = None
|
||||||
|
self.itemClass = None
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def setName(self, theName):
|
||||||
|
self.itemName = theName.strip()
|
||||||
|
return
|
||||||
|
|
||||||
|
def setHandle(self, theHandle):
|
||||||
|
self.itemHandle = theHandle
|
||||||
|
return
|
||||||
|
|
||||||
|
def setParent(self, theParent):
|
||||||
|
self.parHandle = theParent
|
||||||
|
return
|
||||||
|
|
||||||
|
def setOrder(self, theOrder):
|
||||||
|
self.itemOrder = theOrder
|
||||||
|
return
|
||||||
|
|
||||||
|
def setType(self, theType):
|
||||||
|
if isinstance(theType, int):
|
||||||
|
self.itemType = theType
|
||||||
|
elif theType == "ROOT":
|
||||||
|
self.itemType = self.TYPE_ROOT
|
||||||
|
elif theType == "FOLDER":
|
||||||
|
self.itemType = self.TYPE_FOLDER
|
||||||
|
elif theType == "FILE":
|
||||||
|
self.itemType = self.TYPE_FILE
|
||||||
|
else:
|
||||||
|
logger.error("Unrecognised item type '%s'" % theType)
|
||||||
|
self.itemType = None
|
||||||
|
return
|
||||||
|
|
||||||
|
def setClass(self, theClass):
|
||||||
|
if isinstance(theClass, int):
|
||||||
|
self.itemClass = theClass
|
||||||
|
elif theClass == "NONE":
|
||||||
|
self.itemClass = self.CLASS_NONE
|
||||||
|
elif theClass == "NOVEL":
|
||||||
|
self.itemClass = self.CLASS_NOVEL
|
||||||
|
elif theClass == "CHARACTER":
|
||||||
|
self.itemClass = self.CLASS_CHARACTER
|
||||||
|
elif theClass == "WORLD":
|
||||||
|
self.itemClass = self.CLASS_WORLD
|
||||||
|
else:
|
||||||
|
logger.error("Unrecognised root item '%s'" % theClass)
|
||||||
|
self.itemClass = None
|
||||||
|
return
|
||||||
|
|
||||||
|
def getType(self):
|
||||||
|
if self.itemType == self.TYPE_ROOT: return "ROOT"
|
||||||
|
if self.itemType == self.TYPE_FOLDER: return "FOLDER"
|
||||||
|
if self.itemType == self.TYPE_FILE: return "FILE"
|
||||||
|
return "NONE"
|
||||||
|
|
||||||
|
def getClass(self):
|
||||||
|
if self.itemClass == self.CLASS_NONE: return "NONE"
|
||||||
|
if self.itemClass == self.CLASS_NOVEL: return "NOVEL"
|
||||||
|
if self.itemClass == self.CLASS_CHARACTER: return "CHARACTER"
|
||||||
|
if self.itemClass == self.CLASS_WORLD: return "WORLD"
|
||||||
|
return "NONE"
|
||||||
|
|
||||||
|
# END Class NWItem
|
||||||
+106
-10
@@ -13,9 +13,12 @@
|
|||||||
import logging
|
import logging
|
||||||
import nw
|
import nw
|
||||||
|
|
||||||
from os import path, mkdir
|
from os import path, mkdir
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from datetime import datetime
|
from hashlib import sha256
|
||||||
|
from datetime import datetime, time
|
||||||
|
|
||||||
|
from nw.project.item import NWItem
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -23,10 +26,13 @@ class NWProject():
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
# Internal
|
||||||
self.projTree = []
|
self.mainConf = nw.CONFIG
|
||||||
|
self.seedCount = 0
|
||||||
|
|
||||||
# Project Settings
|
# Project Settings
|
||||||
|
self.projTree = {}
|
||||||
|
self.treeOrder = []
|
||||||
self.projPath = None
|
self.projPath = None
|
||||||
self.projFile = "nwProject.nwx"
|
self.projFile = "nwProject.nwx"
|
||||||
self.projName = ""
|
self.projName = ""
|
||||||
@@ -36,9 +42,47 @@ class NWProject():
|
|||||||
return
|
return
|
||||||
|
|
||||||
def buildProjectTree(self):
|
def buildProjectTree(self):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def newRoot(self, rootName, rootType):
|
||||||
|
newItem = NWItem()
|
||||||
|
newItem.setName(rootName)
|
||||||
|
newItem.setType(NWItem.TYPE_ROOT)
|
||||||
|
newItem.setClass(rootType)
|
||||||
|
self._appendItem(None,None,newItem)
|
||||||
|
return newItem.itemHandle
|
||||||
|
|
||||||
|
def newFolder(self, folderName, pHandle):
|
||||||
|
newItem = NWItem()
|
||||||
|
newItem.setName(folderName)
|
||||||
|
newItem.setType(NWItem.TYPE_FOLDER)
|
||||||
|
newItem.setClass(NWItem.CLASS_NONE)
|
||||||
|
self._appendItem(None,pHandle,newItem)
|
||||||
|
return newItem.itemHandle
|
||||||
|
|
||||||
|
def newFile(self, fileName, pHandle):
|
||||||
|
newItem = NWItem()
|
||||||
|
newItem.setName(fileName)
|
||||||
|
newItem.setType(NWItem.TYPE_FILE)
|
||||||
|
newItem.setClass(NWItem.CLASS_NONE)
|
||||||
|
self._appendItem(None,pHandle,newItem)
|
||||||
|
return newItem.itemHandle
|
||||||
|
|
||||||
|
def newProject(self):
|
||||||
|
self.projTree = {}
|
||||||
|
self.treeOrder - []
|
||||||
|
self.projPath = None
|
||||||
|
self.projFile = "nwProject.nwx"
|
||||||
|
self.projName = ""
|
||||||
|
self.bookTitle = ""
|
||||||
|
self.bookAuthors = []
|
||||||
|
hNovel = self.newRoot("Novel", NWItem.CLASS_NOVEL)
|
||||||
|
hChars = self.newRoot("Characters",NWItem.CLASS_CHARACTER)
|
||||||
|
hWorld = self.newRoot("World", NWItem.CLASS_WORLD)
|
||||||
|
hChapt = self.newFolder("New Chapter", hNovel)
|
||||||
|
hScene = self.newFile("New Scene", hChapt)
|
||||||
|
return
|
||||||
|
|
||||||
def openProject(self, fileName):
|
def openProject(self, fileName):
|
||||||
|
|
||||||
if not path.isfile(fileName):
|
if not path.isfile(fileName):
|
||||||
@@ -108,6 +152,24 @@ class NWProject():
|
|||||||
xBookAuthor = etree.SubElement(xProject,"author")
|
xBookAuthor = etree.SubElement(xProject,"author")
|
||||||
xBookAuthor.text = bookAuthor
|
xBookAuthor.text = bookAuthor
|
||||||
|
|
||||||
|
# Save Tree Content
|
||||||
|
xContent = etree.SubElement(nwXML,"content",attrib={"count":str(len(self.projTree))})
|
||||||
|
itemIdx = 0
|
||||||
|
for tHandle in self.projTree.keys():
|
||||||
|
nwItem = self.projTree[tHandle]
|
||||||
|
xItem = etree.SubElement(xContent,"item",attrib={
|
||||||
|
"handle" : str(tHandle),
|
||||||
|
"parent" : str(nwItem.parHandle),
|
||||||
|
"order" : str(nwItem.itemOrder),
|
||||||
|
})
|
||||||
|
xItemName = etree.SubElement(xItem,"name")
|
||||||
|
xItemName.text = str(nwItem.itemName)
|
||||||
|
xItemType = etree.SubElement(xItem,"type")
|
||||||
|
xItemType.text = str(nwItem.getType())
|
||||||
|
if nwItem.getClass() is not "NONE":
|
||||||
|
xItemClass = etree.SubElement(xItem,"class")
|
||||||
|
xItemClass.text = str(nwItem.getClass())
|
||||||
|
|
||||||
# Write the xml tree to file
|
# Write the xml tree to file
|
||||||
with open(path.join(self.projPath,self.projFile),"wb") as outFile:
|
with open(path.join(self.projPath,self.projFile),"wb") as outFile:
|
||||||
outFile.write(etree.tostring(
|
outFile.write(etree.tostring(
|
||||||
@@ -146,15 +208,49 @@ class NWProject():
|
|||||||
self.bookAuthors.append(bookAuthor)
|
self.bookAuthors.append(bookAuthor)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def setTreeOrder(self, newOrder):
|
||||||
|
if len(self.treeOrder) != len(newOrder):
|
||||||
|
logger.warning("Size of new and old tree order does not match")
|
||||||
|
self.treeOrder - newOrder
|
||||||
|
return True
|
||||||
|
|
||||||
#
|
#
|
||||||
# Internal Functions
|
# Internal Functions
|
||||||
#
|
#
|
||||||
|
|
||||||
def _makeHandle(self,seed=""):
|
def _appendItem(self, tHandle, pHandle, nwItem):
|
||||||
|
tHandle = self._checkString(tHandle,self._makeHandle(),False)
|
||||||
|
pHandle = self._checkString(pHandle,None,True)
|
||||||
|
logger.verbose("Adding entry %s with parent %s" % (str(tHandle),str(pHandle)))
|
||||||
|
nwItem.setHandle(tHandle)
|
||||||
|
nwItem.setParent(pHandle)
|
||||||
|
self.projTree[tHandle] = nwItem
|
||||||
|
self.treeOrder.append(tHandle)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _makeHandle(self, seed=""):
|
||||||
|
seed += str(self.seedCount)
|
||||||
|
self.seedCount += 1
|
||||||
itemHandle = sha256((str(time())+seed).encode()).hexdigest()[0:13]
|
itemHandle = sha256((str(time())+seed).encode()).hexdigest()[0:13]
|
||||||
# if itemHandle in self.treeLookup.keys():
|
if itemHandle in self.projTree.keys():
|
||||||
# logger.warning("BookTree: Duplicate handle encountered! Retrying ...")
|
logger.warning("Duplicate handle encountered! Retrying ...")
|
||||||
# itemHandle = self.makeHandle(seed+"!")
|
itemHandle = self._makeHandle(seed+"!")
|
||||||
return itemHandle
|
return itemHandle
|
||||||
|
|
||||||
|
def _checkString(self,checkValue,defaultValue,allowNone=False):
|
||||||
|
if allowNone:
|
||||||
|
if checkValue == None: return None
|
||||||
|
if checkValue == "None": return None
|
||||||
|
if isinstance(checkValue,str): return str(checkValue)
|
||||||
|
return defaultValue
|
||||||
|
|
||||||
|
def _checkInt(self,checkValue,defaultValue,allowNone=False):
|
||||||
|
if allowNone:
|
||||||
|
if checkValue == None: return None
|
||||||
|
if checkValue == "None": return None
|
||||||
|
try:
|
||||||
|
return int(checkValue)
|
||||||
|
except:
|
||||||
|
return defaultValue
|
||||||
|
|
||||||
# END Class NWProject
|
# END Class NWProject
|
||||||
|
|||||||
@@ -1,9 +1,34 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.1.0" fileVersion="1.0" timeStamp="2018-10-25 22:17:01">
|
<novelWriterXML appVersion="0.1.0" fileVersion="1.0" timeStamp="2018-10-27 23:46:46">
|
||||||
<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>
|
||||||
</project>
|
</project>
|
||||||
|
<content count="5">
|
||||||
|
<item handle="7031beac91f75" order="None" parent="None">
|
||||||
|
<name>Novel</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>NOVEL</class>
|
||||||
|
</item>
|
||||||
|
<item handle="f6622b4617424" order="None" parent="None">
|
||||||
|
<name>Characters</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>CHARACTER</class>
|
||||||
|
</item>
|
||||||
|
<item handle="73eee34351f85" order="None" parent="None">
|
||||||
|
<name>World</name>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<class>WORLD</class>
|
||||||
|
</item>
|
||||||
|
<item handle="e7ded148d6e4a" order="None" parent="7031beac91f75">
|
||||||
|
<name>New Chapter</name>
|
||||||
|
<type>FOLDER</type>
|
||||||
|
</item>
|
||||||
|
<item handle="96b68994dfa3d" order="None" parent="e7ded148d6e4a">
|
||||||
|
<name>New Scene</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
</item>
|
||||||
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
Reference in New Issue
Block a user