Add outline table to Manuscript tool
This commit is contained in:
@@ -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
|
||||||
##
|
##
|
||||||
|
|||||||
Reference in New Issue
Block a user