From 8ac30b036a147d90606cdb3bff8285285c429473 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 26 May 2022 12:11:22 +0200
Subject: [PATCH] Change how tag links are followed
---
novelwriter/enum.py | 8 +++
novelwriter/gui/doceditor.py | 6 +-
novelwriter/gui/docviewer.py | 36 +++---------
novelwriter/gui/outline.py | 108 ++++++++++++++++++++---------------
novelwriter/guimain.py | 48 +++++++++++++---
5 files changed, 123 insertions(+), 83 deletions(-)
diff --git a/novelwriter/enum.py b/novelwriter/enum.py
index fc9604e0..48b894ba 100644
--- a/novelwriter/enum.py
+++ b/novelwriter/enum.py
@@ -62,6 +62,14 @@ class nwItemLayout(Enum):
# END Enum nwItemLayout
+class nwDocMode(Enum):
+
+ VIEW = 0
+ EDIT = 1
+
+# END Enum nwDocMode
+
+
class nwDocAction(Enum):
NO_ACTION = 0
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index 98648ab6..b73fe943 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -33,6 +33,7 @@ import bisect
import logging
import novelwriter
+from enum import Enum
from time import time
from PyQt5.QtCore import (
@@ -50,7 +51,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter.core import NWDoc, NWSpellEnchant, countWords
-from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert
+from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode
from novelwriter.common import transferCase
from novelwriter.constants import nwConst, nwKeyWords, nwUnicode
from novelwriter.gui.dochighlight import GuiDocHighlighter
@@ -69,6 +70,7 @@ class GuiDocEditor(QTextEdit):
spellDictionaryChanged = pyqtSignal(str, str)
docEditedStatusChanged = pyqtSignal(bool)
docCountsChanged = pyqtSignal(str, int, int, int)
+ loadDocumentTagRequest = pyqtSignal(str, Enum)
def __init__(self, theParent):
QTextEdit.__init__(self, theParent)
@@ -1895,7 +1897,7 @@ class GuiDocEditor(QTextEdit):
if loadTag:
logger.verbose("Attempting to follow tag '%s'", theWord)
- self.theParent.docViewer.loadFromTag(theWord)
+ self.loadDocumentTagRequest.emit(theWord, nwDocMode.VIEW)
else:
logger.verbose("Potential tag '%s'", theWord)
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index 4c293da5..10456369 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -30,7 +30,9 @@ along with this program. If not, see .
import logging
import novelwriter
-from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSlot
+from enum import Enum
+
+from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSlot, pyqtSignal
from PyQt5.QtGui import (
QTextOption, QFont, QPalette, QColor, QTextCursor, QIcon, QCursor
)
@@ -40,7 +42,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter.core import ToHtml
-from novelwriter.enum import nwAlert, nwItemType, nwDocAction
+from novelwriter.enum import nwItemType, nwDocAction, nwDocMode
from novelwriter.error import logException
from novelwriter.constants import nwUnicode
@@ -49,6 +51,8 @@ logger = logging.getLogger(__name__)
class GuiDocViewer(QTextBrowser):
+ loadDocumentTagRequest = pyqtSignal(str, Enum)
+
def __init__(self, theParent):
QTextBrowser.__init__(self, theParent)
@@ -239,30 +243,6 @@ class GuiDocViewer(QTextBrowser):
self.updateDocMargins()
return
- def loadFromTag(self, theTag):
- """Load text in the document from a reference given by a meta
- tag rather than a known handle. This function depends on the
- index being up to date.
- """
- logger.debug("Loading document from tag '%s'", theTag)
- tHandle, _, sTitle = self.theParent.theIndex.getTagSource(theTag)
- if tHandle is None:
- self.theParent.makeAlert(self.tr(
- "Could not find the reference for tag '{0}'. It either doesn't "
- "exist, or the index is out of date. The index can be updated "
- "from the Tools menu, or by pressing {1}."
- ).format(
- theTag, "F9"
- ), nwAlert.ERROR)
- return False
- else:
- # Let the parent handle the opening as it also ensures that
- # the doc view panel is visible in case this request comes
- # from outside this class.
- logger.verbose("Tag points to '%s#%s'", tHandle, sTitle)
- self.theParent.viewDocument(tHandle, "#%s" % sTitle)
- return True
-
def docAction(self, theAction):
"""Wrapper function for various document actions on the current
document.
@@ -414,14 +394,14 @@ class GuiDocViewer(QTextBrowser):
@pyqtSlot("QUrl")
def _linkClicked(self, theURL):
- """Slot for a link in the document being clicked.
+ """Process a clicked link internally in the document.
"""
theLink = theURL.url()
logger.verbose("Clicked link: '%s'", theLink)
if len(theLink) > 0:
theBits = theLink.split("=")
if len(theBits) == 2:
- self.loadFromTag(theBits[1])
+ self.loadDocumentTagRequest.emit(theBits[1], nwDocMode.VIEW)
return
@pyqtSlot("QPoint")
diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py
index 6b50df5f..21af7e4d 100644
--- a/novelwriter/gui/outline.py
+++ b/novelwriter/gui/outline.py
@@ -43,7 +43,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter.enum import (
- nwItemClass, nwItemLayout, nwItemType, nwOutline, nwView
+ nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
)
from novelwriter.common import checkInt
from novelwriter.constants import trConst, nwKeyWords, nwLabels
@@ -54,14 +54,13 @@ logger = logging.getLogger(__name__)
class GuiOutline(QWidget):
- viewChangeRequested = pyqtSignal(nwView)
+ loadDocumentTagRequest = pyqtSignal(str, Enum)
def __init__(self, theParent):
QWidget.__init__(self, theParent)
- self.mainConf = novelwriter.CONFIG
- self.theParent = theParent
- self.theProject = theParent.theProject
+ self.mainConf = novelwriter.CONFIG
+ self.theParent = theParent
self.outlineBar = GuiOutlineToolBar(self)
self.outlineView = GuiOutlineView(self)
@@ -82,7 +81,9 @@ class GuiOutline(QWidget):
# Connect Signals
self.outlineView.hiddenStateChanged.connect(self._updateMenuColumns)
- self.outlineBar.columnToggled.connect(self.outlineView.menuColumnToggled)
+ self.outlineView.activeItemChanged.connect(self.outlineData.showItem)
+ self.outlineData.itemTagClicked.connect(self._tagClicked)
+ self.outlineBar.viewColumnToggled.connect(self.outlineView.menuColumnToggled)
self.outlineBar.viewRefreshRequested.connect(
lambda: self.outlineView.refreshTree(overRide=True)
)
@@ -144,13 +145,22 @@ class GuiOutline(QWidget):
self.outlineBar.setColumnHiddenState(self.outlineView.hiddenColumns)
return
+ @pyqtSlot(str)
+ def _tagClicked(self, link):
+ """Capture the click of a tag in the details panel.
+ """
+ if link:
+ self.loadDocumentTagRequest.emit(link, nwDocMode.VIEW)
+ return
+
# END Class GuiOutline
class GuiOutlineToolBar(QToolBar):
- columnToggled = pyqtSignal(bool, Enum)
+ novelRootChanged = pyqtSignal(str)
viewRefreshRequested = pyqtSignal()
+ viewColumnToggled = pyqtSignal(bool, Enum)
def __init__(self, theOutline):
QTreeWidget.__init__(self, theOutline)
@@ -158,7 +168,6 @@ class GuiOutlineToolBar(QToolBar):
logger.debug("Initialising GuiOutlineToolBar ...")
self.mainConf = novelwriter.CONFIG
- self.theOutline = theOutline
self.theParent = theOutline.theParent
self.theProject = theOutline.theParent.theProject
self.theTheme = theOutline.theParent.theTheme
@@ -180,6 +189,7 @@ class GuiOutlineToolBar(QToolBar):
self.novelValue = QComboBox(self)
self.novelValue.setMinimumWidth(self.mainConf.pxInt(200))
+ self.novelValue.currentIndexChanged.connect(self._novelValueChanged)
# Actions
self.aRefresh = QAction(self.tr("Refresh"), self)
@@ -191,7 +201,7 @@ class GuiOutlineToolBar(QToolBar):
# Column Menu
self.mColumns = GuiOutlineHeaderMenu(self)
self.mColumns.columnToggled.connect(
- lambda isChecked, tItem: self.columnToggled.emit(isChecked, tItem)
+ lambda isChecked, tItem: self.viewColumnToggled.emit(isChecked, tItem)
)
self.tbColumns = QToolButton(self)
@@ -207,10 +217,12 @@ class GuiOutlineToolBar(QToolBar):
self.addWidget(self.tbColumns)
self.addWidget(stretch)
- self.populateNovelList()
-
logger.debug("GuiOutlineToolBar initialisation complete")
+ ##
+ # Methods
+ ##
+
def populateNovelList(self):
"""Fill the novel combo box.
"""
@@ -223,9 +235,23 @@ class GuiOutlineToolBar(QToolBar):
return
def setColumnHiddenState(self, hiddenState):
+ """Forward the change of column hidden states to the menu.
+ """
self.mColumns.setHiddenState(hiddenState)
return
+ ##
+ # Slots
+ ##
+
+ @pyqtSlot(int)
+ def _novelValueChanged(self, index):
+ """Emit a signal containing the handle of the selected item.
+ """
+ if index >= 0:
+ self.novelRootChanged.emit(self.novelValue.currentData())
+ return
+
# END Class GuiOutlineToolBar
@@ -272,6 +298,7 @@ class GuiOutlineView(QTreeWidget):
}
hiddenStateChanged = pyqtSignal()
+ activeItemChanged = pyqtSignal(str, str)
def __init__(self, theOutline):
QTreeWidget.__init__(self, theOutline)
@@ -279,7 +306,6 @@ class GuiOutlineView(QTreeWidget):
logger.debug("Initialising GuiOutlineView ...")
self.mainConf = novelwriter.CONFIG
- self.theOutline = theOutline
self.theParent = theOutline.theParent
self.theProject = theOutline.theParent.theProject
self.theTheme = theOutline.theParent.theTheme
@@ -436,7 +462,7 @@ class GuiOutlineView(QTreeWidget):
if selItems:
tHandle = selItems[0].data(self._colIdx[nwOutline.TITLE], Qt.UserRole)
sTitle = selItems[0].data(self._colIdx[nwOutline.LINE], Qt.UserRole)
- self.theOutline.outlineData.showItem(tHandle, sTitle)
+ self.activeItemChanged.emit(tHandle, sTitle)
return
@@ -729,6 +755,8 @@ class GuiOutlineDetails(QScrollArea):
"H4": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Section"),
}
+ itemTagClicked = pyqtSignal(str)
+
def __init__(self, theOutline):
QScrollArea.__init__(self, theOutline)
@@ -828,15 +856,18 @@ class GuiOutlineDetails(QScrollArea):
self.entKeyValue.setWordWrap(True)
self.cstKeyValue.setWordWrap(True)
- self.povKeyValue.linkActivated.connect(self._tagClicked)
- self.focKeyValue.linkActivated.connect(self._tagClicked)
- self.chrKeyValue.linkActivated.connect(self._tagClicked)
- self.pltKeyValue.linkActivated.connect(self._tagClicked)
- self.timKeyValue.linkActivated.connect(self._tagClicked)
- self.wldKeyValue.linkActivated.connect(self._tagClicked)
- self.objKeyValue.linkActivated.connect(self._tagClicked)
- self.entKeyValue.linkActivated.connect(self._tagClicked)
- self.cstKeyValue.linkActivated.connect(self._tagClicked)
+ def tagClicked(link):
+ self.itemTagClicked.emit(link)
+
+ self.povKeyValue.linkActivated.connect(tagClicked)
+ self.focKeyValue.linkActivated.connect(tagClicked)
+ self.chrKeyValue.linkActivated.connect(tagClicked)
+ self.pltKeyValue.linkActivated.connect(tagClicked)
+ self.timKeyValue.linkActivated.connect(tagClicked)
+ self.wldKeyValue.linkActivated.connect(tagClicked)
+ self.objKeyValue.linkActivated.connect(tagClicked)
+ self.entKeyValue.linkActivated.connect(tagClicked)
+ self.cstKeyValue.linkActivated.connect(tagClicked)
self.povKeyLWrap.addWidget(self.povKeyValue, 1)
self.focKeyLWrap.addWidget(self.focKeyValue, 1)
@@ -963,6 +994,11 @@ class GuiOutlineDetails(QScrollArea):
self.updateClasses()
return
+ ##
+ # Slots
+ ##
+
+ @pyqtSlot(str, str)
def showItem(self, tHandle, sTitle):
"""Update the content of the tree with the given handle and line
number pointing to a header.
@@ -1006,22 +1042,6 @@ class GuiOutlineDetails(QScrollArea):
return True
- ##
- # Slots
- ##
-
- @pyqtSlot(str)
- def _tagClicked(self, theLink):
- """Capture the click of a tag in the right-most column.
- """
- logger.verbose("Clicked link: '%s'", theLink)
- if len(theLink) > 0:
- theBits = theLink.split("=")
- if len(theBits) == 2:
- self.theOutline.viewChangeRequested.emit(nwView.PROJECT)
- self.theParent.docViewer.loadFromTag(theBits[1])
- return
-
@pyqtSlot()
def updateClasses(self):
"""Update the visibility status of class details.
@@ -1050,16 +1070,12 @@ class GuiOutlineDetails(QScrollArea):
return
- ##
- # Internal Functions
- ##
-
- def _formatTags(self, refs, key):
- """Format the tags as clickable links.
+ @staticmethod
+ def _formatTags(refs, key):
+ """Convert a list of tags into a list of clickable tag links.
"""
- mKey = key[1:]
return ", ".join(
- [f"{tag}" for tag in refs.get(key, [])]
+ [f"{tag}" for tag in refs.get(key, [])]
)
# END Class GuiOutlineDetails
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 80fd06e2..67bf126b 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -27,6 +27,7 @@ import os
import logging
import novelwriter
+from enum import Enum
from time import time
from datetime import datetime
@@ -52,7 +53,7 @@ from novelwriter.tools import (
)
from novelwriter.core import NWProject, NWIndex
from novelwriter.enum import (
- nwItemType, nwItemClass, nwAlert, nwWidget, nwState, nwView
+ nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwState, nwView
)
from novelwriter.common import getGuiItem, hexToInt
@@ -117,10 +118,7 @@ class GuiMain(QMainWindow):
self.viewsBar = GuiViewsBar(self)
# Connect Signals Between Main Elements
- self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
- self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
- self.docEditor.docCountsChanged.connect(self.treeMeta.doUpdateCounts)
- self.docEditor.docCountsChanged.connect(self.treeView.doUpdateCounts)
+ self.viewsBar.viewChangeRequested.connect(self._changeView)
self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
@@ -128,8 +126,15 @@ class GuiMain(QMainWindow):
self.treeView.wordCountsChanged.connect(self._updateStatusWordCount)
self.treeView.rootFoldersChanged.connect(self.projView.projectUpdated)
- self.viewsBar.viewChangeRequested.connect(self._changeView)
- self.projView.viewChangeRequested.connect(self._changeView)
+ self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
+ self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
+ self.docEditor.docCountsChanged.connect(self.treeMeta.doUpdateCounts)
+ self.docEditor.docCountsChanged.connect(self.treeView.doUpdateCounts)
+ self.docEditor.loadDocumentTagRequest.connect(self._followTag)
+
+ self.docViewer.loadDocumentTagRequest.connect(self._followTag)
+
+ self.projView.loadDocumentTagRequest.connect(self._followTag)
# Project Tree Stack
self.projStack = QStackedWidget()
@@ -1444,6 +1449,23 @@ class GuiMain(QMainWindow):
return projData
+ def _getTagSource(self, tTag):
+ """A wrapper function for the index lookup of a tag that will
+ display an alert if the tag cannot be found.
+ """
+ tHandle, _, sTitle = self.theIndex.getTagSource(tTag)
+ if tHandle is None:
+ self.makeAlert(self.tr(
+ "Could not find the reference for tag '{0}'. It either doesn't "
+ "exist, or the index is out of date. The index can be updated "
+ "from the Tools menu, or by pressing {1}."
+ ).format(
+ tTag, "F9"
+ ), nwAlert.ERROR)
+ return None, None
+
+ return tHandle, sTitle
+
##
# Events
##
@@ -1462,6 +1484,18 @@ class GuiMain(QMainWindow):
# Slots
##
+ @pyqtSlot(str, Enum)
+ def _followTag(self, tTag, tMode):
+ """Follow a tag after user interaction with a link.
+ """
+ tHandle, sTitle = self._getTagSource(tTag)
+ if tHandle is not None:
+ if tMode == nwDocMode.EDIT:
+ self.openDocument(tHandle)
+ elif tMode == nwDocMode.VIEW:
+ self.viewDocument(tHandle=tHandle, tAnchor=f"#{sTitle}")
+ return
+
@pyqtSlot(nwView)
def _changeView(self, view):
"""Handle the requested change of view from the GuiViewBar.