Add outline navigation to viewer
This commit is contained in:
@@ -519,11 +519,16 @@ class NWIndex:
|
|||||||
|
|
||||||
def getItemHeading(self, tHandle: str, sTitle: str) -> IndexHeading | None:
|
def getItemHeading(self, tHandle: str, sTitle: str) -> IndexHeading | None:
|
||||||
"""Get the heading entry for a specific item and heading."""
|
"""Get the heading entry for a specific item and heading."""
|
||||||
tItem = self._itemIndex[tHandle]
|
if tItem := self._itemIndex[tHandle]:
|
||||||
if isinstance(tItem, IndexItem):
|
|
||||||
return tItem[sTitle]
|
return tItem[sTitle]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def iterItemHeadings(self, tHandle: str) -> Iterator[str, IndexHeading]:
|
||||||
|
"""Get all headings for a specific item."""
|
||||||
|
if tItem := self._itemIndex[tHandle]:
|
||||||
|
yield from tItem.items()
|
||||||
|
return []
|
||||||
|
|
||||||
def novelStructure(
|
def novelStructure(
|
||||||
self, rootHandle: str | None = None, activeOnly: bool = True
|
self, rootHandle: str | None = None, activeOnly: bool = True
|
||||||
) -> Iterator[tuple[str, str, str, IndexHeading]]:
|
) -> Iterator[tuple[str, str, str, IndexHeading]]:
|
||||||
@@ -900,13 +905,11 @@ class ItemIndex:
|
|||||||
|
|
||||||
if rHandle is None:
|
if rHandle is None:
|
||||||
for sTitle in self._items[tHandle].headings():
|
for sTitle in self._items[tHandle].headings():
|
||||||
hItem = self._items[tHandle][sTitle]
|
if hItem := self._items[tHandle][sTitle]:
|
||||||
if hItem:
|
|
||||||
yield tHandle, sTitle, hItem
|
yield tHandle, sTitle, hItem
|
||||||
elif tItem.itemRoot == rHandle:
|
elif tItem.itemRoot == rHandle:
|
||||||
for sTitle in self._items[tHandle].headings():
|
for sTitle in self._items[tHandle].headings():
|
||||||
hItem = self._items[tHandle][sTitle]
|
if hItem := self._items[tHandle][sTitle]:
|
||||||
if hItem:
|
|
||||||
yield tHandle, sTitle, hItem
|
yield tHandle, sTitle, hItem
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2974,6 +2974,8 @@ class GuiDocEditHeader(QWidget):
|
|||||||
self.outlineButton.setVisible(False)
|
self.outlineButton.setVisible(False)
|
||||||
self.closeButton.setVisible(False)
|
self.closeButton.setVisible(False)
|
||||||
self.minmaxButton.setVisible(False)
|
self.minmaxButton.setVisible(False)
|
||||||
|
self.outlineMenu.clear()
|
||||||
|
self._docOutline = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
if CONFIG.showFullPath:
|
if CONFIG.showFullPath:
|
||||||
@@ -3012,8 +3014,11 @@ class GuiDocEditHeader(QWidget):
|
|||||||
self.closeDocumentRequest.emit()
|
self.closeDocumentRequest.emit()
|
||||||
self.tbButton.setVisible(False)
|
self.tbButton.setVisible(False)
|
||||||
self.searchButton.setVisible(False)
|
self.searchButton.setVisible(False)
|
||||||
|
self.outlineButton.setVisible(False)
|
||||||
self.closeButton.setVisible(False)
|
self.closeButton.setVisible(False)
|
||||||
self.minmaxButton.setVisible(False)
|
self.minmaxButton.setVisible(False)
|
||||||
|
self.outlineMenu.clear()
|
||||||
|
self._docOutline = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
|
|||||||
@@ -240,6 +240,10 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
self._docHandle = tHandle
|
self._docHandle = tHandle
|
||||||
SHARED.project.data.setLastHandle(tHandle, "viewer")
|
SHARED.project.data.setLastHandle(tHandle, "viewer")
|
||||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||||
|
self.docHeader.setOutline({
|
||||||
|
sTitle: hItem.title
|
||||||
|
for sTitle, hItem in SHARED.project.index.iterItemHeadings(tHandle)
|
||||||
|
})
|
||||||
self.updateDocMargins()
|
self.updateDocMargins()
|
||||||
|
|
||||||
# Since we change the content while it may still be rendering, we mark
|
# Since we change the content while it may still be rendering, we mark
|
||||||
@@ -281,13 +285,6 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def navigateTo(self, tAnchor: str) -> None:
|
|
||||||
"""Go to a specific #link in the document."""
|
|
||||||
if isinstance(tAnchor, str) and tAnchor.startswith("#"):
|
|
||||||
logger.debug("Moving to anchor '%s'", tAnchor)
|
|
||||||
self.setSource(QUrl(tAnchor))
|
|
||||||
return
|
|
||||||
|
|
||||||
def clearNavHistory(self) -> None:
|
def clearNavHistory(self) -> None:
|
||||||
"""Clear the navigation history."""
|
"""Clear the navigation history."""
|
||||||
self.docHistory.clear()
|
self.docHistory.clear()
|
||||||
@@ -345,6 +342,14 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
self.updateDocMargins()
|
self.updateDocMargins()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def navigateTo(self, anchor: str) -> None:
|
||||||
|
"""Go to a specific #link in the document."""
|
||||||
|
if isinstance(anchor, str) and anchor.startswith("#"):
|
||||||
|
logger.debug("Moving to anchor '%s'", anchor)
|
||||||
|
self.setSource(QUrl(anchor))
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Private Slots
|
# Private Slots
|
||||||
##
|
##
|
||||||
@@ -630,9 +635,12 @@ class GuiDocViewHeader(QWidget):
|
|||||||
|
|
||||||
# Internal Variables
|
# Internal Variables
|
||||||
self._docHandle = None
|
self._docHandle = None
|
||||||
|
self._docOutline: dict[int, str] = {}
|
||||||
|
|
||||||
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
||||||
hSp = CONFIG.pxInt(6)
|
hSp = CONFIG.pxInt(6)
|
||||||
|
mPx = CONFIG.pxInt(8)
|
||||||
|
iconSize = QSize(fPx, fPx)
|
||||||
|
|
||||||
# Main Widget Settings
|
# Main Widget Settings
|
||||||
self.setAutoFillBackground(True)
|
self.setAutoFillBackground(True)
|
||||||
@@ -651,10 +659,13 @@ class GuiDocViewHeader(QWidget):
|
|||||||
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
||||||
self.docTitle.setFont(lblFont)
|
self.docTitle.setFont(lblFont)
|
||||||
|
|
||||||
|
# Other Widgets
|
||||||
|
self.outlineMenu = QMenu(self)
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
self.backButton = QToolButton(self)
|
self.backButton = QToolButton(self)
|
||||||
self.backButton.setContentsMargins(0, 0, 0, 0)
|
self.backButton.setContentsMargins(0, 0, 0, 0)
|
||||||
self.backButton.setIconSize(QSize(fPx, fPx))
|
self.backButton.setIconSize(iconSize)
|
||||||
self.backButton.setFixedSize(fPx, fPx)
|
self.backButton.setFixedSize(fPx, fPx)
|
||||||
self.backButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
self.backButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
||||||
self.backButton.setVisible(False)
|
self.backButton.setVisible(False)
|
||||||
@@ -663,16 +674,26 @@ class GuiDocViewHeader(QWidget):
|
|||||||
|
|
||||||
self.forwardButton = QToolButton(self)
|
self.forwardButton = QToolButton(self)
|
||||||
self.forwardButton.setContentsMargins(0, 0, 0, 0)
|
self.forwardButton.setContentsMargins(0, 0, 0, 0)
|
||||||
self.forwardButton.setIconSize(QSize(fPx, fPx))
|
self.forwardButton.setIconSize(iconSize)
|
||||||
self.forwardButton.setFixedSize(fPx, fPx)
|
self.forwardButton.setFixedSize(fPx, fPx)
|
||||||
self.forwardButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
self.forwardButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
||||||
self.forwardButton.setVisible(False)
|
self.forwardButton.setVisible(False)
|
||||||
self.forwardButton.setToolTip(self.tr("Go Forward"))
|
self.forwardButton.setToolTip(self.tr("Go Forward"))
|
||||||
self.forwardButton.clicked.connect(self.docViewer.navForward)
|
self.forwardButton.clicked.connect(self.docViewer.navForward)
|
||||||
|
|
||||||
|
self.outlineButton = QToolButton(self)
|
||||||
|
self.outlineButton.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.outlineButton.setIconSize(iconSize)
|
||||||
|
self.outlineButton.setFixedSize(fPx, fPx)
|
||||||
|
self.outlineButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
||||||
|
self.outlineButton.setVisible(False)
|
||||||
|
self.outlineButton.setToolTip(self.tr("Outline"))
|
||||||
|
self.outlineButton.setMenu(self.outlineMenu)
|
||||||
|
self.outlineButton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
||||||
|
|
||||||
self.refreshButton = QToolButton(self)
|
self.refreshButton = QToolButton(self)
|
||||||
self.refreshButton.setContentsMargins(0, 0, 0, 0)
|
self.refreshButton.setContentsMargins(0, 0, 0, 0)
|
||||||
self.refreshButton.setIconSize(QSize(fPx, fPx))
|
self.refreshButton.setIconSize(iconSize)
|
||||||
self.refreshButton.setFixedSize(fPx, fPx)
|
self.refreshButton.setFixedSize(fPx, fPx)
|
||||||
self.refreshButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
self.refreshButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
||||||
self.refreshButton.setVisible(False)
|
self.refreshButton.setVisible(False)
|
||||||
@@ -681,7 +702,7 @@ class GuiDocViewHeader(QWidget):
|
|||||||
|
|
||||||
self.closeButton = QToolButton(self)
|
self.closeButton = QToolButton(self)
|
||||||
self.closeButton.setContentsMargins(0, 0, 0, 0)
|
self.closeButton.setContentsMargins(0, 0, 0, 0)
|
||||||
self.closeButton.setIconSize(QSize(fPx, fPx))
|
self.closeButton.setIconSize(iconSize)
|
||||||
self.closeButton.setFixedSize(fPx, fPx)
|
self.closeButton.setFixedSize(fPx, fPx)
|
||||||
self.closeButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
self.closeButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
||||||
self.closeButton.setVisible(False)
|
self.closeButton.setVisible(False)
|
||||||
@@ -693,17 +714,19 @@ class GuiDocViewHeader(QWidget):
|
|||||||
self.outerBox.setSpacing(hSp)
|
self.outerBox.setSpacing(hSp)
|
||||||
self.outerBox.addWidget(self.backButton, 0)
|
self.outerBox.addWidget(self.backButton, 0)
|
||||||
self.outerBox.addWidget(self.forwardButton, 0)
|
self.outerBox.addWidget(self.forwardButton, 0)
|
||||||
|
self.outerBox.addWidget(self.outlineButton, 0)
|
||||||
self.outerBox.addWidget(self.docTitle, 1)
|
self.outerBox.addWidget(self.docTitle, 1)
|
||||||
|
self.outerBox.addSpacing(fPx + hSp)
|
||||||
self.outerBox.addWidget(self.refreshButton, 0)
|
self.outerBox.addWidget(self.refreshButton, 0)
|
||||||
self.outerBox.addWidget(self.closeButton, 0)
|
self.outerBox.addWidget(self.closeButton, 0)
|
||||||
|
|
||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
|
|
||||||
# Fix Margins and Size
|
# Fix Margins and Size
|
||||||
# This is needed for high DPI systems. See issue #499.
|
# This is needed for high DPI systems. See issue #499.
|
||||||
cM = CONFIG.pxInt(8)
|
|
||||||
self.setContentsMargins(0, 0, 0, 0)
|
self.setContentsMargins(0, 0, 0, 0)
|
||||||
self.outerBox.setContentsMargins(cM, cM, cM, cM)
|
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
|
||||||
self.setMinimumHeight(fPx + 2*cM)
|
self.setMinimumHeight(fPx + 2*mPx)
|
||||||
|
|
||||||
# Fix the Colours
|
# Fix the Colours
|
||||||
self.updateTheme()
|
self.updateTheme()
|
||||||
@@ -716,10 +739,23 @@ class GuiDocViewHeader(QWidget):
|
|||||||
# Methods
|
# Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
|
def setOutline(self, data: dict[int, str]) -> None:
|
||||||
|
"""Set the document outline dataset."""
|
||||||
|
if data != self._docOutline:
|
||||||
|
self.outlineMenu.clear()
|
||||||
|
for title, text in data.items():
|
||||||
|
action = self.outlineMenu.addAction(text)
|
||||||
|
action.triggered.connect(
|
||||||
|
lambda _, title=title: self.docViewer.navigateTo(f"#{title}")
|
||||||
|
)
|
||||||
|
self._docOutline = data
|
||||||
|
return
|
||||||
|
|
||||||
def updateTheme(self) -> None:
|
def updateTheme(self) -> None:
|
||||||
"""Update theme elements."""
|
"""Update theme elements."""
|
||||||
self.backButton.setIcon(SHARED.theme.getIcon("backward"))
|
self.backButton.setIcon(SHARED.theme.getIcon("backward"))
|
||||||
self.forwardButton.setIcon(SHARED.theme.getIcon("forward"))
|
self.forwardButton.setIcon(SHARED.theme.getIcon("forward"))
|
||||||
|
self.outlineButton.setIcon(SHARED.theme.getIcon("list"))
|
||||||
self.refreshButton.setIcon(SHARED.theme.getIcon("refresh"))
|
self.refreshButton.setIcon(SHARED.theme.getIcon("refresh"))
|
||||||
self.closeButton.setIcon(SHARED.theme.getIcon("close"))
|
self.closeButton.setIcon(SHARED.theme.getIcon("close"))
|
||||||
|
|
||||||
@@ -728,9 +764,11 @@ class GuiDocViewHeader(QWidget):
|
|||||||
"QToolButton {{border: none; background: transparent;}} "
|
"QToolButton {{border: none; background: transparent;}} "
|
||||||
"QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
|
"QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
|
||||||
).format(colText.red(), colText.green(), colText.blue())
|
).format(colText.red(), colText.green(), colText.blue())
|
||||||
|
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
|
||||||
|
|
||||||
self.backButton.setStyleSheet(buttonStyle)
|
self.backButton.setStyleSheet(buttonStyle)
|
||||||
self.forwardButton.setStyleSheet(buttonStyle)
|
self.forwardButton.setStyleSheet(buttonStyle)
|
||||||
|
self.outlineButton.setStyleSheet(buttonStyleMenu)
|
||||||
self.refreshButton.setStyleSheet(buttonStyle)
|
self.refreshButton.setStyleSheet(buttonStyle)
|
||||||
self.closeButton.setStyleSheet(buttonStyle)
|
self.closeButton.setStyleSheet(buttonStyle)
|
||||||
|
|
||||||
@@ -759,8 +797,11 @@ class GuiDocViewHeader(QWidget):
|
|||||||
self.docTitle.setText("")
|
self.docTitle.setText("")
|
||||||
self.backButton.setVisible(False)
|
self.backButton.setVisible(False)
|
||||||
self.forwardButton.setVisible(False)
|
self.forwardButton.setVisible(False)
|
||||||
|
self.outlineButton.setVisible(False)
|
||||||
self.closeButton.setVisible(False)
|
self.closeButton.setVisible(False)
|
||||||
self.refreshButton.setVisible(False)
|
self.refreshButton.setVisible(False)
|
||||||
|
self.outlineMenu.clear()
|
||||||
|
self._docOutline = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
if CONFIG.showFullPath:
|
if CONFIG.showFullPath:
|
||||||
@@ -772,6 +813,7 @@ class GuiDocViewHeader(QWidget):
|
|||||||
|
|
||||||
self.backButton.setVisible(True)
|
self.backButton.setVisible(True)
|
||||||
self.forwardButton.setVisible(True)
|
self.forwardButton.setVisible(True)
|
||||||
|
self.outlineButton.setVisible(True)
|
||||||
self.closeButton.setVisible(True)
|
self.closeButton.setVisible(True)
|
||||||
self.refreshButton.setVisible(True)
|
self.refreshButton.setVisible(True)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user