diff --git a/docs/source/usage_notes.rst b/docs/source/usage_notes.rst index 38e6a7ce..b0b46143 100644 --- a/docs/source/usage_notes.rst +++ b/docs/source/usage_notes.rst @@ -24,7 +24,8 @@ Tags in Notes Each new heading in a note can have a tag associated with it. The format of a tag is ``@tag: tagname``, where tagname is a unique identifier. Tags can then be referenced in the novel documents, or cross-referenced in other notes, and will show up in the outline view and in the -back-reference panel when a document is being viewed. +back-reference panel when a document is being viewed. See :ref:`a_struct_tags` for how to reference +notes. The syntax highlighter will alert the user that the keyword is correctly used and that the tag is allowed, that is, the tag is unique. Duplicate tags should be detected as long as the index is up diff --git a/docs/source/usage_structure.rst b/docs/source/usage_structure.rst index 385d16d6..8bfe81d5 100644 --- a/docs/source/usage_structure.rst +++ b/docs/source/usage_structure.rst @@ -86,10 +86,11 @@ more details. Tag References ============== -Each text partition indicated by a heading of any level, can contain references to tags set in the -supporting notes of the project. The references are gathered by the indexer and used to generate -an outline view on the :guilabel:`Outline` tab of how the different parts of the novel are -connected. +Each text partition, indicated by a heading of any level, can contain references to tags set in the +supporting notes of the project. The references are gathered by the indexer and used to generate an +outline view on the :guilabel:`Outline` tab of how the different parts of the novel are connected. +This section covers how to set references to tags. See :ref:`a_notes_tags` for how to define tags +the references can point to. References and tags are also clickable in the document editor and viewer, making it easy to navigate between reference notes while writing. Clicked links are always opened in the view panel. diff --git a/nw/constants/constants.py b/nw/constants/constants.py index 10c44bac..9d9bd4d1 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -210,7 +210,6 @@ class nwQuotes(): """Allowed quotation marks. Source: https://en.wikipedia.org/wiki/Quotation_mark """ - SYMBOLS = { "\u0027" : "Straight single quotation mark", "\u0022" : "Straight double quotation mark", @@ -241,7 +240,6 @@ class nwQuotes(): class nwUnicode: """Supported unicode character constants and their HTML equivalents. """ - # Unicode Constants # ================= diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 91a4b255..9e4b8b89 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -416,9 +416,7 @@ class GuiDocEditor(QTextEdit): self.theIndex.scanText(tHandle, docText) if self._updateHeaders(checkLevel=True): - if self.theParent.projTabs.currentIndex() == self.theParent.idxNovelView: - logger.verbose("Document headers have changed, updating novel tree") - self.theParent.novelView.refreshTree() + self.theParent.requestNovelTreeRefresh() else: self.theParent.novelView.updateWordCounts(tHandle) @@ -1990,6 +1988,7 @@ class GuiDocEditSearch(QFrame): if theText is not None: self.searchBox.setText(theText) self.searchBox.setFocus() + self.searchBox.selectAll() if self.isRegEx: self._alertSearchValid(True) logger.verbose("Setting search text to '%s'" % theText) diff --git a/nw/gui/itemdetails.py b/nw/gui/itemdetails.py index a5415939..52584d40 100644 --- a/nw/gui/itemdetails.py +++ b/nw/gui/itemdetails.py @@ -227,55 +227,58 @@ class GuiItemDetails(QWidget): def updateViewBox(self, tHandle): """Populate the details box from a given handle. """ - self.theHandle = tHandle - nwItem = self.theProject.projTree[tHandle] + if tHandle is None: + self.clearDetails() + return + nwItem = self.theProject.projTree[tHandle] if nwItem is None: self.clearDetails() + return + self.theHandle = tHandle + theLabel = nwItem.itemName + if len(theLabel) > 100: + theLabel = theLabel[:96].rstrip()+" ..." + + itStatus = nwItem.itemStatus + if nwItem.itemClass == nwItemClass.NOVEL: + itStatus = self.theProject.statusItems.checkEntry(itStatus) # Make sure it's valid + flagIcon = self.theParent.statusIcons[itStatus] else: - theLabel = nwItem.itemName - if len(theLabel) > 100: - theLabel = theLabel[:96].rstrip()+" ..." + itStatus = self.theProject.importItems.checkEntry(itStatus) # Make sure it's valid + flagIcon = self.theParent.importIcons[itStatus] - itStatus = nwItem.itemStatus - if nwItem.itemClass == nwItemClass.NOVEL: - itStatus = self.theProject.statusItems.checkEntry(itStatus) # Make sure it's valid - flagIcon = self.theParent.statusIcons[itStatus] + if nwItem.itemType == nwItemType.FILE: + if nwItem.isExported: + self.labelFlag.setPixmap(self.expCheck) else: - itStatus = self.theProject.importItems.checkEntry(itStatus) # Make sure it's valid - flagIcon = self.theParent.importIcons[itStatus] + self.labelFlag.setPixmap(self.expCross) + else: + self.labelFlag.setPixmap(QPixmap(1, 1)) - if nwItem.itemType == nwItemType.FILE: - if nwItem.isExported: - self.labelFlag.setPixmap(self.expCheck) - else: - self.labelFlag.setPixmap(self.expCross) - else: - self.labelFlag.setPixmap(QPixmap(1, 1)) + iPx = int(round(0.8*self.theTheme.baseIconSize)) + self.statusFlag.setPixmap(flagIcon.pixmap(iPx, iPx)) + self.classFlag.setText(nwLabels.CLASS_FLAG[nwItem.itemClass]) - iPx = int(round(0.8*self.theTheme.baseIconSize)) - self.statusFlag.setPixmap(flagIcon.pixmap(iPx, iPx)) - self.classFlag.setText(nwLabels.CLASS_FLAG[nwItem.itemClass]) + if nwItem.itemLayout == nwItemLayout.NO_LAYOUT: + self.layoutFlag.setText("-") + else: + self.layoutFlag.setText(nwLabels.LAYOUT_FLAG[nwItem.itemLayout]) - if nwItem.itemLayout == nwItemLayout.NO_LAYOUT: - self.layoutFlag.setText("-") - else: - self.layoutFlag.setText(nwLabels.LAYOUT_FLAG[nwItem.itemLayout]) + self.labelData.setText(theLabel) + self.statusData.setText(nwItem.itemStatus) + self.classData.setText(nwLabels.CLASS_NAME[nwItem.itemClass]) + self.layoutData.setText(nwLabels.LAYOUT_NAME[nwItem.itemLayout]) - self.labelData.setText(theLabel) - self.statusData.setText(nwItem.itemStatus) - self.classData.setText(nwLabels.CLASS_NAME[nwItem.itemClass]) - self.layoutData.setText(nwLabels.LAYOUT_NAME[nwItem.itemLayout]) - - if nwItem.itemType == nwItemType.FILE: - self.cCountData.setText(f"{nwItem.charCount:n}") - self.wCountData.setText(f"{nwItem.wordCount:n}") - self.pCountData.setText(f"{nwItem.paraCount:n}") - else: - self.cCountData.setText("–") - self.wCountData.setText("–") - self.pCountData.setText("–") + if nwItem.itemType == nwItemType.FILE: + self.cCountData.setText(f"{nwItem.charCount:n}") + self.wCountData.setText(f"{nwItem.wordCount:n}") + self.pCountData.setText(f"{nwItem.paraCount:n}") + else: + self.cCountData.setText("–") + self.wCountData.setText("–") + self.pCountData.setText("–") return diff --git a/nw/gui/noveltree.py b/nw/gui/noveltree.py index 7a7990f8..0ceaf9ec 100644 --- a/nw/gui/noveltree.py +++ b/nw/gui/noveltree.py @@ -131,10 +131,11 @@ class GuiNovelTree(QTreeWidget): def refreshTree(self, overRide=False): """Called whenever the Novel tab is activated. """ + logger.verbose("Requesting refresh of the novel tree") treeChanged = self.theParent.treeView.changedSince(self._lastBuild) indexChanged = self.theIndex.novelChangedSince(self._lastBuild) if not (treeChanged or indexChanged): - logger.verbose("No changes made to the novel") + logger.verbose("No changes have been made to the novel index") return selItem = self.selectedItems() diff --git a/nw/gui/projload.py b/nw/gui/projload.py index d8d72c7e..29401de3 100644 --- a/nw/gui/projload.py +++ b/nw/gui/projload.py @@ -129,6 +129,9 @@ class GuiProjectLoad(QDialog): self.newButton = self.buttonBox.addButton("New", QDialogButtonBox.ActionRole) self.newButton.clicked.connect(self._doNewProject) + self.delButton = self.buttonBox.addButton("Remove", QDialogButtonBox.ActionRole) + self.delButton.clicked.connect(self._doDeleteRecent) + self.outerBox.addLayout(self.innerBox) self.outerBox.addWidget(self.buttonBox) self.setLayout(self.outerBox) @@ -138,7 +141,7 @@ class GuiProjectLoad(QDialog): keyDelete = QShortcut(self.listBox) keyDelete.setKey(QKeySequence(Qt.Key_Delete)) - keyDelete.activated.connect(self._keyPressDelete) + keyDelete.activated.connect(self._doDeleteRecent) logger.debug("GuiProjectLoad initialisation complete") @@ -212,15 +215,16 @@ class GuiProjectLoad(QDialog): self.accept() return - def _keyPressDelete(self): + def _doDeleteRecent(self): """Remove an entry from the recent projects list. """ selList = self.listBox.selectedItems() if selList: - msgYes = self.theParent.askQuestion( - "Remove Entry", - "Remove the selected entry from the recent projects list?" - ) + projName = selList[0].text(self.C_NAME) + msgYes = self.theParent.askQuestion("Remove Entry", ( + "Remove '%s' from the recent projects list? " + "The project files will not be deleted." + ) % projName) if msgYes: self.mainConf.removeFromRecentCache( selList[0].data(self.C_NAME, Qt.UserRole) diff --git a/nw/guimain.py b/nw/guimain.py index f6538bf0..3500f87a 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -817,6 +817,14 @@ class GuiMain(QMainWindow): self.novelView.refreshTree() return + def requestNovelTreeRefresh(self): + """Update the novel tree, but only if it is visible. + """ + if self.projTabs.currentIndex() == self.idxNovelView and self.hasProject: + self.novelView.refreshTree() + return True + return False + def rebuildIndex(self, beQuiet=False): """Rebuild the entire index. """ diff --git a/tests/test_gui/test_gui_projload.py b/tests/test_gui/test_gui_projload.py index 1776a6a7..41458d9d 100644 --- a/tests/test_gui/test_gui_projload.py +++ b/tests/test_gui/test_gui_projload.py @@ -102,7 +102,7 @@ def testGuiLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal): qtbot.wait(stepDelay) nwLoad.show() - nwLoad._keyPressDelete() + nwLoad._doDeleteRecent() assert nwLoad.listBox.topLevelItemCount() == recentCount - 1 getFile = os.path.join(nwMinimal, "nwProject.nwx")