diff --git a/nw/config.py b/nw/config.py index fad1502c..5015430e 100644 --- a/nw/config.py +++ b/nw/config.py @@ -52,7 +52,8 @@ class Config: self.confChanged = False ## General - self.winGeometry = [1100, 650] + self.winGeometry = [1100, 650] + self.treeColWidth = [120, 30, 50] # Check if config file exists if path.isfile(path.join(self.confPath,self.confFile)): @@ -83,6 +84,10 @@ class Config: self.winGeometry = self.unpackList( 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 cnfSec = "Path" @@ -105,6 +110,7 @@ class Config: confParser.add_section(cnfSec) confParser.set(cnfSec,"timestamp", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) confParser.set(cnfSec,"geometry", self.packList(self.winGeometry)) + confParser.set(cnfSec,"treecols", self.packList(self.treeColWidth)) ## Path cnfSec = "Path" @@ -142,7 +148,6 @@ class Config: self.recentList.insert(0,recentPath) return - def setConfPath(self, newPath): if newPath is None: return if not path.isfile(newPath): @@ -161,4 +166,8 @@ class Config: self.confChanged = True return + def setTreeColWidths(self, colWidths): + self.treeColWidth = colWidths + return + # End Class Config diff --git a/nw/gui/doctree.py b/nw/gui/doctree.py index 9a82c7af..29ca90f7 100644 --- a/nw/gui/doctree.py +++ b/nw/gui/doctree.py @@ -15,28 +15,78 @@ import nw from os import path from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QTreeView -from PyQt5.QtGui import QStandardItemModel +from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QTreeWidgetItemIterator, QAbstractItemView logger = logging.getLogger(__name__) -class GuiDocTree(QTreeView): +class GuiDocTree(QTreeWidget): - def __init__(self): - QTreeView.__init__(self) + def __init__(self, theProject): + QTreeWidget.__init__(self) 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.treeModel.setHeaderData(0, Qt.Horizontal, "Name") - self.treeModel.setHeaderData(1, Qt.Horizontal, "Words") - self.treeModel.setHeaderData(2, Qt.Horizontal, "Status") + self.setColumnCount(4) + self.setHeaderLabels(["Name","","","Handle"]) + if not self.debugGUI: + 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") 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 diff --git a/nw/gui/winmain.py b/nw/gui/winmain.py index 0f4605d0..3d3f6bcd 100644 --- a/nw/gui/winmain.py +++ b/nw/gui/winmain.py @@ -46,8 +46,8 @@ class GuiMain(QMainWindow): self.splitMain.addWidget(self.treePane) self.splitMain.addWidget(self.docEditor) - self.treeBox = QVBoxLayout() - self.treeView = GuiDocTree() + self.treeBox = QVBoxLayout() + self.treeView = GuiDocTree(self.theProject) self.treeBox.addWidget(self.treeView) self.treePane.setLayout(self.treeBox) @@ -58,8 +58,7 @@ class GuiMain(QMainWindow): self.setCentralWidget(self.splitMain) self._buildMenu() - - # self.docTabs.createTab() + self.treeView.buildTree() self.show() @@ -76,7 +75,8 @@ class GuiMain(QMainWindow): def newProject(self): logger.info("Creating new project") - self.docTabs.createTab(tabType=nw.DOCTYPE_PROJECT) + self.theProject.newProject() + self.treeView.buildTree() return def openProject(self): @@ -101,6 +101,7 @@ class GuiMain(QMainWindow): self.theProject.setProjectPath(projPath) else: return False + self.treeView.saveTreeOrder() self.theProject.saveProject() return True @@ -130,6 +131,7 @@ class GuiMain(QMainWindow): def _closeMain(self): logger.info("Exiting %s" % nw.__package__) self.mainConf.setWinSize(self.width(), self.height()) + self.mainConf.setTreeColWidths(self.treeView.getColumnSizes()) self.mainConf.saveConfig() return diff --git a/nw/project/item.py b/nw/project/item.py new file mode 100644 index 00000000..2bc49640 --- /dev/null +++ b/nw/project/item.py @@ -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 diff --git a/nw/project/project.py b/nw/project/project.py index 7013a615..2db609d1 100644 --- a/nw/project/project.py +++ b/nw/project/project.py @@ -13,9 +13,12 @@ import logging import nw -from os import path, mkdir -from lxml import etree -from datetime import datetime +from os import path, mkdir +from lxml import etree +from hashlib import sha256 +from datetime import datetime, time + +from nw.project.item import NWItem logger = logging.getLogger(__name__) @@ -23,10 +26,13 @@ class NWProject(): def __init__(self): - self.mainConf = nw.CONFIG - self.projTree = [] + # Internal + self.mainConf = nw.CONFIG + self.seedCount = 0 # Project Settings + self.projTree = {} + self.treeOrder = [] self.projPath = None self.projFile = "nwProject.nwx" self.projName = "" @@ -36,9 +42,47 @@ class NWProject(): return def buildProjectTree(self): - 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): if not path.isfile(fileName): @@ -108,6 +152,24 @@ class NWProject(): xBookAuthor = etree.SubElement(xProject,"author") 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 with open(path.join(self.projPath,self.projFile),"wb") as outFile: outFile.write(etree.tostring( @@ -146,15 +208,49 @@ class NWProject(): self.bookAuthors.append(bookAuthor) 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 # - 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] - # if itemHandle in self.treeLookup.keys(): - # logger.warning("BookTree: Duplicate handle encountered! Retrying ...") - # itemHandle = self.makeHandle(seed+"!") + if itemHandle in self.projTree.keys(): + logger.warning("Duplicate handle encountered! Retrying ...") + itemHandle = self._makeHandle(seed+"!") 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 diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx index fffe3249..5fcbf62f 100644 --- a/sample/sampleNovel/nwProject.nwx +++ b/sample/sampleNovel/nwProject.nwx @@ -1,9 +1,34 @@ - + Sample Project Sample Project Jane Smith Jay Doh + + + Novel + ROOT + NOVEL + + + Characters + ROOT + CHARACTER + + + World + ROOT + WORLD + + + New Chapter + FOLDER + + + New Scene + FILE + +