novelWriter 2.3.1__py3-none-any.whl → 2.4b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/METADATA +1 -1
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/RECORD +81 -70
- novelwriter/__init__.py +5 -5
- novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_dark/typ_arrow-down.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_arrow-right.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_light/icons.conf +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_light/typ_arrow-down.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_arrow-right.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +25 -23
- novelwriter/common.py +1 -1
- novelwriter/config.py +35 -12
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +60 -40
- novelwriter/core/coretools.py +98 -13
- novelwriter/core/docbuild.py +74 -7
- novelwriter/core/document.py +24 -3
- novelwriter/core/index.py +31 -112
- novelwriter/core/project.py +10 -15
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/status.py +4 -4
- novelwriter/core/storage.py +8 -2
- novelwriter/core/tohtml.py +22 -25
- novelwriter/core/tokenizer.py +416 -232
- novelwriter/core/tomd.py +17 -8
- novelwriter/core/toodt.py +65 -7
- novelwriter/core/tree.py +8 -8
- novelwriter/dialogs/docsplit.py +7 -8
- novelwriter/dialogs/preferences.py +3 -6
- novelwriter/enum.py +17 -14
- novelwriter/extensions/modified.py +20 -2
- novelwriter/extensions/versioninfo.py +1 -1
- novelwriter/gui/doceditor.py +257 -279
- novelwriter/gui/dochighlight.py +29 -25
- novelwriter/gui/docviewer.py +139 -148
- novelwriter/gui/docviewerpanel.py +4 -24
- novelwriter/gui/editordocument.py +12 -1
- novelwriter/gui/itemdetails.py +6 -6
- novelwriter/gui/mainmenu.py +37 -16
- novelwriter/gui/noveltree.py +11 -19
- novelwriter/gui/outline.py +43 -20
- novelwriter/gui/projtree.py +35 -43
- novelwriter/gui/search.py +316 -0
- novelwriter/gui/sidebar.py +25 -30
- novelwriter/gui/theme.py +59 -6
- novelwriter/guimain.py +176 -173
- novelwriter/shared.py +26 -1
- novelwriter/text/__init__.py +3 -0
- novelwriter/text/counting.py +137 -0
- novelwriter/tools/manuscript.py +344 -55
- novelwriter/tools/manussettings.py +213 -71
- novelwriter/tools/welcome.py +1 -1
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/WHEEL +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/top_level.txt +0 -0
novelwriter/tools/manuscript.py
CHANGED
@@ -31,24 +31,27 @@ from typing import TYPE_CHECKING
|
|
31
31
|
from datetime import datetime
|
32
32
|
|
33
33
|
from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent
|
34
|
-
from PyQt5.QtCore import QSize, QTimer, Qt, pyqtSlot
|
34
|
+
from PyQt5.QtCore import QSize, QTimer, QUrl, Qt, pyqtSignal, pyqtSlot
|
35
35
|
from PyQt5.QtWidgets import (
|
36
|
-
QAbstractItemView, QDialog, QGridLayout, QHBoxLayout, QLabel,
|
37
|
-
QListWidgetItem, QPushButton,
|
38
|
-
|
36
|
+
QAbstractItemView, QDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel,
|
37
|
+
QListWidget, QListWidgetItem, QPushButton, QSizePolicy, QSplitter,
|
38
|
+
QStackedWidget, QTabWidget, QTextBrowser, QTreeWidget, QTreeWidgetItem,
|
39
|
+
QVBoxLayout, QWidget, qApp
|
39
40
|
)
|
40
41
|
from PyQt5.QtPrintSupport import QPrintPreviewDialog, QPrinter
|
41
42
|
|
42
43
|
from novelwriter import CONFIG, SHARED
|
43
|
-
from novelwriter.error import logException
|
44
44
|
from novelwriter.common import checkInt, fuzzyTime
|
45
|
-
from novelwriter.core.
|
45
|
+
from novelwriter.core.buildsettings import BuildCollection, BuildSettings
|
46
46
|
from novelwriter.core.docbuild import NWBuildDocument
|
47
|
+
from novelwriter.core.tohtml import ToHtml
|
47
48
|
from novelwriter.core.tokenizer import HeadingFormatter
|
48
|
-
from novelwriter.
|
49
|
+
from novelwriter.error import logException
|
50
|
+
from novelwriter.extensions.circularprogress import NProgressCircle
|
51
|
+
from novelwriter.extensions.modified import NIconToolButton
|
52
|
+
from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON
|
49
53
|
from novelwriter.tools.manusbuild import GuiManuscriptBuild
|
50
54
|
from novelwriter.tools.manussettings import GuiBuildSettings
|
51
|
-
from novelwriter.extensions.circularprogress import NProgressCircle
|
52
55
|
|
53
56
|
if TYPE_CHECKING: # pragma: no cover
|
54
57
|
from novelwriter.guimain import GuiMain
|
@@ -100,29 +103,22 @@ class GuiManuscript(QDialog):
|
|
100
103
|
qPalette.setBrush(QPalette.Window, qPalette.base())
|
101
104
|
self.setPalette(qPalette)
|
102
105
|
|
103
|
-
|
104
|
-
buttonStyle = (
|
105
|
-
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
|
106
|
-
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
107
|
-
).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
106
|
+
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
108
107
|
|
109
|
-
self.tbAdd =
|
108
|
+
self.tbAdd = NIconToolButton(self, iPx)
|
110
109
|
self.tbAdd.setIcon(SHARED.theme.getIcon("add"))
|
111
|
-
self.tbAdd.setIconSize(QSize(iPx, iPx))
|
112
110
|
self.tbAdd.setToolTip(self.tr("Add New Build"))
|
113
111
|
self.tbAdd.setStyleSheet(buttonStyle)
|
114
112
|
self.tbAdd.clicked.connect(self._createNewBuild)
|
115
113
|
|
116
|
-
self.tbDel =
|
114
|
+
self.tbDel = NIconToolButton(self, iPx)
|
117
115
|
self.tbDel.setIcon(SHARED.theme.getIcon("remove"))
|
118
|
-
self.tbDel.setIconSize(QSize(iPx, iPx))
|
119
116
|
self.tbDel.setToolTip(self.tr("Delete Selected Build"))
|
120
117
|
self.tbDel.setStyleSheet(buttonStyle)
|
121
118
|
self.tbDel.clicked.connect(self._deleteSelectedBuild)
|
122
119
|
|
123
|
-
self.tbEdit =
|
120
|
+
self.tbEdit = NIconToolButton(self, iPx)
|
124
121
|
self.tbEdit.setIcon(SHARED.theme.getIcon("edit"))
|
125
|
-
self.tbEdit.setIconSize(QSize(iPx, iPx))
|
126
122
|
self.tbEdit.setToolTip(self.tr("Edit Selected Build"))
|
127
123
|
self.tbEdit.setStyleSheet(buttonStyle)
|
128
124
|
self.tbEdit.clicked.connect(self._editSelectedBuild)
|
@@ -140,21 +136,31 @@ class GuiManuscript(QDialog):
|
|
140
136
|
# Builds
|
141
137
|
# ======
|
142
138
|
|
143
|
-
self.buildList = QListWidget()
|
139
|
+
self.buildList = QListWidget(self)
|
144
140
|
self.buildList.setIconSize(QSize(iPx, iPx))
|
145
141
|
self.buildList.doubleClicked.connect(self._editSelectedBuild)
|
146
142
|
self.buildList.currentItemChanged.connect(self._updateBuildDetails)
|
147
143
|
self.buildList.setSelectionMode(QAbstractItemView.SingleSelection)
|
148
144
|
self.buildList.setDragDropMode(QAbstractItemView.InternalMove)
|
149
145
|
|
146
|
+
# Details Tabs
|
147
|
+
# ============
|
148
|
+
|
150
149
|
self.buildDetails = _DetailsWidget(self)
|
151
150
|
self.buildDetails.setColumnWidth(
|
152
151
|
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "detailsWidth", 100)),
|
153
152
|
)
|
154
153
|
|
154
|
+
self.buildOutline = _OutlineWidget(self)
|
155
|
+
|
156
|
+
self.detailsTabs = QTabWidget(self)
|
157
|
+
self.detailsTabs.addTab(self.buildDetails, self.tr("Build"))
|
158
|
+
self.detailsTabs.addTab(self.buildOutline, self.tr("Outline"))
|
159
|
+
self.detailsTabs.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_FLAT_TABS))
|
160
|
+
|
155
161
|
self.buildSplit = QSplitter(Qt.Orientation.Vertical, self)
|
156
162
|
self.buildSplit.addWidget(self.buildList)
|
157
|
-
self.buildSplit.addWidget(self.
|
163
|
+
self.buildSplit.addWidget(self.detailsTabs)
|
158
164
|
self.buildSplit.setSizes([
|
159
165
|
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "listHeight", 50)),
|
160
166
|
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "detailsHeight", 50)),
|
@@ -185,6 +191,15 @@ class GuiManuscript(QDialog):
|
|
185
191
|
# ============
|
186
192
|
|
187
193
|
self.docPreview = _PreviewWidget(self)
|
194
|
+
self.docStats = _StatsWidget(self)
|
195
|
+
|
196
|
+
self.docBox = QVBoxLayout()
|
197
|
+
self.docBox.addWidget(self.docPreview, 1)
|
198
|
+
self.docBox.addWidget(self.docStats, 0)
|
199
|
+
self.docBox.setContentsMargins(0, 0, 0, 0)
|
200
|
+
|
201
|
+
self.docWdiget = QWidget(self)
|
202
|
+
self.docWdiget.setLayout(self.docBox)
|
188
203
|
|
189
204
|
self.controlBox = QVBoxLayout()
|
190
205
|
self.controlBox.addLayout(self.listToolBox, 0)
|
@@ -192,12 +207,12 @@ class GuiManuscript(QDialog):
|
|
192
207
|
self.controlBox.addLayout(self.processBox, 0)
|
193
208
|
self.controlBox.setContentsMargins(0, 0, 0, 0)
|
194
209
|
|
195
|
-
self.optsWidget = QWidget()
|
210
|
+
self.optsWidget = QWidget(self)
|
196
211
|
self.optsWidget.setLayout(self.controlBox)
|
197
212
|
|
198
213
|
self.mainSplit = QSplitter()
|
199
214
|
self.mainSplit.addWidget(self.optsWidget)
|
200
|
-
self.mainSplit.addWidget(self.
|
215
|
+
self.mainSplit.addWidget(self.docWdiget)
|
201
216
|
self.mainSplit.setCollapsible(0, False)
|
202
217
|
self.mainSplit.setCollapsible(1, False)
|
203
218
|
self.mainSplit.setStretchFactor(0, 0)
|
@@ -213,6 +228,9 @@ class GuiManuscript(QDialog):
|
|
213
228
|
self.setLayout(self.outerBox)
|
214
229
|
self.setSizeGripEnabled(True)
|
215
230
|
|
231
|
+
# Signals
|
232
|
+
self.buildOutline.outlineEntryClicked.connect(self.docPreview.navigateTo)
|
233
|
+
|
216
234
|
logger.debug("Ready: GuiManuscript")
|
217
235
|
|
218
236
|
return
|
@@ -245,7 +263,7 @@ class GuiManuscript(QDialog):
|
|
245
263
|
if isinstance(build, BuildSettings):
|
246
264
|
self._updatePreview(data, build)
|
247
265
|
except Exception:
|
248
|
-
logger.error("Failed to
|
266
|
+
logger.error("Failed to load build cache")
|
249
267
|
logException()
|
250
268
|
return
|
251
269
|
|
@@ -328,6 +346,7 @@ class GuiManuscript(QDialog):
|
|
328
346
|
return
|
329
347
|
|
330
348
|
docBuild = NWBuildDocument(SHARED.project, build)
|
349
|
+
docBuild.setPreviewMode(True)
|
331
350
|
docBuild.queueAll()
|
332
351
|
|
333
352
|
self.docPreview.beginNewBuild(len(docBuild))
|
@@ -340,6 +359,8 @@ class GuiManuscript(QDialog):
|
|
340
359
|
result = {
|
341
360
|
"uuid": build.buildID,
|
342
361
|
"time": int(time()),
|
362
|
+
"stats": buildObj.textStats,
|
363
|
+
"outline": buildObj.textOutline,
|
343
364
|
"styles": buildObj.getStyleSheet(),
|
344
365
|
"html": buildObj.fullHTML,
|
345
366
|
}
|
@@ -395,6 +416,8 @@ class GuiManuscript(QDialog):
|
|
395
416
|
self.docPreview.setJustify(
|
396
417
|
build.getBool("format.justifyText")
|
397
418
|
)
|
419
|
+
self.docStats.updateStats(data.get("stats", {}))
|
420
|
+
self.buildOutline.updateOutline(data.get("outline", {}))
|
398
421
|
return
|
399
422
|
|
400
423
|
def _getSelectedBuild(self) -> BuildSettings | None:
|
@@ -504,7 +527,7 @@ class _DetailsWidget(QWidget):
|
|
504
527
|
self.listView = QTreeWidget(self)
|
505
528
|
self.listView.setHeaderLabels([self.tr("Setting"), self.tr("Value")])
|
506
529
|
self.listView.setIndentation(SHARED.theme.baseIconSize)
|
507
|
-
self.listView.setSelectionMode(QAbstractItemView.NoSelection)
|
530
|
+
self.listView.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
508
531
|
|
509
532
|
# Assemble
|
510
533
|
self.outerBox = QVBoxLayout()
|
@@ -592,24 +615,26 @@ class _DetailsWidget(QWidget):
|
|
592
615
|
hFmt.resetScene()
|
593
616
|
hFmt.incScene()
|
594
617
|
title = self.tr("Title")
|
618
|
+
hidden = self.tr("Hidden")
|
595
619
|
|
596
620
|
item = QTreeWidgetItem()
|
597
621
|
item.setText(0, build.getLabel("headings"))
|
598
622
|
item.setText(1, "")
|
599
623
|
self.listView.addTopLevelItem(item)
|
600
|
-
|
601
|
-
"headings.fmtTitle", "headings.
|
602
|
-
"headings.
|
603
|
-
|
604
|
-
|
624
|
+
for hFormat, hHide in [
|
625
|
+
("headings.fmtTitle", "headings.hideTitle"),
|
626
|
+
("headings.fmtChapter", "headings.hideChapter"),
|
627
|
+
("headings.fmtUnnumbered", "headings.hideUnnumbered"),
|
628
|
+
("headings.fmtScene", "headings.hideScene"),
|
629
|
+
("headings.fmtHardScene", "headings.hideHardScene"),
|
630
|
+
("headings.fmtSection", "headings.hideSection"),
|
631
|
+
]:
|
605
632
|
sub = QTreeWidgetItem()
|
606
|
-
sub.setText(0, build.getLabel(
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
sub.setText(0, build.getLabel(key))
|
612
|
-
sub.setIcon(1, on if build.getBool(key) else off)
|
633
|
+
sub.setText(0, build.getLabel(hFormat))
|
634
|
+
if build.getBool(hHide):
|
635
|
+
sub.setText(1, f"[{hidden}]")
|
636
|
+
else:
|
637
|
+
sub.setText(1, hFmt.apply(build.getStr(hFormat), title, 0))
|
613
638
|
item.addChild(sub)
|
614
639
|
|
615
640
|
# Text Content
|
@@ -617,11 +642,10 @@ class _DetailsWidget(QWidget):
|
|
617
642
|
item.setText(0, build.getLabel("text.grpContent"))
|
618
643
|
item.setText(1, "")
|
619
644
|
self.listView.addTopLevelItem(item)
|
620
|
-
|
645
|
+
for key in [
|
621
646
|
"text.includeSynopsis", "text.includeComments",
|
622
647
|
"text.includeKeywords", "text.includeBodyText",
|
623
|
-
]
|
624
|
-
for key in entries:
|
648
|
+
]:
|
625
649
|
sub = QTreeWidgetItem()
|
626
650
|
sub.setText(0, build.getLabel(key))
|
627
651
|
sub.setIcon(1, on if build.getBool(key) else off)
|
@@ -635,6 +659,85 @@ class _DetailsWidget(QWidget):
|
|
635
659
|
# END Class _DetailsWidget
|
636
660
|
|
637
661
|
|
662
|
+
class _OutlineWidget(QWidget):
|
663
|
+
|
664
|
+
D_LINE = Qt.ItemDataRole.UserRole
|
665
|
+
|
666
|
+
outlineEntryClicked = pyqtSignal(str)
|
667
|
+
|
668
|
+
def __init__(self, parent: QWidget) -> None:
|
669
|
+
super().__init__(parent=parent)
|
670
|
+
|
671
|
+
self._outline = {}
|
672
|
+
|
673
|
+
# Tree Widget
|
674
|
+
self.listView = QTreeWidget(self)
|
675
|
+
self.listView.setHeaderHidden(True)
|
676
|
+
self.listView.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
677
|
+
self.listView.itemClicked.connect(self._onItemClick)
|
678
|
+
|
679
|
+
# Assemble
|
680
|
+
self.outerBox = QVBoxLayout()
|
681
|
+
self.outerBox.addWidget(self.listView)
|
682
|
+
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
683
|
+
self.setLayout(self.outerBox)
|
684
|
+
|
685
|
+
return
|
686
|
+
|
687
|
+
def updateOutline(self, data: dict[str, str]) -> None:
|
688
|
+
"""Update the outline."""
|
689
|
+
if isinstance(data, dict) and data != self._outline:
|
690
|
+
self.listView.clear()
|
691
|
+
|
692
|
+
tFont = self.font()
|
693
|
+
tFont.setBold(True)
|
694
|
+
tBrush = self.palette().highlight()
|
695
|
+
|
696
|
+
hFont = self.font()
|
697
|
+
hFont.setBold(True)
|
698
|
+
hFont.setUnderline(True)
|
699
|
+
|
700
|
+
root = self.listView.invisibleRootItem()
|
701
|
+
parent = root
|
702
|
+
indent = False
|
703
|
+
for anchor, entry in data.items():
|
704
|
+
prefix, _, text = entry.partition("|")
|
705
|
+
if prefix in ("TT", "PT", "CH", "SC", "H1", "H2"):
|
706
|
+
item = QTreeWidgetItem([text])
|
707
|
+
item.setData(0, self.D_LINE, anchor)
|
708
|
+
if prefix == "TT":
|
709
|
+
item.setFont(0, tFont)
|
710
|
+
item.setForeground(0, tBrush)
|
711
|
+
root.addChild(item)
|
712
|
+
parent = root
|
713
|
+
elif prefix == "PT":
|
714
|
+
item.setFont(0, hFont)
|
715
|
+
root.addChild(item)
|
716
|
+
parent = root
|
717
|
+
elif prefix in ("CH", "H1"):
|
718
|
+
root.addChild(item)
|
719
|
+
parent = item
|
720
|
+
elif prefix in ("SC", "H2"):
|
721
|
+
parent.addChild(item)
|
722
|
+
indent = True
|
723
|
+
|
724
|
+
self.listView.setIndentation(SHARED.theme.baseIconSize if indent else CONFIG.pxInt(4))
|
725
|
+
self._outline = data
|
726
|
+
|
727
|
+
return
|
728
|
+
|
729
|
+
##
|
730
|
+
# Private Slots
|
731
|
+
##
|
732
|
+
|
733
|
+
def _onItemClick(self, item: QTreeWidgetItem) -> None:
|
734
|
+
"""Process tree item click."""
|
735
|
+
self.outlineEntryClicked.emit(str(item.data(0, self.D_LINE)))
|
736
|
+
return
|
737
|
+
|
738
|
+
# END Class _OutlineWidget
|
739
|
+
|
740
|
+
|
638
741
|
class _PreviewWidget(QTextBrowser):
|
639
742
|
|
640
743
|
def __init__(self, parent: QWidget) -> None:
|
@@ -674,7 +777,7 @@ class _PreviewWidget(QTextBrowser):
|
|
674
777
|
self.ageLabel.setFont(aFont)
|
675
778
|
self.ageLabel.setPalette(aPalette)
|
676
779
|
self.ageLabel.setAutoFillBackground(True)
|
677
|
-
self.ageLabel.setAlignment(Qt.AlignCenter)
|
780
|
+
self.ageLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
678
781
|
self.ageLabel.setFixedHeight(int(2.1*SHARED.theme.fontPixelSize))
|
679
782
|
|
680
783
|
# Progress
|
@@ -692,8 +795,8 @@ class _PreviewWidget(QTextBrowser):
|
|
692
795
|
self._updateBuildAge()
|
693
796
|
|
694
797
|
# Age Timer
|
695
|
-
self.ageTimer = QTimer()
|
696
|
-
self.ageTimer.setInterval(
|
798
|
+
self.ageTimer = QTimer(self)
|
799
|
+
self.ageTimer.setInterval(10000)
|
697
800
|
self.ageTimer.timeout.connect(self._updateBuildAge)
|
698
801
|
self.ageTimer.start()
|
699
802
|
|
@@ -713,9 +816,9 @@ class _PreviewWidget(QTextBrowser):
|
|
713
816
|
"""Enable/disable the justify text option."""
|
714
817
|
pOptions = self.document().defaultTextOption()
|
715
818
|
if state:
|
716
|
-
pOptions.setAlignment(Qt.AlignJustify)
|
819
|
+
pOptions.setAlignment(Qt.AlignmentFlag.AlignJustify)
|
717
820
|
else:
|
718
|
-
pOptions.setAlignment(Qt.AlignAbsolute)
|
821
|
+
pOptions.setAlignment(Qt.AlignmentFlag.AlignAbsolute)
|
719
822
|
self.document().setDefaultTextOption(pOptions)
|
720
823
|
return
|
721
824
|
|
@@ -751,23 +854,16 @@ class _PreviewWidget(QTextBrowser):
|
|
751
854
|
def setContent(self, data: dict) -> None:
|
752
855
|
"""Set the content of the preview widget."""
|
753
856
|
sPos = self.verticalScrollBar().value()
|
754
|
-
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
857
|
+
qApp.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
755
858
|
|
756
859
|
self.buildProgress.setCentreText(self.tr("Processing ..."))
|
757
860
|
qApp.processEvents()
|
758
861
|
|
759
|
-
styles = "\n".join(data.get("styles", [
|
760
|
-
"h1, h2 {color: rgb(66, 113, 174);}",
|
761
|
-
"h3, h4 {color: rgb(50, 50, 50);}",
|
762
|
-
"a {color: rgb(66, 113, 174);}",
|
763
|
-
".tags {color: rgb(245, 135, 31); font-weight: bold;}",
|
764
|
-
]))
|
862
|
+
styles = "\n".join(data.get("styles", []))
|
765
863
|
self.document().setDefaultStyleSheet(styles)
|
766
864
|
|
767
865
|
html = "".join(data.get("html", []))
|
768
866
|
html = html.replace("\t", "!!tab!!")
|
769
|
-
html = html.replace("<del>", "<span style='text-decoration: line-through;'>")
|
770
|
-
html = html.replace("</del>", "</span>")
|
771
867
|
self.setHtml(html)
|
772
868
|
qApp.processEvents()
|
773
869
|
while self.find("!!tab!!"):
|
@@ -812,6 +908,13 @@ class _PreviewWidget(QTextBrowser):
|
|
812
908
|
qApp.restoreOverrideCursor()
|
813
909
|
return
|
814
910
|
|
911
|
+
@pyqtSlot(str)
|
912
|
+
def navigateTo(self, anchor: str) -> None:
|
913
|
+
"""Go to a specific #link in the document."""
|
914
|
+
logger.debug("Moving to anchor '#%s'", anchor)
|
915
|
+
self.setSource(QUrl(f"#{anchor}"))
|
916
|
+
return
|
917
|
+
|
815
918
|
##
|
816
919
|
# Private Slots
|
817
920
|
##
|
@@ -821,7 +924,7 @@ class _PreviewWidget(QTextBrowser):
|
|
821
924
|
"""Update the build time and the fuzzy age."""
|
822
925
|
if self._docTime > 0:
|
823
926
|
strBuildTime = "%s (%s)" % (
|
824
|
-
datetime.fromtimestamp(self._docTime)
|
927
|
+
CONFIG.localDateTime(datetime.fromtimestamp(self._docTime)),
|
825
928
|
fuzzyTime(int(time()) - self._docTime)
|
826
929
|
)
|
827
930
|
else:
|
@@ -859,3 +962,189 @@ class _PreviewWidget(QTextBrowser):
|
|
859
962
|
return
|
860
963
|
|
861
964
|
# END Class _PreviewWidget
|
965
|
+
|
966
|
+
|
967
|
+
class _StatsWidget(QWidget):
|
968
|
+
|
969
|
+
def __init__(self, parent: QWidget) -> None:
|
970
|
+
super().__init__(parent=parent)
|
971
|
+
|
972
|
+
font = self.font()
|
973
|
+
font.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
974
|
+
self.setFont(font)
|
975
|
+
|
976
|
+
self.minWidget = QWidget(self)
|
977
|
+
self.maxWidget = QWidget(self)
|
978
|
+
|
979
|
+
iPx = int(0.6*SHARED.theme.baseIconSize)
|
980
|
+
toggleIcon = SHARED.theme.getToggleIcon("unfold", (iPx, iPx))
|
981
|
+
|
982
|
+
self.toggleButton = NIconToolButton(self, iPx)
|
983
|
+
self.toggleButton.setCheckable(True)
|
984
|
+
self.toggleButton.setIcon(toggleIcon)
|
985
|
+
self.toggleButton.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON))
|
986
|
+
self.toggleButton.toggled.connect(self._toggleView)
|
987
|
+
|
988
|
+
self._buildMinimal()
|
989
|
+
self._buildMaximal()
|
990
|
+
|
991
|
+
self.mainStack = QStackedWidget(self)
|
992
|
+
self.mainStack.addWidget(self.minWidget)
|
993
|
+
self.mainStack.addWidget(self.maxWidget)
|
994
|
+
|
995
|
+
self.outerBox = QHBoxLayout()
|
996
|
+
self.outerBox.addWidget(self.toggleButton, 0, Qt.AlignmentFlag.AlignTop)
|
997
|
+
self.outerBox.addWidget(self.mainStack, 1, Qt.AlignmentFlag.AlignTop)
|
998
|
+
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
999
|
+
|
1000
|
+
self.setLayout(self.outerBox)
|
1001
|
+
|
1002
|
+
self._toggleView(False)
|
1003
|
+
|
1004
|
+
return
|
1005
|
+
|
1006
|
+
def updateStats(self, data: dict[str, int]) -> None:
|
1007
|
+
"""Update the stats values from a Tokenizer stats dict."""
|
1008
|
+
# Minimal
|
1009
|
+
self.minWordCount.setText("{0:n}".format(data.get("allWords", 0)))
|
1010
|
+
self.minCharCount.setText("{0:n}".format(data.get("allChars", 0)))
|
1011
|
+
|
1012
|
+
# Maximal
|
1013
|
+
self.maxTotalWords.setText("{0:n}".format(data.get("allWords", 0)))
|
1014
|
+
self.maxHeaderWords.setText("{0:n}".format(data.get("titleWords", 0)))
|
1015
|
+
self.maxTextWords.setText("{0:n}".format(data.get("textWords", 0)))
|
1016
|
+
self.maxTitleCount.setText("{0:n}".format(data.get("titleCount", 0)))
|
1017
|
+
self.maxParCount.setText("{0:n}".format(data.get("paragraphCount", 0)))
|
1018
|
+
|
1019
|
+
self.maxTotalChars.setText("{0:n}".format(data.get("allChars", 0)))
|
1020
|
+
self.maxHeaderChars.setText("{0:n}".format(data.get("titleChars", 0)))
|
1021
|
+
self.maxTextChars.setText("{0:n}".format(data.get("textChars", 0)))
|
1022
|
+
|
1023
|
+
self.maxTotalWordChars.setText("{0:n}".format(data.get("allWordChars", 0)))
|
1024
|
+
self.maxHeaderWordChars.setText("{0:n}".format(data.get("titleWordChars", 0)))
|
1025
|
+
self.maxTextWordChars.setText("{0:n}".format(data.get("textWordChars", 0)))
|
1026
|
+
|
1027
|
+
return
|
1028
|
+
|
1029
|
+
##
|
1030
|
+
# Private Slots
|
1031
|
+
##
|
1032
|
+
|
1033
|
+
@pyqtSlot(bool)
|
1034
|
+
def _toggleView(self, state: bool) -> None:
|
1035
|
+
"""Toggle minimal or maximal view."""
|
1036
|
+
ignored = QSizePolicy.Policy.Ignored
|
1037
|
+
expanded = QSizePolicy.Policy.Expanding
|
1038
|
+
if state:
|
1039
|
+
self.mainStack.setCurrentWidget(self.maxWidget)
|
1040
|
+
self.maxWidget.setSizePolicy(expanded, expanded)
|
1041
|
+
self.minWidget.setSizePolicy(ignored, ignored)
|
1042
|
+
else:
|
1043
|
+
self.mainStack.setCurrentWidget(self.minWidget)
|
1044
|
+
self.maxWidget.setSizePolicy(ignored, ignored)
|
1045
|
+
self.minWidget.setSizePolicy(expanded, expanded)
|
1046
|
+
self.maxWidget.adjustSize()
|
1047
|
+
self.minWidget.adjustSize()
|
1048
|
+
self.mainStack.adjustSize()
|
1049
|
+
self.adjustSize()
|
1050
|
+
return
|
1051
|
+
|
1052
|
+
##
|
1053
|
+
# Internal Functions
|
1054
|
+
##
|
1055
|
+
|
1056
|
+
def _buildMinimal(self) -> None:
|
1057
|
+
"""Build the minimal stats page."""
|
1058
|
+
mPx = CONFIG.pxInt(8)
|
1059
|
+
|
1060
|
+
self.lblWordCount = QLabel(self.tr("Words"))
|
1061
|
+
self.minWordCount = QLabel(self)
|
1062
|
+
|
1063
|
+
self.lblCharCount = QLabel(self.tr("Characters"))
|
1064
|
+
self.minCharCount = QLabel(self)
|
1065
|
+
|
1066
|
+
# Assemble
|
1067
|
+
self.minLayout = QHBoxLayout()
|
1068
|
+
self.minLayout.addWidget(self.lblWordCount)
|
1069
|
+
self.minLayout.addWidget(self.minWordCount)
|
1070
|
+
self.minLayout.addSpacing(mPx)
|
1071
|
+
self.minLayout.addWidget(self.lblCharCount)
|
1072
|
+
self.minLayout.addWidget(self.minCharCount)
|
1073
|
+
self.minLayout.addStretch(1)
|
1074
|
+
self.minLayout.setSpacing(mPx)
|
1075
|
+
self.minLayout.setContentsMargins(0, 0, 0, 0)
|
1076
|
+
|
1077
|
+
self.minWidget.setLayout(self.minLayout)
|
1078
|
+
|
1079
|
+
return
|
1080
|
+
|
1081
|
+
def _buildMaximal(self) -> None:
|
1082
|
+
"""Build the maximal stats page."""
|
1083
|
+
hPx = CONFIG.pxInt(12)
|
1084
|
+
vPx = CONFIG.pxInt(4)
|
1085
|
+
|
1086
|
+
alignRight = Qt.AlignmentFlag.AlignRight
|
1087
|
+
|
1088
|
+
# Left Column
|
1089
|
+
self.maxTotalWords = QLabel(self)
|
1090
|
+
self.maxHeaderWords = QLabel(self)
|
1091
|
+
self.maxTextWords = QLabel(self)
|
1092
|
+
self.maxTitleCount = QLabel(self)
|
1093
|
+
self.maxParCount = QLabel(self)
|
1094
|
+
|
1095
|
+
self.maxTotalWords.setAlignment(alignRight)
|
1096
|
+
self.maxHeaderWords.setAlignment(alignRight)
|
1097
|
+
self.maxTextWords.setAlignment(alignRight)
|
1098
|
+
self.maxTitleCount.setAlignment(alignRight)
|
1099
|
+
self.maxParCount.setAlignment(alignRight)
|
1100
|
+
|
1101
|
+
self.leftForm = QFormLayout()
|
1102
|
+
self.leftForm.addRow(self.tr("Words"), self.maxTotalWords)
|
1103
|
+
self.leftForm.addRow(self.tr("Heading Words"), self.maxHeaderWords)
|
1104
|
+
self.leftForm.addRow(self.tr("Body Text Words"), self.maxTextWords)
|
1105
|
+
self.leftForm.addRow("", QLabel(self))
|
1106
|
+
self.leftForm.addRow(self.tr("Headings"), self.maxTitleCount)
|
1107
|
+
self.leftForm.addRow(self.tr("Paragraphs"), self.maxParCount)
|
1108
|
+
self.leftForm.setHorizontalSpacing(hPx)
|
1109
|
+
self.leftForm.setVerticalSpacing(vPx)
|
1110
|
+
|
1111
|
+
# Right Column
|
1112
|
+
self.maxTotalChars = QLabel(self)
|
1113
|
+
self.maxHeaderChars = QLabel(self)
|
1114
|
+
self.maxTextChars = QLabel(self)
|
1115
|
+
|
1116
|
+
self.maxTotalWordChars = QLabel(self)
|
1117
|
+
self.maxHeaderWordChars = QLabel(self)
|
1118
|
+
self.maxTextWordChars = QLabel(self)
|
1119
|
+
|
1120
|
+
self.maxTotalChars.setAlignment(alignRight)
|
1121
|
+
self.maxHeaderChars.setAlignment(alignRight)
|
1122
|
+
self.maxTextChars.setAlignment(alignRight)
|
1123
|
+
|
1124
|
+
self.maxTotalWordChars.setAlignment(alignRight)
|
1125
|
+
self.maxHeaderWordChars.setAlignment(alignRight)
|
1126
|
+
self.maxTextWordChars.setAlignment(alignRight)
|
1127
|
+
|
1128
|
+
self.rightForm = QFormLayout()
|
1129
|
+
self.rightForm.addRow(self.tr("Characters"), self.maxTotalChars)
|
1130
|
+
self.rightForm.addRow(self.tr("Heading Characters"), self.maxHeaderChars)
|
1131
|
+
self.rightForm.addRow(self.tr("Body Text Characters"), self.maxTextChars)
|
1132
|
+
self.rightForm.addRow(self.tr("Characters, No Spaces"), self.maxTotalWordChars)
|
1133
|
+
self.rightForm.addRow(self.tr("Heading Characters, No Spaces"), self.maxHeaderWordChars)
|
1134
|
+
self.rightForm.addRow(self.tr("Body Text Characters, No Spaces"), self.maxTextWordChars)
|
1135
|
+
self.rightForm.setHorizontalSpacing(hPx)
|
1136
|
+
self.rightForm.setVerticalSpacing(vPx)
|
1137
|
+
|
1138
|
+
# Assemble
|
1139
|
+
self.maxLayout = QHBoxLayout()
|
1140
|
+
self.maxLayout.addLayout(self.leftForm)
|
1141
|
+
self.maxLayout.addLayout(self.rightForm)
|
1142
|
+
self.maxLayout.addStretch(1)
|
1143
|
+
self.maxLayout.setSpacing(CONFIG.pxInt(32))
|
1144
|
+
self.maxLayout.setContentsMargins(0, 0, 0, 0)
|
1145
|
+
|
1146
|
+
self.maxWidget.setLayout(self.maxLayout)
|
1147
|
+
|
1148
|
+
return
|
1149
|
+
|
1150
|
+
# END Class _StatsWidget
|