diff --git a/nw/gui/elements/outline.py b/nw/gui/elements/outline.py index 166d6fef..6fbe3d21 100644 --- a/nw/gui/elements/outline.py +++ b/nw/gui/elements/outline.py @@ -15,6 +15,7 @@ import nw from os import path +from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QTreeWidget, QTreeWidgetItem ) @@ -25,6 +26,31 @@ logger = logging.getLogger(__name__) class GuiProjectOutline(QWidget): + I_TITLE = 0 + I_LEVEL = 1 + I_LABEL = 2 + I_LINE = 3 + I_WORDS = 4 + I_CHARS = 5 + I_PARAS = 6 + I_SYNOP = 7 + + COL_ORDER = [ + I_TITLE, I_LEVEL, I_LABEL, I_LINE, + I_WORDS, I_CHARS, I_PARAS, I_SYNOP, + ] + + COL_LABELS = { + I_TITLE : "Title", + I_LEVEL : "Level", + I_LABEL : "Document", + I_LINE : "Line", + I_WORDS : "Words", + I_CHARS : "Chars", + I_PARAS : "Pars", + I_SYNOP : "Synopsis", + } + def __init__(self, theParent, theProject): QWidget.__init__(self, theParent) @@ -39,8 +65,9 @@ class GuiProjectOutline(QWidget): self.showSynopsis = True self.showFilePath = False - self.outerBox = QVBoxLayout() - self.mainTree = QTreeWidget() + self.outerBox = QVBoxLayout() + self.mainTree = QTreeWidget() + self.treeOrder = self.COL_ORDER self.outerBox.addWidget(self.mainTree) self.outerBox.setContentsMargins(0,0,0,0) @@ -52,8 +79,17 @@ class GuiProjectOutline(QWidget): def populateTree(self): + theLabels = [] + for n in self.treeOrder: + theLabels.append(self.COL_LABELS[n]) + self.mainTree.clear() - self.mainTree.setHeaderLabels(["Title","Level","Document","Line"]) + self.mainTree.setHeaderLabels(theLabels) + + treeHead = self.mainTree.headerItem() + treeHead.setTextAlignment(self.I_CHARS,Qt.AlignRight) + treeHead.setTextAlignment(self.I_WORDS,Qt.AlignRight) + treeHead.setTextAlignment(self.I_PARAS,Qt.AlignRight) currTitle = None currChapter = None @@ -69,11 +105,22 @@ class GuiProjectOutline(QWidget): continue for tEntry in self.theIndex.novelIndex[tHandle]: + + theLine = str(tEntry[0]) + newItem = QTreeWidgetItem([""]*4) - newItem.setText(0, tEntry[2]) - newItem.setText(1, str(tEntry[1])) - newItem.setText(2, nwItem.itemName) - newItem.setText(3, str(tEntry[0])) + newItem.setText(self.I_TITLE, tEntry[2]) + newItem.setText(self.I_LEVEL, str(tEntry[1])) + newItem.setText(self.I_LABEL, nwItem.itemName) + newItem.setText(self.I_LINE, theLine) + + cC, wC, pC = self.theIndex.getCounts(tHandle, theLine) + newItem.setText(self.I_CHARS, str(cC)) + newItem.setText(self.I_WORDS, str(wC)) + newItem.setText(self.I_PARAS, str(pC)) + newItem.setTextAlignment(self.I_CHARS,Qt.AlignRight) + newItem.setTextAlignment(self.I_WORDS,Qt.AlignRight) + newItem.setTextAlignment(self.I_PARAS,Qt.AlignRight) if tEntry[1] == 1: currTitle = newItem diff --git a/nw/project/index.py b/nw/project/index.py index 382abbae..e9bb2661 100644 --- a/nw/project/index.py +++ b/nw/project/index.py @@ -62,8 +62,9 @@ class NWIndex(): self.noteIndex = {} # Meta Data + self.fileCounts = {} self.textCounts = {} - self.fileSynopsis = {} + self.textSynopsis = {} # Lists self.novelList = [] @@ -79,8 +80,9 @@ class NWIndex(): self.refIndex = {} self.novelIndex = {} self.noteIndex = {} + self.fileCounts = {} self.textCounts = {} - self.fileSynopsis = {} + self.textSynopsis = {} return def deleteHandle(self, tHandle): @@ -145,10 +147,12 @@ class NWIndex(): logger.error(str(e)) return False + if "fileCounts" in theData.keys(): + self.fileCounts = theData["fileCounts"] if "textCounts" in theData.keys(): self.textCounts = theData["textCounts"] - if "fileSynopsis" in theData.keys(): - self.fileSynopsis = theData["fileSynopsis"] + if "textSynopsis" in theData.keys(): + self.textSynopsis = theData["textSynopsis"] loadsOK &= True @@ -186,8 +190,9 @@ class NWIndex(): try: with open(metaFile,mode="w+",encoding="utf8") as outFile: outFile.write(json.dumps({ + "fileCounts" : self.fileCounts, "textCounts" : self.textCounts, - "fileSynopsis" : self.fileSynopsis, + "textSynopsis" : self.textSynopsis, }, indent=nIndent)) except Exception as e: logger.error("Failed to save meta file") @@ -203,28 +208,37 @@ class NWIndex(): self.indexBroken = False - for tTag in self.tagIndex: - if len(self.tagIndex[tTag]) != 3: - self.indexBroken = True - - for tHandle in self.refIndex: - for tEntry in self.refIndex[tHandle]: - if len(tEntry) != 4: + try: + for tTag in self.tagIndex: + if len(self.tagIndex[tTag]) != 3: self.indexBroken = True - for tHandle in self.novelIndex: - for tEntry in self.novelIndex[tHandle]: - if len(tEntry) != 4: + for tHandle in self.refIndex: + for tEntry in self.refIndex[tHandle]: + if len(tEntry) != 4: + self.indexBroken = True + + for tHandle in self.novelIndex: + for tEntry in self.novelIndex[tHandle]: + if len(tEntry) != 4: + self.indexBroken = True + + for tHandle in self.noteIndex: + for tEntry in self.noteIndex[tHandle]: + if len(tEntry) != 4: + self.indexBroken = True + + for tHandle in self.fileCounts: + if len(self.fileCounts[tHandle]) != 3: self.indexBroken = True - for tHandle in self.noteIndex: - for tEntry in self.noteIndex[tHandle]: - if len(tEntry) != 4: - self.indexBroken = True + for tHandle in self.textCounts: + for tLine in self.textCounts[tHandle]: + if len(self.textCounts[tHandle][tLine]) != 3: + self.indexBroken = True - for tHandle in self.textCounts: - if len(self.textCounts[tHandle]) != 3: - self.indexBroken = True + except: + self.indexBroken = True if self.indexBroken: self.clearIndex() @@ -275,24 +289,47 @@ class NWIndex(): nLine = 0 nTitle = 0 - for aLine in theText.splitlines(): + theSynopsis = {} + theCounts = {} + theLines = theText.splitlines() + for aLine in theLines: aLine = aLine nLine += 1 nChar = len(aLine.strip()) if nChar == 0: continue + if aLine.startswith(r"#"): isTitle = self.indexTitle(tHandle, isNovel, aLine, nLine, itemLayout) - if isTitle: + if isTitle and nLine > 0: + # Count words in the previous section, before we tag + # the new title location + if nTitle > 0: + lastText = "\n".join(theLines[nTitle-1:nLine-1]) + cC, wC, pC = countWords(lastText) + theCounts[str(nTitle)] = [cC, wC, pC] nTitle = nLine + elif aLine.startswith(r"@"): self.indexNoteRef(tHandle, aLine, nLine, nTitle) self.indexTag(tHandle, aLine, nLine, itemClass) - elif aLine.startswith(r"%synopsis:"): - self.fileSynopsis[tHandle] = aLine[10:].strip() - # Run word counter + elif aLine.startswith(r"%synopsis:"): + theSynopsis[str(nTitle)] = aLine[10:].strip() + + # Count words for remaining text after last heading + if nTitle > 0: + lastText = "\n".join(theLines[nTitle-1:]) + cC, wC, pC = countWords(lastText) + theCounts[str(nTitle)] = [cC, wC, pC] + + if theSynopsis: + self.textSynopsis[tHandle] = theSynopsis + if theCounts: + self.textCounts[tHandle] = theCounts + + # Run word counter for whole text cC, wC, pC = countWords(theText) - self.textCounts[tHandle] = [cC, wC, pC] + self.fileCounts[tHandle] = [cC, wC, pC] return True @@ -438,14 +475,27 @@ class NWIndex(): # Extract Data ## - def getCounts(self, tHandle): + def getCounts(self, tHandle, tLine=None): + """Returns the counts for a file, or a section of a file + starting at line tLine. + """ + cC = 0 wC = 0 pC = 0 - if tHandle in self.textCounts: - cC = self.textCounts[tHandle][0] - wC = self.textCounts[tHandle][1] - pC = self.textCounts[tHandle][2] + + if tLine is None: + if tHandle in self.fileCounts: + cC = self.fileCounts[tHandle][0] + wC = self.fileCounts[tHandle][1] + pC = self.fileCounts[tHandle][2] + else: + if tHandle in self.textCounts: + if tLine in self.textCounts[tHandle]: + cC = self.textCounts[tHandle][tLine][0] + wC = self.textCounts[tHandle][tLine][1] + pC = self.textCounts[tHandle][tLine][2] + return cC, wC, pC def buildNovelList(self):