Change how tag links are followed

This commit is contained in:
Veronica Berglyd Olsen
2022-05-26 12:11:22 +02:00
parent ecc0585c87
commit 8ac30b036a
5 changed files with 123 additions and 83 deletions
+8
View File
@@ -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
+4 -2
View File
@@ -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)
+8 -28
View File
@@ -30,7 +30,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
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")
+62 -46
View File
@@ -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"<a href='#{mKey}={tag}'>{tag}</a>" for tag in refs.get(key, [])]
[f"<a href='{tag}'>{tag}</a>" for tag in refs.get(key, [])]
)
# END Class GuiOutlineDetails
+41 -7
View File
@@ -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.