From 946331145f8efb32dbe71ae79ba369d77d8a3efd Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Apr 2020 17:48:14 +0200 Subject: [PATCH 1/2] Added a hidden handle column to the outline tree, and connected the double click to open a document --- nw/constants/constants.py | 1 + nw/constants/enum.py | 31 ++++++++++++++++--------------- nw/gui/elements/doceditor.py | 22 ++++++++++++++++++++-- nw/gui/elements/outline.py | 30 ++++++++++++++++++++++++++---- nw/guimain.py | 6 ++++-- 5 files changed, 67 insertions(+), 23 deletions(-) diff --git a/nw/constants/constants.py b/nw/constants/constants.py index e373dd7a..643c3a37 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -121,6 +121,7 @@ class nwLabels(): } OUTLINE_COLS = { nwOutline.TITLE : "Title", + nwOutline.HANDLE : "Handle", nwOutline.LEVEL : "Level", nwOutline.LABEL : "Document", nwOutline.LINE : "Line", diff --git a/nw/constants/enum.py b/nw/constants/enum.py index 7d369d3a..e7b8a62a 100644 --- a/nw/constants/enum.py +++ b/nw/constants/enum.py @@ -107,20 +107,21 @@ class nwAlert(Enum): class nwOutline(Enum): TITLE = 0 - LEVEL = 1 - LABEL = 2 - LINE = 3 - CCOUNT = 4 - WCOUNT = 5 - PCOUNT = 6 - POV = 7 - CHAR = 8 - PLOT = 9 - TIME = 10 - WORLD = 11 - OBJECT = 12 - ENTITY = 13 - CUSTOM = 14 - SYNOP = 15 + HANDLE = 1 + LEVEL = 2 + LABEL = 3 + LINE = 4 + CCOUNT = 5 + WCOUNT = 6 + PCOUNT = 7 + POV = 8 + CHAR = 9 + PLOT = 10 + TIME = 11 + WORLD = 12 + OBJECT = 13 + ENTITY = 14 + CUSTOM = 15 + SYNOP = 16 # END Enum nwOutline diff --git a/nw/gui/elements/doceditor.py b/nw/gui/elements/doceditor.py index 46fcf6d1..2ad49a1b 100644 --- a/nw/gui/elements/doceditor.py +++ b/nw/gui/elements/doceditor.py @@ -218,7 +218,7 @@ class GuiDocEditor(QTextEdit): return True - def loadText(self, tHandle): + def loadText(self, tHandle, tLine=None): """Load text from a document into the editor. If we have an io error, we must handle this and clear the editor so that we don't risk overwriting the file if it exists. This can for instance @@ -249,7 +249,10 @@ class GuiDocEditor(QTextEdit): afTime = time() logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime))) - self.setCursorPosition(self.nwDocument.theItem.cursorPos) + if tLine is None: + self.setCursorPosition(self.nwDocument.theItem.cursorPos) + else: + self.setCursorLine(tLine) self.lastEdit = time() self._runCounter() self.wcTimer.start() @@ -353,6 +356,8 @@ class GuiDocEditor(QTextEdit): return theText def setCursorPosition(self, thePosition): + """Move the cursor to a given position in the document. + """ if thePosition >= 0: theCursor = self.textCursor() theCursor.setPosition(thePosition) @@ -360,9 +365,22 @@ class GuiDocEditor(QTextEdit): return True def getCursorPosition(self): + """Find the cursor position in the document. + """ theCursor = self.textCursor() return theCursor.position() + def setCursorLine(self, theLine): + """Move the cursor to a given line in the document. + """ + if theLine is None: + return False + if theLine >= 0: + theBlock = self.qDocument.findBlockByLineNumber(theLine) + if theBlock: + self.setCursorPosition(theBlock.position()) + return True + ## # Spell Checking ## diff --git a/nw/gui/elements/outline.py b/nw/gui/elements/outline.py index d0b7215a..fe274277 100644 --- a/nw/gui/elements/outline.py +++ b/nw/gui/elements/outline.py @@ -43,6 +43,7 @@ class GuiProjectOutline(QTreeWidget): DEF_WIDTH = { nwOutline.TITLE : 200, + nwOutline.HANDLE : 0, nwOutline.LEVEL : 40, nwOutline.LABEL : 150, nwOutline.LINE : 40, @@ -62,6 +63,7 @@ class GuiProjectOutline(QTreeWidget): DEF_HIDDEN = { nwOutline.TITLE : False, + nwOutline.HANDLE : True, nwOutline.LEVEL : True, nwOutline.LABEL : False, nwOutline.LINE : True, @@ -182,7 +184,17 @@ class GuiProjectOutline(QTreeWidget): ## def _treeDoubleClick(self, tItem, tCol): - print(tItem, tCol) + """Extract the handle and line number of the title double- + clicked, and send it to the main gui class for opening in the + document editor. + """ + tHandle = tItem.text(self.colIndex[nwOutline.HANDLE]) + try: + tLine = int(tItem.text(self.colIndex[nwOutline.LINE])) + except: + tLine = 1 + logger.verbose("User selected entry with handle %s on line %s" % (tHandle, tLine)) + self.theParent.openDocument(tHandle, tLine - 1) return def _headerRightClick(self, clickPos): @@ -301,7 +313,11 @@ class GuiProjectOutline(QTreeWidget): return def _populateTree(self): - """Build the tree based on the project index. + """Build the tree based on the project index, and the header + based on the defined constants, default values and user selected + width, order and hidden state. All columns are populated, even + if they are hidden. This ensures that showing and hiding columns + is fast and doesn't require a rebuild of the tree. """ self.clear() @@ -317,6 +333,11 @@ class GuiProjectOutline(QTreeWidget): self.setColumnWidth(self.colIndex[hItem], self.colWidth[hItem]) self.setColumnHidden(self.colIndex[hItem], self.colHidden[hItem]) + # Make sure title column is always visible, + # and handle column always hidden + self.setColumnHidden(self.colIndex[nwOutline.TITLE], False) + self.setColumnHidden(self.colIndex[nwOutline.HANDLE], True) + headItem = self.headerItem() headItem.setTextAlignment(self.colIndex[nwOutline.CCOUNT], Qt.AlignRight) headItem.setTextAlignment(self.colIndex[nwOutline.WCOUNT], Qt.AlignRight) @@ -390,6 +411,7 @@ class GuiProjectOutline(QTreeWidget): newItem = QTreeWidgetItem() newItem.setText(self.colIndex[nwOutline.TITLE], novIdx["title"]) + newItem.setText(self.colIndex[nwOutline.HANDLE], tHandle) newItem.setText(self.colIndex[nwOutline.LEVEL], novIdx["level"]) newItem.setText(self.colIndex[nwOutline.LABEL], nwItem.itemName) newItem.setText(self.colIndex[nwOutline.LINE], sTitle[1:]) @@ -429,7 +451,7 @@ class GuiOutlineHeaderMenu(QMenu): self.actionMap = {} for hItem in nwOutline: - if hItem == nwOutline.TITLE: + if hItem == nwOutline.TITLE or hItem == nwOutline.HANDLE: continue self.actionMap[hItem] = QAction(nwLabels.OUTLINE_COLS[hItem], self) self.actionMap[hItem].setCheckable(True) @@ -447,7 +469,7 @@ class GuiOutlineHeaderMenu(QMenu): self.acceptToggle = False for hItem in nwOutline: - if hItem == nwOutline.TITLE or hItem not in hiddenState: + if hItem == nwOutline.TITLE or hItem == nwOutline.HANDLE or hItem not in hiddenState: continue self.actionMap[hItem].setChecked(not hiddenState[hItem]) diff --git a/nw/guimain.py b/nw/guimain.py index ad047a5c..46a32384 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -452,11 +452,13 @@ class GuiMain(QMainWindow): self.docEditor.clearEditor() return True - def openDocument(self, tHandle): + def openDocument(self, tHandle, tLine=None): + """Open a specific document, optionally at a given line. + """ if self.hasProject: self.closeDocument() self.tabWidget.setCurrentWidget(self.splitView) - if self.docEditor.loadText(tHandle): + if self.docEditor.loadText(tHandle, tLine): self.docEditor.setFocus() self.theProject.setLastEdited(tHandle) else: From c934532ea20bda740fb91f952c618810464e4992 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Apr 2020 18:06:57 +0200 Subject: [PATCH 2/2] Loosened the restriction on the synopsis tag a bit --- nw/gui/tools/dochighlight.py | 12 ++++++++---- nw/project/index.py | 9 +++++++-- sample/sampleNovel/data_6/a2d6d5f4f401_main.nwd | 2 +- sample/sampleNovel/nwProject.nwx | 6 +++--- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/nw/gui/tools/dochighlight.py b/nw/gui/tools/dochighlight.py index 26f7fe61..297cdeea 100644 --- a/nw/gui/tools/dochighlight.py +++ b/nw/gui/tools/dochighlight.py @@ -267,11 +267,15 @@ class GuiDocHighlighter(QSyntaxHighlighter): self.setFormat(4, len(theText), self.hStyles["header4"]) elif theText.startswith("%"): # Comments - if theText.startswith("%synopsis:"): - self.setFormat(0, 10, self.hStyles["modifier"]) - self.setFormat(10, len(theText), self.hStyles["hidden"]) + toCheck = theText[1:].lstrip().lower() + tLen = len(theText) + cLen = len(toCheck) + cOff = tLen - cLen + if toCheck.startswith("synopsis:"): + self.setFormat(0, cOff+9, self.hStyles["modifier"]) + self.setFormat(cOff+9, tLen, self.hStyles["hidden"]) else: - self.setFormat(0, len(theText), self.hStyles["hidden"]) + self.setFormat(0, tLen, self.hStyles["hidden"]) else: # Text Paragraph for rX, xFmt in self.rxRules: diff --git a/nw/project/index.py b/nw/project/index.py index fe0495e8..b2e20455 100644 --- a/nw/project/index.py +++ b/nw/project/index.py @@ -308,9 +308,14 @@ class NWIndex(): self.indexNoteRef(tHandle, aLine, nLine, nTitle) self.indexTag(tHandle, aLine, nLine, itemClass) - elif aLine.startswith(r"%synopsis:"): + elif aLine.startswith(r"%"): if nTitle > 0: - self.indexSynopsis(tHandle, isNovel, aLine[10:].strip(), nTitle) + toCheck = aLine[1:].lstrip().lower() + tLen = len(aLine) + cLen = len(toCheck) + cOff = tLen - cLen + if toCheck.startswith("synopsis:"): + self.indexSynopsis(tHandle, isNovel, aLine[cOff+9:].strip(), nTitle) # Count words for remaining text after last heading if nTitle > 0: diff --git a/sample/sampleNovel/data_6/a2d6d5f4f401_main.nwd b/sample/sampleNovel/data_6/a2d6d5f4f401_main.nwd index c9e0b7a9..59cb746f 100644 --- a/sample/sampleNovel/data_6/a2d6d5f4f401_main.nwd +++ b/sample/sampleNovel/data_6/a2d6d5f4f401_main.nwd @@ -3,5 +3,5 @@ @pov: Jane @location: Earth -%synopsis: We can add a chapter file, but keep the scene files separate. In the chapter file we can set the meta data that applies to the whole chapter if we wish to. +% Synopsis: We can add a chapter file, but keep the scene files separate. In the chapter file we can set the meta data that applies to the whole chapter if we wish to. diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx index 5b4c6835..f2b5d09e 100644 --- a/sample/sampleNovel/nwProject.nwx +++ b/sample/sampleNovel/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -71,7 +71,7 @@ 12 3 0 - 214 + 215 Making a Scene @@ -83,7 +83,7 @@ 1199 216 7 - 19 + 950 Another Scene