Add outline table to Manuscript tool

This commit is contained in:
Veronica Berglyd Olsen
2024-03-20 20:43:31 +01:00
parent d42074f8ea
commit 46c8bedd66
+104 -9
View File
@@ -31,7 +31,7 @@ from typing import TYPE_CHECKING
from datetime import datetime from datetime import datetime
from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent
from PyQt5.QtCore import QSize, QTimer, Qt, pyqtSlot from PyQt5.QtCore import QSize, QTimer, QUrl, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel, QAbstractItemView, QDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel,
QListWidget, QListWidgetItem, QPushButton, QSizePolicy, QSplitter, QListWidget, QListWidgetItem, QPushButton, QSizePolicy, QSplitter,
@@ -58,6 +58,14 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
OUTLINE_MAP = {
"TT": 0,
"H1": 1,
"H2": 2,
"H3": 3,
"H4": 4,
}
class GuiManuscript(QDialog): class GuiManuscript(QDialog):
"""GUI Tools: Manuscript Tool """GUI Tools: Manuscript Tool
@@ -151,8 +159,11 @@ class GuiManuscript(QDialog):
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "detailsWidth", 100)), CONFIG.pxInt(pOptions.getInt("GuiManuscript", "detailsWidth", 100)),
) )
self.buildOutline = _OutlineWidget(self)
self.detailsTabs = QTabWidget(self) self.detailsTabs = QTabWidget(self)
self.detailsTabs.addTab(self.buildDetails, self.tr("Build")) self.detailsTabs.addTab(self.buildDetails, self.tr("Build"))
self.detailsTabs.addTab(self.buildOutline, self.tr("Outline"))
self.detailsTabs.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_FLAT_TABS)) self.detailsTabs.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_FLAT_TABS))
self.buildSplit = QSplitter(Qt.Orientation.Vertical, self) self.buildSplit = QSplitter(Qt.Orientation.Vertical, self)
@@ -225,6 +236,9 @@ class GuiManuscript(QDialog):
self.setLayout(self.outerBox) self.setLayout(self.outerBox)
self.setSizeGripEnabled(True) self.setSizeGripEnabled(True)
# Signals
self.buildOutline.outlineEntryClicked.connect(self.docPreview.navigateTo)
logger.debug("Ready: GuiManuscript") logger.debug("Ready: GuiManuscript")
return return
@@ -354,6 +368,7 @@ class GuiManuscript(QDialog):
"uuid": build.buildID, "uuid": build.buildID,
"time": int(time()), "time": int(time()),
"stats": buildObj.textStats, "stats": buildObj.textStats,
"outline": buildObj.navigationMap,
"styles": buildObj.getStyleSheet(), "styles": buildObj.getStyleSheet(),
"html": buildObj.fullHTML, "html": buildObj.fullHTML,
} }
@@ -410,6 +425,7 @@ class GuiManuscript(QDialog):
build.getBool("format.justifyText") build.getBool("format.justifyText")
) )
self.docStats.updateStats(data.get("stats", {})) self.docStats.updateStats(data.get("stats", {}))
self.buildOutline.updateOutline(data.get("outline", {}))
return return
def _getSelectedBuild(self) -> BuildSettings | None: def _getSelectedBuild(self) -> BuildSettings | None:
@@ -519,7 +535,7 @@ class _DetailsWidget(QWidget):
self.listView = QTreeWidget(self) self.listView = QTreeWidget(self)
self.listView.setHeaderLabels([self.tr("Setting"), self.tr("Value")]) self.listView.setHeaderLabels([self.tr("Setting"), self.tr("Value")])
self.listView.setIndentation(SHARED.theme.baseIconSize) self.listView.setIndentation(SHARED.theme.baseIconSize)
self.listView.setSelectionMode(QAbstractItemView.NoSelection) self.listView.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
# Assemble # Assemble
self.outerBox = QVBoxLayout() self.outerBox = QVBoxLayout()
@@ -651,6 +667,80 @@ class _DetailsWidget(QWidget):
# END Class _DetailsWidget # END Class _DetailsWidget
class _OutlineWidget(QWidget):
D_LINE = Qt.ItemDataRole.UserRole
outlineEntryClicked = pyqtSignal(str)
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
self._outline = {}
# Tree Widget
self.listView = QTreeWidget(self)
self.listView.setHeaderHidden(True)
self.listView.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
self.listView.itemClicked.connect(self._onItemClick)
# Assemble
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(self.listView)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.outerBox)
return
def updateOutline(self, data: dict[str, str]) -> None:
"""Update the outline."""
if isinstance(data, dict) and data != self._outline:
self.listView.clear()
tFont = self.font()
tFont.setBold(True)
tBrush = self.palette().highlight()
hFont = self.font()
hFont.setBold(True)
hFont.setUnderline(True)
root = self.listView.invisibleRootItem()
parent = root
for anchor, text in data.items():
level = OUTLINE_MAP.get(text[:2], -1)
text = text[3:]
if 0 <= level < 4:
item = QTreeWidgetItem([text])
item.setData(0, self.D_LINE, anchor)
if level == 0:
item.setFont(0, tFont)
item.setForeground(0, tBrush)
elif level == 1:
item.setFont(0, hFont)
if level < 3:
root.addChild(item)
parent = item
elif parent:
parent.addChild(item)
self._outline = data
return
##
# Private Slots
##
def _onItemClick(self, item: QTreeWidgetItem) -> None:
"""Process tree item click."""
self.outlineEntryClicked.emit(str(item.data(0, self.D_LINE)))
return
# END Class _OutlineWidget
class _PreviewWidget(QTextBrowser): class _PreviewWidget(QTextBrowser):
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
@@ -690,7 +780,7 @@ class _PreviewWidget(QTextBrowser):
self.ageLabel.setFont(aFont) self.ageLabel.setFont(aFont)
self.ageLabel.setPalette(aPalette) self.ageLabel.setPalette(aPalette)
self.ageLabel.setAutoFillBackground(True) self.ageLabel.setAutoFillBackground(True)
self.ageLabel.setAlignment(Qt.AlignCenter) self.ageLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.ageLabel.setFixedHeight(int(2.1*SHARED.theme.fontPixelSize)) self.ageLabel.setFixedHeight(int(2.1*SHARED.theme.fontPixelSize))
# Progress # Progress
@@ -708,7 +798,7 @@ class _PreviewWidget(QTextBrowser):
self._updateBuildAge() self._updateBuildAge()
# Age Timer # Age Timer
self.ageTimer = QTimer() self.ageTimer = QTimer(self)
self.ageTimer.setInterval(10) self.ageTimer.setInterval(10)
self.ageTimer.timeout.connect(self._updateBuildAge) self.ageTimer.timeout.connect(self._updateBuildAge)
self.ageTimer.start() self.ageTimer.start()
@@ -729,9 +819,9 @@ class _PreviewWidget(QTextBrowser):
"""Enable/disable the justify text option.""" """Enable/disable the justify text option."""
pOptions = self.document().defaultTextOption() pOptions = self.document().defaultTextOption()
if state: if state:
pOptions.setAlignment(Qt.AlignJustify) pOptions.setAlignment(Qt.AlignmentFlag.AlignJustify)
else: else:
pOptions.setAlignment(Qt.AlignAbsolute) pOptions.setAlignment(Qt.AlignmentFlag.AlignAbsolute)
self.document().setDefaultTextOption(pOptions) self.document().setDefaultTextOption(pOptions)
return return
@@ -767,7 +857,7 @@ class _PreviewWidget(QTextBrowser):
def setContent(self, data: dict) -> None: def setContent(self, data: dict) -> None:
"""Set the content of the preview widget.""" """Set the content of the preview widget."""
sPos = self.verticalScrollBar().value() sPos = self.verticalScrollBar().value()
qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) qApp.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
self.buildProgress.setCentreText(self.tr("Processing ...")) self.buildProgress.setCentreText(self.tr("Processing ..."))
qApp.processEvents() qApp.processEvents()
@@ -777,8 +867,6 @@ class _PreviewWidget(QTextBrowser):
html = "".join(data.get("html", [])) html = "".join(data.get("html", []))
html = html.replace("\t", "!!tab!!") html = html.replace("\t", "!!tab!!")
html = html.replace("<del>", "<span style='text-decoration: line-through;'>")
html = html.replace("</del>", "</span>")
self.setHtml(html) self.setHtml(html)
qApp.processEvents() qApp.processEvents()
while self.find("!!tab!!"): while self.find("!!tab!!"):
@@ -823,6 +911,13 @@ class _PreviewWidget(QTextBrowser):
qApp.restoreOverrideCursor() qApp.restoreOverrideCursor()
return return
@pyqtSlot(str)
def navigateTo(self, anchor: str) -> None:
"""Go to a specific #link in the document."""
logger.debug("Moving to anchor '#%s'", anchor)
self.setSource(QUrl(f"#{anchor}"))
return
## ##
# Private Slots # Private Slots
## ##