Change how tag links are followed
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user