Connect the new viewer panel and add icon for it

This commit is contained in:
Veronica Berglyd Olsen
2023-11-14 22:41:39 +01:00
parent 1d89e4935f
commit 4d7ef63e83
12 changed files with 184 additions and 275 deletions
+59 -225
View File
@@ -4,7 +4,6 @@ novelWriter GUI Document Viewer
File History:
Created: 2019-05-10 [0.0.1] GuiDocViewer
Created: 2019-10-31 [0.3.2] GuiDocViewDetails
Created: 2020-04-25 [0.4.5] GuiDocViewHeader
Created: 2020-06-09 [0.8] GuiDocViewFooter
Created: 2020-09-08 [1.0b1] GuiDocViewHistory
@@ -38,8 +37,8 @@ from PyQt5.QtGui import (
QTextOption
)
from PyQt5.QtWidgets import (
QAction, qApp, QFrame, QHBoxLayout, QLabel, QMenu, QScrollArea,
QTextBrowser, QToolButton, QWidget
QAction, qApp, QFrame, QHBoxLayout, QLabel, QMenu, QTextBrowser,
QToolButton, QWidget
)
from novelwriter import CONFIG, SHARED
@@ -58,6 +57,7 @@ logger = logging.getLogger(__name__)
class GuiDocViewer(QTextBrowser):
loadDocumentTagRequest = pyqtSignal(str, Enum)
togglePanelVisibility = pyqtSignal()
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
@@ -242,7 +242,7 @@ class GuiDocViewer(QTextBrowser):
self.updateDocMargins()
# Make sure the main GUI knows we changed the content
self.mainGui.viewMeta.refreshReferences(tHandle)
# self.mainGui.viewMeta.refreshReferences(tHandle)
# Since we change the content while it may still be rendering, we mark
# the document dirty again to make sure it's re-rendered properly.
@@ -342,7 +342,7 @@ class GuiDocViewer(QTextBrowser):
@pyqtSlot(str)
def updateDocInfo(self, tHandle: str) -> None:
"""Update the header titlebar if needed."""
"""Update the header title bar if needed."""
if tHandle == self._docHandle:
self.docHeader.setTitleFromHandle(self._docHandle)
self.updateDocMargins()
@@ -530,20 +530,16 @@ class GuiDocViewer(QTextBrowser):
class GuiDocViewHistory:
def __init__(self, docViewer):
def __init__(self, docViewer: GuiDocViewer) -> None:
self.docViewer = docViewer
self._navHistory = []
self._posHistory = []
self._currPos = -1
self._prevPos = -1
return
def clear(self):
"""Clear the view history.
"""
def clear(self) -> None:
"""Clear the view history."""
logger.debug("View history cleared")
self._navHistory = []
self._posHistory = []
@@ -551,7 +547,7 @@ class GuiDocViewHistory:
self._prevPos = -1
return
def append(self, tHandle):
def append(self, tHandle: str) -> bool:
"""Append a document handle and its scroll bar position to the
history, but only if the document is different than the current
active entry. Any further entries are truncated.
@@ -577,74 +573,61 @@ class GuiDocViewHistory:
return True
def forward(self):
"""Navigate to the next entry in the view history.
"""
def forward(self) -> None:
"""Navigate to the next entry in the view history."""
newPos = self._currPos + 1
if newPos < len(self._navHistory):
logger.debug("Move forward in view history")
self._prevPos = self._currPos
self._updateScrollBar()
self.docViewer.loadText(self._navHistory[newPos], updateHistory=False)
self.docViewer.setScrollPosition(self._posHistory[newPos])
self._currPos = newPos
self._updateNavButtons()
self._dumpHistory()
return
def backward(self):
"""Navigate to the previous entry in the view history.
"""
def backward(self) -> None:
"""Navigate to the previous entry in the view history."""
newPos = self._currPos - 1
if newPos >= 0:
logger.debug("Move backward in view history")
self._prevPos = self._currPos
self._updateScrollBar()
self.docViewer.loadText(self._navHistory[newPos], updateHistory=False)
self.docViewer.setScrollPosition(self._posHistory[newPos])
self._currPos = newPos
self._updateNavButtons()
self._dumpHistory()
return
##
# Internal Functions
##
def _updateScrollBar(self):
"""Update the scrollbar position of the previous entry.
"""
def _updateScrollBar(self) -> None:
"""Update the scrollbar position of the previous entry."""
if self._prevPos >= 0 and self._prevPos < len(self._posHistory):
self._posHistory[self._prevPos] = self.docViewer.scrollPosition
return
def _updateNavButtons(self):
"""Update the navigation buttons in the document header.
"""
def _updateNavButtons(self) -> None:
"""Update the navigation buttons in the document header."""
self.docViewer.docHeader.updateNavButtons(0, len(self._navHistory) - 1, self._currPos)
return
def _truncateHistory(self, atPos):
def _truncateHistory(self, atPos: int) -> None:
"""Truncate the navigation history to the given position. Also
enforces a maximum length of the navigation history to 20.
"""
nSkip = 1 if atPos > 19 else 0
self._navHistory = self._navHistory[nSkip:atPos + 1]
self._posHistory = self._posHistory[nSkip:atPos + 1]
self._currPos -= nSkip
self._prevPos -= nSkip
return
def _dumpHistory(self):
def _dumpHistory(self) -> None:
"""Debug function to dump history to the logger. Since it is a
for loop, it is skipped entirely if log level isn't DEBUG.
"""
@@ -667,7 +650,7 @@ class GuiDocViewHistory:
class GuiDocViewHeader(QWidget):
def __init__(self, docViewer):
def __init__(self, docViewer: GuiDocViewer) -> None:
super().__init__(parent=docViewer)
logger.debug("Create: GuiDocViewHeader")
@@ -763,9 +746,8 @@ class GuiDocViewHeader(QWidget):
# Methods
##
def updateTheme(self):
"""Update theme elements.
"""
def updateTheme(self) -> None:
"""Update theme elements."""
self.backButton.setIcon(SHARED.theme.getIcon("backward"))
self.forwardButton.setIcon(SHARED.theme.getIcon("forward"))
self.refreshButton.setIcon(SHARED.theme.getIcon("refresh"))
@@ -785,7 +767,7 @@ class GuiDocViewHeader(QWidget):
return
def matchColours(self):
def matchColours(self) -> None:
"""Update the colours of the widget to match those of the syntax
theme rather than the main GUI.
"""
@@ -793,13 +775,11 @@ class GuiDocViewHeader(QWidget):
thePalette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
thePalette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
thePalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
self.setPalette(thePalette)
self.theTitle.setPalette(thePalette)
return
def setTitleFromHandle(self, tHandle):
def setTitleFromHandle(self, tHandle: str | None) -> bool:
"""Sets the document title from the handle, or alternatively,
set the whole document path.
"""
@@ -835,28 +815,25 @@ class GuiDocViewHeader(QWidget):
return True
def updateNavButtons(self, firstIdx, lastIdx, currIdx):
"""Enable and disable nav buttons based on index in history.
"""
def updateNavButtons(self, firstIdx: int, lastIdx: int, currIdx: int) -> None:
"""Enable and disable nav buttons based on index in history."""
self.backButton.setEnabled(currIdx > firstIdx)
self.forwardButton.setEnabled(currIdx < lastIdx)
return
##
# Slots
# Private Slots
##
@pyqtSlot()
def _closeDocument(self):
"""Trigger the close editor/viewer on the main window.
"""
def _closeDocument(self) -> None:
"""Trigger the close editor/viewer on the main window."""
self.mainGui.closeDocViewer()
return
@pyqtSlot()
def _refreshDocument(self):
"""Reload the content of the document.
"""
def _refreshDocument(self) -> None:
"""Reload the content of the document."""
if self.docViewer.docHandle == self.mainGui.docEditor.docHandle:
self.mainGui.saveDocument()
self.docViewer.reloadText()
@@ -866,7 +843,7 @@ class GuiDocViewHeader(QWidget):
# Events
##
def mousePressEvent(self, theEvent):
def mousePressEvent(self, event: QMouseEvent) -> None:
"""Capture a click on the title and ensure that the item is
selected in the project tree.
"""
@@ -883,14 +860,13 @@ class GuiDocViewHeader(QWidget):
class GuiDocViewFooter(QWidget):
def __init__(self, docViewer):
def __init__(self, docViewer: GuiDocViewer) -> None:
super().__init__(parent=docViewer)
logger.debug("Create: GuiDocViewFooter")
self.docViewer = docViewer
self.mainGui = docViewer.mainGui
self.viewMeta = docViewer.mainGui.viewMeta
# Internal Variables
self._docHandle = None
@@ -908,19 +884,8 @@ class GuiDocViewFooter(QWidget):
self.showHide.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.showHide.setIconSize(QSize(fPx, fPx))
self.showHide.setFixedSize(QSize(fPx, fPx))
self.showHide.clicked.connect(self._doShowHide)
self.showHide.setToolTip(self.tr("Show/hide the references panel"))
# Sticky Button
self.stickyRefs = QToolButton(self)
self.stickyRefs.setCheckable(True)
self.stickyRefs.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.stickyRefs.setIconSize(QSize(fPx, fPx))
self.stickyRefs.setFixedSize(QSize(fPx, fPx))
self.stickyRefs.toggled.connect(self._doToggleSticky)
self.stickyRefs.setToolTip(self.tr(
"Activate to freeze the content of the references panel when changing document"
))
self.showHide.clicked.connect(lambda: self.docViewer.togglePanelVisibility.emit())
self.showHide.setToolTip(self.tr("Show/Hide Viewer Panel"))
# Show Comments
self.showComments = QToolButton(self)
@@ -930,7 +895,7 @@ class GuiDocViewFooter(QWidget):
self.showComments.setIconSize(QSize(fPx, fPx))
self.showComments.setFixedSize(QSize(fPx, fPx))
self.showComments.toggled.connect(self._doToggleComments)
self.showComments.setToolTip(self.tr("Show comments"))
self.showComments.setToolTip(self.tr("Show Comments"))
# Show Synopsis
self.showSynopsis = QToolButton(self)
@@ -940,27 +905,9 @@ class GuiDocViewFooter(QWidget):
self.showSynopsis.setIconSize(QSize(fPx, fPx))
self.showSynopsis.setFixedSize(QSize(fPx, fPx))
self.showSynopsis.toggled.connect(self._doToggleSynopsis)
self.showSynopsis.setToolTip(self.tr("Show synopsis comments"))
self.showSynopsis.setToolTip(self.tr("Show Synopsis Comments"))
# Labels
self.lblRefs = QLabel(self.tr("References"))
self.lblRefs.setBuddy(self.showHide)
self.lblRefs.setIndent(0)
self.lblRefs.setMargin(0)
self.lblRefs.setContentsMargins(0, 0, 0, 0)
self.lblRefs.setAutoFillBackground(True)
self.lblRefs.setFixedHeight(fPx)
self.lblRefs.setAlignment(Qt.AlignLeft | Qt.AlignTop)
self.lblSticky = QLabel(self.tr("Sticky"))
self.lblSticky.setBuddy(self.stickyRefs)
self.lblSticky.setIndent(0)
self.lblSticky.setMargin(0)
self.lblSticky.setContentsMargins(0, 0, 0, 0)
self.lblSticky.setAutoFillBackground(True)
self.lblSticky.setFixedHeight(fPx)
self.lblSticky.setAlignment(Qt.AlignLeft | Qt.AlignTop)
self.lblComments = QLabel(self.tr("Comments"))
self.lblComments.setBuddy(self.showComments)
self.lblComments.setIndent(0)
@@ -981,8 +928,6 @@ class GuiDocViewFooter(QWidget):
lblFont = self.font()
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
self.lblRefs.setFont(lblFont)
self.lblSticky.setFont(lblFont)
self.lblComments.setFont(lblFont)
self.lblSynopsis.setFont(lblFont)
@@ -990,10 +935,6 @@ class GuiDocViewFooter(QWidget):
self.outerBox = QHBoxLayout()
self.outerBox.setSpacing(bSp)
self.outerBox.addWidget(self.showHide, 0)
self.outerBox.addWidget(self.lblRefs, 0)
self.outerBox.addSpacing(hSp)
self.outerBox.addWidget(self.stickyRefs, 0)
self.outerBox.addWidget(self.lblSticky, 0)
self.outerBox.addStretch(1)
self.outerBox.addWidget(self.showComments, 0)
self.outerBox.addWidget(self.lblComments, 0)
@@ -1024,23 +965,19 @@ class GuiDocViewFooter(QWidget):
"""Update theme elements."""
# Icons
fPx = int(0.9*SHARED.theme.fontPixelSize)
stickyIcon = SHARED.theme.getToggleIcon("sticky", (fPx, fPx))
bulletIcon = SHARED.theme.getToggleIcon("bullet", (fPx, fPx))
self.showHide.setIcon(SHARED.theme.getIcon("reference"))
self.stickyRefs.setIcon(stickyIcon)
self.showHide.setIcon(SHARED.theme.getIcon("panel"))
self.showComments.setIcon(bulletIcon)
self.showSynopsis.setIcon(bulletIcon)
# StyleSheets
buttonStyle = (
"QToolButton {{border: none; background: transparent;}} "
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
).format(*SHARED.theme.colText)
self.showHide.setStyleSheet(buttonStyle)
self.stickyRefs.setStyleSheet(buttonStyle)
self.showComments.setStyleSheet(buttonStyle)
self.showSynopsis.setStyleSheet(buttonStyle)
@@ -1048,138 +985,35 @@ class GuiDocViewFooter(QWidget):
return
def matchColours(self):
def matchColours(self) -> None:
"""Update the colours of the widget to match those of the syntax
theme rather than the main GUI.
"""
thePalette = QPalette()
thePalette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
thePalette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
thePalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
self.setPalette(thePalette)
self.lblRefs.setPalette(thePalette)
self.lblSticky.setPalette(thePalette)
self.lblComments.setPalette(thePalette)
self.lblSynopsis.setPalette(thePalette)
return
##
# Slots
##
@pyqtSlot()
def _doShowHide(self):
"""Toggle the expand/collapse of the panel.
"""
isVisible = self.viewMeta.isVisible()
self.viewMeta.setVisible(not isVisible)
return
@pyqtSlot(bool)
def _doToggleSticky(self, theState):
"""Toggle the sticky flag for the reference panel.
"""
logger.debug("Reference sticky is %s", str(theState))
self.docViewer.stickyRef = theState
if not theState and self.docViewer.docHandle is not None:
self.viewMeta.refreshReferences(self.docViewer.docHandle)
return
@pyqtSlot(bool)
def _doToggleComments(self, theState):
"""Toggle the view comment button and reload the document.
"""
CONFIG.viewComments = theState
self.docViewer.reloadText()
return
@pyqtSlot(bool)
def _doToggleSynopsis(self, theState):
"""Toggle the view synopsis button and reload the document.
"""
CONFIG.viewSynopsis = theState
self.docViewer.reloadText()
return
# END Class GuiDocViewFooter
# =============================================================================================== #
# The Document Back-Reference Panel
# Placed in a separate QSplitter position in the main GUI window
# =============================================================================================== #
class GuiDocViewDetails(QScrollArea):
def __init__(self, mainGui):
super().__init__(parent=mainGui)
logger.debug("Create: GuiDocViewDetails")
self.mainGui = mainGui
self.refList = QLabel("")
self.refList.setWordWrap(True)
self.refList.setAlignment(Qt.AlignTop)
self.refList.setScaledContents(True)
self.refList.linkActivated.connect(self._linkClicked)
self.linkStyle = "style='color: rgb({0},{1},{2})'".format(*SHARED.theme.colLink)
# Assemble
self.outerWidget = QWidget()
self.outerBox = QHBoxLayout()
self.outerBox.addWidget(self.refList, 1)
self.outerWidget.setLayout(self.outerBox)
self.setWidget(self.outerWidget)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.setWidgetResizable(True)
self.setMinimumHeight(CONFIG.pxInt(50))
self.setFrameStyle(QFrame.NoFrame)
logger.debug("Ready: GuiDocViewDetails")
return
def refreshReferences(self, tHandle):
"""Update the current list of document references from the
project index.
"""
if self.mainGui.docViewer.stickyRef:
return
theRefs = SHARED.project.index.getBackReferenceList(tHandle)
theList = []
for tHandle in theRefs:
tItem = SHARED.project.tree[tHandle]
if tItem is not None:
theList.append("<a href='%s#%s' %s>%s</a>" % (
tHandle, theRefs[tHandle], self.linkStyle, tItem.itemName
))
self.refList.setText(", ".join(theList))
palette = QPalette()
palette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
palette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
palette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
self.setPalette(palette)
self.lblComments.setPalette(palette)
self.lblSynopsis.setPalette(palette)
return
##
# Private Slots
##
@pyqtSlot(str)
def _linkClicked(self, theLink):
"""Capture the link-click and forward it to the document viewer
class for handling.
"""
logger.debug("Clicked link: '%s'", theLink)
if len(theLink) >= 13:
tHandle = theLink[:13]
tAnchor = theLink[13:] or None
self.mainGui.viewDocument(tHandle, tAnchor)
@pyqtSlot(bool)
def _doToggleComments(self, state: bool) -> None:
"""Toggle the view comment button and reload the document."""
CONFIG.viewComments = state
self.docViewer.reloadText()
return
# END Class GuiDocViewDetails
@pyqtSlot(bool)
def _doToggleSynopsis(self, state: bool) -> None:
"""Toggle the view synopsis button and reload the document."""
CONFIG.viewSynopsis = state
self.docViewer.reloadText()
return
# END Class GuiDocViewFooter