novelWriter 2.3.1__py3-none-any.whl → 2.4rc1__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.4rc1.dist-info}/METADATA +5 -6
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/RECORD +102 -95
- novelwriter/__init__.py +7 -7
- novelwriter/assets/icons/none.svg +4 -0
- 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_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_dark/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
- 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_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/icons/typicons_light/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
- 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 +7 -2
- novelwriter/config.py +43 -16
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +60 -40
- novelwriter/core/coretools.py +97 -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 +6 -5
- 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 +385 -350
- novelwriter/core/tree.py +8 -8
- novelwriter/dialogs/about.py +9 -11
- novelwriter/dialogs/docmerge.py +17 -14
- novelwriter/dialogs/docsplit.py +20 -19
- novelwriter/dialogs/editlabel.py +5 -4
- novelwriter/dialogs/preferences.py +31 -39
- novelwriter/dialogs/projectsettings.py +29 -26
- novelwriter/dialogs/quotes.py +10 -9
- novelwriter/dialogs/wordlist.py +15 -12
- novelwriter/enum.py +17 -14
- novelwriter/error.py +13 -11
- novelwriter/extensions/circularprogress.py +12 -8
- novelwriter/extensions/configlayout.py +1 -3
- novelwriter/extensions/modified.py +51 -2
- novelwriter/extensions/pagedsidebar.py +16 -14
- novelwriter/extensions/simpleprogress.py +3 -1
- novelwriter/extensions/statusled.py +3 -1
- novelwriter/extensions/switch.py +10 -9
- novelwriter/extensions/switchbox.py +14 -13
- novelwriter/extensions/versioninfo.py +1 -1
- novelwriter/gui/doceditor.py +413 -478
- novelwriter/gui/dochighlight.py +33 -29
- novelwriter/gui/docviewer.py +162 -175
- novelwriter/gui/docviewerpanel.py +20 -37
- novelwriter/gui/editordocument.py +15 -4
- novelwriter/gui/itemdetails.py +51 -54
- novelwriter/gui/mainmenu.py +37 -16
- novelwriter/gui/noveltree.py +30 -36
- novelwriter/gui/outline.py +114 -92
- novelwriter/gui/projtree.py +60 -66
- novelwriter/gui/search.py +362 -0
- novelwriter/gui/sidebar.py +36 -45
- novelwriter/gui/statusbar.py +14 -14
- novelwriter/gui/theme.py +93 -28
- novelwriter/guimain.py +207 -200
- novelwriter/shared.py +31 -6
- novelwriter/text/counting.py +137 -0
- novelwriter/tools/dictionaries.py +13 -12
- novelwriter/tools/lipsum.py +20 -17
- novelwriter/tools/manusbuild.py +35 -27
- novelwriter/tools/manuscript.py +374 -90
- novelwriter/tools/manussettings.py +261 -124
- novelwriter/tools/noveldetails.py +20 -18
- novelwriter/tools/welcome.py +48 -44
- novelwriter/tools/writingstats.py +61 -55
- novelwriter/types.py +90 -0
- novelwriter/core/__init__.py +0 -3
- novelwriter/dialogs/__init__.py +0 -3
- novelwriter/extensions/__init__.py +0 -3
- novelwriter/gui/__init__.py +0 -3
- novelwriter/tools/__init__.py +0 -3
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/WHEEL +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/top_level.txt +0 -0
novelwriter/tools/manuscript.py
CHANGED
@@ -26,29 +26,36 @@ from __future__ import annotations
|
|
26
26
|
import json
|
27
27
|
import logging
|
28
28
|
|
29
|
+
from datetime import datetime
|
29
30
|
from time import time
|
30
31
|
from typing import TYPE_CHECKING
|
31
|
-
from datetime import datetime
|
32
32
|
|
33
|
+
from PyQt5.QtCore import QTimer, QUrl, Qt, pyqtSignal, pyqtSlot
|
33
34
|
from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent
|
34
|
-
from PyQt5.
|
35
|
+
from PyQt5.QtPrintSupport import QPrintPreviewDialog, QPrinter
|
35
36
|
from PyQt5.QtWidgets import (
|
36
|
-
QAbstractItemView,
|
37
|
-
|
38
|
-
|
37
|
+
QAbstractItemView, QApplication, QDialog, QFormLayout, QGridLayout,
|
38
|
+
QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPushButton,
|
39
|
+
QSizePolicy, QSplitter, QStackedWidget, QTabWidget, QTextBrowser,
|
40
|
+
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
|
39
41
|
)
|
40
|
-
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 NIconToggleButton, 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.
|
55
|
+
from novelwriter.types import (
|
56
|
+
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignRight, QtAlignTop,
|
57
|
+
QtUserRole
|
58
|
+
)
|
52
59
|
|
53
60
|
if TYPE_CHECKING: # pragma: no cover
|
54
61
|
from novelwriter.guimain import GuiMain
|
@@ -64,7 +71,7 @@ class GuiManuscript(QDialog):
|
|
64
71
|
a document directly to disk.
|
65
72
|
"""
|
66
73
|
|
67
|
-
D_KEY =
|
74
|
+
D_KEY = QtUserRole
|
68
75
|
|
69
76
|
def __init__(self, mainGui: GuiMain) -> None:
|
70
77
|
super().__init__(parent=mainGui)
|
@@ -83,7 +90,7 @@ class GuiManuscript(QDialog):
|
|
83
90
|
self.setMinimumWidth(CONFIG.pxInt(600))
|
84
91
|
self.setMinimumHeight(CONFIG.pxInt(500))
|
85
92
|
|
86
|
-
|
93
|
+
iSz = SHARED.theme.baseIconSize
|
87
94
|
wWin = CONFIG.pxInt(900)
|
88
95
|
hWin = CONFIG.pxInt(600)
|
89
96
|
|
@@ -97,37 +104,27 @@ class GuiManuscript(QDialog):
|
|
97
104
|
# ==============
|
98
105
|
|
99
106
|
qPalette = self.palette()
|
100
|
-
qPalette.setBrush(QPalette.Window, qPalette.base())
|
107
|
+
qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base())
|
101
108
|
self.setPalette(qPalette)
|
102
109
|
|
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())
|
110
|
+
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
108
111
|
|
109
|
-
self.tbAdd =
|
110
|
-
self.tbAdd.setIcon(SHARED.theme.getIcon("add"))
|
111
|
-
self.tbAdd.setIconSize(QSize(iPx, iPx))
|
112
|
+
self.tbAdd = NIconToolButton(self, iSz, "add")
|
112
113
|
self.tbAdd.setToolTip(self.tr("Add New Build"))
|
113
114
|
self.tbAdd.setStyleSheet(buttonStyle)
|
114
115
|
self.tbAdd.clicked.connect(self._createNewBuild)
|
115
116
|
|
116
|
-
self.tbDel =
|
117
|
-
self.tbDel.setIcon(SHARED.theme.getIcon("remove"))
|
118
|
-
self.tbDel.setIconSize(QSize(iPx, iPx))
|
117
|
+
self.tbDel = NIconToolButton(self, iSz, "remove")
|
119
118
|
self.tbDel.setToolTip(self.tr("Delete Selected Build"))
|
120
119
|
self.tbDel.setStyleSheet(buttonStyle)
|
121
120
|
self.tbDel.clicked.connect(self._deleteSelectedBuild)
|
122
121
|
|
123
|
-
self.tbEdit =
|
124
|
-
self.tbEdit.setIcon(SHARED.theme.getIcon("edit"))
|
125
|
-
self.tbEdit.setIconSize(QSize(iPx, iPx))
|
122
|
+
self.tbEdit = NIconToolButton(self, iSz, "edit")
|
126
123
|
self.tbEdit.setToolTip(self.tr("Edit Selected Build"))
|
127
124
|
self.tbEdit.setStyleSheet(buttonStyle)
|
128
125
|
self.tbEdit.clicked.connect(self._editSelectedBuild)
|
129
126
|
|
130
|
-
self.lblBuilds = QLabel("<b>{0}</b>".format(self.tr("Builds")))
|
127
|
+
self.lblBuilds = QLabel("<b>{0}</b>".format(self.tr("Builds")), self)
|
131
128
|
|
132
129
|
self.listToolBox = QHBoxLayout()
|
133
130
|
self.listToolBox.addWidget(self.lblBuilds)
|
@@ -140,21 +137,31 @@ class GuiManuscript(QDialog):
|
|
140
137
|
# Builds
|
141
138
|
# ======
|
142
139
|
|
143
|
-
self.buildList = QListWidget()
|
144
|
-
self.buildList.setIconSize(
|
140
|
+
self.buildList = QListWidget(self)
|
141
|
+
self.buildList.setIconSize(iSz)
|
145
142
|
self.buildList.doubleClicked.connect(self._editSelectedBuild)
|
146
143
|
self.buildList.currentItemChanged.connect(self._updateBuildDetails)
|
147
|
-
self.buildList.setSelectionMode(QAbstractItemView.SingleSelection)
|
148
|
-
self.buildList.setDragDropMode(QAbstractItemView.InternalMove)
|
144
|
+
self.buildList.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
145
|
+
self.buildList.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
146
|
+
|
147
|
+
# Details Tabs
|
148
|
+
# ============
|
149
149
|
|
150
150
|
self.buildDetails = _DetailsWidget(self)
|
151
151
|
self.buildDetails.setColumnWidth(
|
152
152
|
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "detailsWidth", 100)),
|
153
153
|
)
|
154
154
|
|
155
|
+
self.buildOutline = _OutlineWidget(self)
|
156
|
+
|
157
|
+
self.detailsTabs = QTabWidget(self)
|
158
|
+
self.detailsTabs.addTab(self.buildDetails, self.tr("Build"))
|
159
|
+
self.detailsTabs.addTab(self.buildOutline, self.tr("Outline"))
|
160
|
+
self.detailsTabs.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_FLAT_TABS))
|
161
|
+
|
155
162
|
self.buildSplit = QSplitter(Qt.Orientation.Vertical, self)
|
156
163
|
self.buildSplit.addWidget(self.buildList)
|
157
|
-
self.buildSplit.addWidget(self.
|
164
|
+
self.buildSplit.addWidget(self.detailsTabs)
|
158
165
|
self.buildSplit.setSizes([
|
159
166
|
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "listHeight", 50)),
|
160
167
|
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "detailsHeight", 50)),
|
@@ -163,16 +170,16 @@ class GuiManuscript(QDialog):
|
|
163
170
|
# Process Controls
|
164
171
|
# ================
|
165
172
|
|
166
|
-
self.btnPreview = QPushButton(self.tr("Preview"))
|
173
|
+
self.btnPreview = QPushButton(self.tr("Preview"), self)
|
167
174
|
self.btnPreview.clicked.connect(self._generatePreview)
|
168
175
|
|
169
|
-
self.btnPrint = QPushButton(self.tr("Print"))
|
176
|
+
self.btnPrint = QPushButton(self.tr("Print"), self)
|
170
177
|
self.btnPrint.clicked.connect(self._printDocument)
|
171
178
|
|
172
|
-
self.btnBuild = QPushButton(self.tr("Build"))
|
179
|
+
self.btnBuild = QPushButton(self.tr("Build"), self)
|
173
180
|
self.btnBuild.clicked.connect(self._buildManuscript)
|
174
181
|
|
175
|
-
self.btnClose = QPushButton(self.tr("Close"))
|
182
|
+
self.btnClose = QPushButton(self.tr("Close"), self)
|
176
183
|
self.btnClose.clicked.connect(self.close)
|
177
184
|
|
178
185
|
self.processBox = QGridLayout()
|
@@ -185,6 +192,15 @@ class GuiManuscript(QDialog):
|
|
185
192
|
# ============
|
186
193
|
|
187
194
|
self.docPreview = _PreviewWidget(self)
|
195
|
+
self.docStats = _StatsWidget(self)
|
196
|
+
|
197
|
+
self.docBox = QVBoxLayout()
|
198
|
+
self.docBox.addWidget(self.docPreview, 1)
|
199
|
+
self.docBox.addWidget(self.docStats, 0)
|
200
|
+
self.docBox.setContentsMargins(0, 0, 0, 0)
|
201
|
+
|
202
|
+
self.docWdiget = QWidget(self)
|
203
|
+
self.docWdiget.setLayout(self.docBox)
|
188
204
|
|
189
205
|
self.controlBox = QVBoxLayout()
|
190
206
|
self.controlBox.addLayout(self.listToolBox, 0)
|
@@ -192,12 +208,12 @@ class GuiManuscript(QDialog):
|
|
192
208
|
self.controlBox.addLayout(self.processBox, 0)
|
193
209
|
self.controlBox.setContentsMargins(0, 0, 0, 0)
|
194
210
|
|
195
|
-
self.optsWidget = QWidget()
|
211
|
+
self.optsWidget = QWidget(self)
|
196
212
|
self.optsWidget.setLayout(self.controlBox)
|
197
213
|
|
198
|
-
self.mainSplit = QSplitter()
|
214
|
+
self.mainSplit = QSplitter(self)
|
199
215
|
self.mainSplit.addWidget(self.optsWidget)
|
200
|
-
self.mainSplit.addWidget(self.
|
216
|
+
self.mainSplit.addWidget(self.docWdiget)
|
201
217
|
self.mainSplit.setCollapsible(0, False)
|
202
218
|
self.mainSplit.setCollapsible(1, False)
|
203
219
|
self.mainSplit.setStretchFactor(0, 0)
|
@@ -213,6 +229,9 @@ class GuiManuscript(QDialog):
|
|
213
229
|
self.setLayout(self.outerBox)
|
214
230
|
self.setSizeGripEnabled(True)
|
215
231
|
|
232
|
+
# Signals
|
233
|
+
self.buildOutline.outlineEntryClicked.connect(self.docPreview.navigateTo)
|
234
|
+
|
216
235
|
logger.debug("Ready: GuiManuscript")
|
217
236
|
|
218
237
|
return
|
@@ -245,7 +264,7 @@ class GuiManuscript(QDialog):
|
|
245
264
|
if isinstance(build, BuildSettings):
|
246
265
|
self._updatePreview(data, build)
|
247
266
|
except Exception:
|
248
|
-
logger.error("Failed to
|
267
|
+
logger.error("Failed to load build cache")
|
249
268
|
logException()
|
250
269
|
return
|
251
270
|
|
@@ -328,18 +347,21 @@ class GuiManuscript(QDialog):
|
|
328
347
|
return
|
329
348
|
|
330
349
|
docBuild = NWBuildDocument(SHARED.project, build)
|
350
|
+
docBuild.setPreviewMode(True)
|
331
351
|
docBuild.queueAll()
|
332
352
|
|
333
353
|
self.docPreview.beginNewBuild(len(docBuild))
|
334
354
|
for step, _ in docBuild.iterBuildHTML(None):
|
335
355
|
self.docPreview.buildStep(step + 1)
|
336
|
-
|
356
|
+
QApplication.processEvents()
|
337
357
|
|
338
358
|
buildObj = docBuild.lastBuild
|
339
359
|
assert isinstance(buildObj, ToHtml)
|
340
360
|
result = {
|
341
361
|
"uuid": build.buildID,
|
342
362
|
"time": int(time()),
|
363
|
+
"stats": buildObj.textStats,
|
364
|
+
"outline": buildObj.textOutline,
|
343
365
|
"styles": buildObj.getStyleSheet(),
|
344
366
|
"html": buildObj.fullHTML,
|
345
367
|
}
|
@@ -364,7 +386,7 @@ class GuiManuscript(QDialog):
|
|
364
386
|
build = self._getSelectedBuild()
|
365
387
|
if isinstance(build, BuildSettings):
|
366
388
|
dlgBuild = GuiManuscriptBuild(self, build)
|
367
|
-
dlgBuild.
|
389
|
+
dlgBuild.exec()
|
368
390
|
|
369
391
|
# After the build is done, save build settings changes
|
370
392
|
if build.changed:
|
@@ -377,7 +399,7 @@ class GuiManuscript(QDialog):
|
|
377
399
|
"""Open the print preview dialog."""
|
378
400
|
preview = QPrintPreviewDialog(self)
|
379
401
|
preview.paintRequested.connect(self.docPreview.printPreview)
|
380
|
-
preview.
|
402
|
+
preview.exec()
|
381
403
|
return
|
382
404
|
|
383
405
|
##
|
@@ -395,6 +417,8 @@ class GuiManuscript(QDialog):
|
|
395
417
|
self.docPreview.setJustify(
|
396
418
|
build.getBool("format.justifyText")
|
397
419
|
)
|
420
|
+
self.docStats.updateStats(data.get("stats", {}))
|
421
|
+
self.buildOutline.updateOutline(data.get("outline", {}))
|
398
422
|
return
|
399
423
|
|
400
424
|
def _getSelectedBuild(self) -> BuildSettings | None:
|
@@ -463,7 +487,7 @@ class GuiManuscript(QDialog):
|
|
463
487
|
dlgSettings.setModal(False)
|
464
488
|
dlgSettings.show()
|
465
489
|
dlgSettings.raise_()
|
466
|
-
|
490
|
+
QApplication.processEvents()
|
467
491
|
dlgSettings.loadContent()
|
468
492
|
dlgSettings.newSettingsReady.connect(self._processNewSettings)
|
469
493
|
|
@@ -503,8 +527,8 @@ class _DetailsWidget(QWidget):
|
|
503
527
|
# Tree Widget
|
504
528
|
self.listView = QTreeWidget(self)
|
505
529
|
self.listView.setHeaderLabels([self.tr("Setting"), self.tr("Value")])
|
506
|
-
self.listView.setIndentation(SHARED.theme.
|
507
|
-
self.listView.setSelectionMode(QAbstractItemView.NoSelection)
|
530
|
+
self.listView.setIndentation(SHARED.theme.baseIconHeight)
|
531
|
+
self.listView.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
508
532
|
|
509
533
|
# Assemble
|
510
534
|
self.outerBox = QVBoxLayout()
|
@@ -592,24 +616,26 @@ class _DetailsWidget(QWidget):
|
|
592
616
|
hFmt.resetScene()
|
593
617
|
hFmt.incScene()
|
594
618
|
title = self.tr("Title")
|
619
|
+
hidden = self.tr("Hidden")
|
595
620
|
|
596
621
|
item = QTreeWidgetItem()
|
597
622
|
item.setText(0, build.getLabel("headings"))
|
598
623
|
item.setText(1, "")
|
599
624
|
self.listView.addTopLevelItem(item)
|
600
|
-
|
601
|
-
"headings.fmtTitle", "headings.
|
602
|
-
"headings.
|
603
|
-
|
604
|
-
|
625
|
+
for hFormat, hHide in [
|
626
|
+
("headings.fmtTitle", "headings.hideTitle"),
|
627
|
+
("headings.fmtChapter", "headings.hideChapter"),
|
628
|
+
("headings.fmtUnnumbered", "headings.hideUnnumbered"),
|
629
|
+
("headings.fmtScene", "headings.hideScene"),
|
630
|
+
("headings.fmtHardScene", "headings.hideHardScene"),
|
631
|
+
("headings.fmtSection", "headings.hideSection"),
|
632
|
+
]:
|
605
633
|
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)
|
634
|
+
sub.setText(0, build.getLabel(hFormat))
|
635
|
+
if build.getBool(hHide):
|
636
|
+
sub.setText(1, f"[{hidden}]")
|
637
|
+
else:
|
638
|
+
sub.setText(1, hFmt.apply(build.getStr(hFormat), title, 0))
|
613
639
|
item.addChild(sub)
|
614
640
|
|
615
641
|
# Text Content
|
@@ -617,11 +643,10 @@ class _DetailsWidget(QWidget):
|
|
617
643
|
item.setText(0, build.getLabel("text.grpContent"))
|
618
644
|
item.setText(1, "")
|
619
645
|
self.listView.addTopLevelItem(item)
|
620
|
-
|
646
|
+
for key in [
|
621
647
|
"text.includeSynopsis", "text.includeComments",
|
622
648
|
"text.includeKeywords", "text.includeBodyText",
|
623
|
-
]
|
624
|
-
for key in entries:
|
649
|
+
]:
|
625
650
|
sub = QTreeWidgetItem()
|
626
651
|
sub.setText(0, build.getLabel(key))
|
627
652
|
sub.setIcon(1, on if build.getBool(key) else off)
|
@@ -635,6 +660,87 @@ class _DetailsWidget(QWidget):
|
|
635
660
|
# END Class _DetailsWidget
|
636
661
|
|
637
662
|
|
663
|
+
class _OutlineWidget(QWidget):
|
664
|
+
|
665
|
+
D_LINE = QtUserRole
|
666
|
+
|
667
|
+
outlineEntryClicked = pyqtSignal(str)
|
668
|
+
|
669
|
+
def __init__(self, parent: QWidget) -> None:
|
670
|
+
super().__init__(parent=parent)
|
671
|
+
|
672
|
+
self._outline = {}
|
673
|
+
|
674
|
+
# Tree Widget
|
675
|
+
self.listView = QTreeWidget(self)
|
676
|
+
self.listView.setHeaderHidden(True)
|
677
|
+
self.listView.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
678
|
+
self.listView.itemClicked.connect(self._onItemClick)
|
679
|
+
|
680
|
+
# Assemble
|
681
|
+
self.outerBox = QVBoxLayout()
|
682
|
+
self.outerBox.addWidget(self.listView)
|
683
|
+
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
684
|
+
self.setLayout(self.outerBox)
|
685
|
+
|
686
|
+
return
|
687
|
+
|
688
|
+
def updateOutline(self, data: dict[str, str]) -> None:
|
689
|
+
"""Update the outline."""
|
690
|
+
if isinstance(data, dict) and data != self._outline:
|
691
|
+
self.listView.clear()
|
692
|
+
|
693
|
+
tFont = self.font()
|
694
|
+
tFont.setBold(True)
|
695
|
+
tBrush = self.palette().highlight()
|
696
|
+
|
697
|
+
hFont = self.font()
|
698
|
+
hFont.setBold(True)
|
699
|
+
hFont.setUnderline(True)
|
700
|
+
|
701
|
+
root = self.listView.invisibleRootItem()
|
702
|
+
parent = root
|
703
|
+
indent = False
|
704
|
+
for anchor, entry in data.items():
|
705
|
+
prefix, _, text = entry.partition("|")
|
706
|
+
if prefix in ("TT", "PT", "CH", "SC", "H1", "H2"):
|
707
|
+
item = QTreeWidgetItem([text])
|
708
|
+
item.setData(0, self.D_LINE, anchor)
|
709
|
+
if prefix == "TT":
|
710
|
+
item.setFont(0, tFont)
|
711
|
+
item.setForeground(0, tBrush)
|
712
|
+
root.addChild(item)
|
713
|
+
parent = root
|
714
|
+
elif prefix == "PT":
|
715
|
+
item.setFont(0, hFont)
|
716
|
+
root.addChild(item)
|
717
|
+
parent = root
|
718
|
+
elif prefix in ("CH", "H1"):
|
719
|
+
root.addChild(item)
|
720
|
+
parent = item
|
721
|
+
elif prefix in ("SC", "H2"):
|
722
|
+
parent.addChild(item)
|
723
|
+
indent = True
|
724
|
+
|
725
|
+
self.listView.setIndentation(
|
726
|
+
SHARED.theme.baseIconHeight if indent else CONFIG.pxInt(4)
|
727
|
+
)
|
728
|
+
self._outline = data
|
729
|
+
|
730
|
+
return
|
731
|
+
|
732
|
+
##
|
733
|
+
# Private Slots
|
734
|
+
##
|
735
|
+
|
736
|
+
def _onItemClick(self, item: QTreeWidgetItem) -> None:
|
737
|
+
"""Process tree item click."""
|
738
|
+
self.outlineEntryClicked.emit(str(item.data(0, self.D_LINE)))
|
739
|
+
return
|
740
|
+
|
741
|
+
# END Class _OutlineWidget
|
742
|
+
|
743
|
+
|
638
744
|
class _PreviewWidget(QTextBrowser):
|
639
745
|
|
640
746
|
def __init__(self, parent: QWidget) -> None:
|
@@ -645,8 +751,8 @@ class _PreviewWidget(QTextBrowser):
|
|
645
751
|
|
646
752
|
# Document Setup
|
647
753
|
dPalette = self.palette()
|
648
|
-
dPalette.setColor(QPalette.Base, QColor(255, 255, 255))
|
649
|
-
dPalette.setColor(QPalette.Text, QColor(0, 0, 0))
|
754
|
+
dPalette.setColor(QPalette.ColorRole.Base, QColor(255, 255, 255))
|
755
|
+
dPalette.setColor(QPalette.ColorRole.Text, QColor(0, 0, 0))
|
650
756
|
self.setPalette(dPalette)
|
651
757
|
|
652
758
|
self.setMinimumWidth(40*SHARED.theme.textNWidth)
|
@@ -663,8 +769,8 @@ class _PreviewWidget(QTextBrowser):
|
|
663
769
|
|
664
770
|
# Document Age
|
665
771
|
aPalette = self.palette()
|
666
|
-
aPalette.setColor(QPalette.
|
667
|
-
aPalette.setColor(QPalette.
|
772
|
+
aPalette.setColor(QPalette.ColorRole.Window, aPalette.toolTipBase().color())
|
773
|
+
aPalette.setColor(QPalette.ColorRole.WindowText, aPalette.toolTipText().color())
|
668
774
|
|
669
775
|
aFont = self.font()
|
670
776
|
aFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
@@ -674,7 +780,7 @@ class _PreviewWidget(QTextBrowser):
|
|
674
780
|
self.ageLabel.setFont(aFont)
|
675
781
|
self.ageLabel.setPalette(aPalette)
|
676
782
|
self.ageLabel.setAutoFillBackground(True)
|
677
|
-
self.ageLabel.setAlignment(
|
783
|
+
self.ageLabel.setAlignment(QtAlignCenter)
|
678
784
|
self.ageLabel.setFixedHeight(int(2.1*SHARED.theme.fontPixelSize))
|
679
785
|
|
680
786
|
# Progress
|
@@ -692,8 +798,8 @@ class _PreviewWidget(QTextBrowser):
|
|
692
798
|
self._updateBuildAge()
|
693
799
|
|
694
800
|
# Age Timer
|
695
|
-
self.ageTimer = QTimer()
|
696
|
-
self.ageTimer.setInterval(
|
801
|
+
self.ageTimer = QTimer(self)
|
802
|
+
self.ageTimer.setInterval(10000)
|
697
803
|
self.ageTimer.timeout.connect(self._updateBuildAge)
|
698
804
|
self.ageTimer.start()
|
699
805
|
|
@@ -713,9 +819,9 @@ class _PreviewWidget(QTextBrowser):
|
|
713
819
|
"""Enable/disable the justify text option."""
|
714
820
|
pOptions = self.document().defaultTextOption()
|
715
821
|
if state:
|
716
|
-
pOptions.setAlignment(
|
822
|
+
pOptions.setAlignment(QtAlignJustify)
|
717
823
|
else:
|
718
|
-
pOptions.setAlignment(
|
824
|
+
pOptions.setAlignment(QtAlignAbsolute)
|
719
825
|
self.document().setDefaultTextOption(pOptions)
|
720
826
|
return
|
721
827
|
|
@@ -745,31 +851,24 @@ class _PreviewWidget(QTextBrowser):
|
|
745
851
|
def buildStep(self, value: int) -> None:
|
746
852
|
"""Update the progress bar value."""
|
747
853
|
self.buildProgress.setValue(value)
|
748
|
-
|
854
|
+
QApplication.processEvents()
|
749
855
|
return
|
750
856
|
|
751
857
|
def setContent(self, data: dict) -> None:
|
752
858
|
"""Set the content of the preview widget."""
|
753
859
|
sPos = self.verticalScrollBar().value()
|
754
|
-
|
860
|
+
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
755
861
|
|
756
862
|
self.buildProgress.setCentreText(self.tr("Processing ..."))
|
757
|
-
|
758
|
-
|
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
|
-
]))
|
863
|
+
QApplication.processEvents()
|
864
|
+
|
865
|
+
styles = "\n".join(data.get("styles", []))
|
765
866
|
self.document().setDefaultStyleSheet(styles)
|
766
867
|
|
767
868
|
html = "".join(data.get("html", []))
|
768
869
|
html = html.replace("\t", "!!tab!!")
|
769
|
-
html = html.replace("<del>", "<span style='text-decoration: line-through;'>")
|
770
|
-
html = html.replace("</del>", "</span>")
|
771
870
|
self.setHtml(html)
|
772
|
-
|
871
|
+
QApplication.processEvents()
|
773
872
|
while self.find("!!tab!!"):
|
774
873
|
cursor = self.textCursor()
|
775
874
|
cursor.insertText("\t")
|
@@ -783,8 +882,8 @@ class _PreviewWidget(QTextBrowser):
|
|
783
882
|
self.document().markContentsDirty(0, self.document().characterCount())
|
784
883
|
|
785
884
|
self.buildProgress.setCentreText(self.tr("Done"))
|
786
|
-
|
787
|
-
|
885
|
+
QApplication.restoreOverrideCursor()
|
886
|
+
QApplication.processEvents()
|
788
887
|
QTimer.singleShot(300, self._hideProgress)
|
789
888
|
|
790
889
|
return
|
@@ -806,10 +905,17 @@ class _PreviewWidget(QTextBrowser):
|
|
806
905
|
@pyqtSlot("QPrinter*")
|
807
906
|
def printPreview(self, printer: QPrinter) -> None:
|
808
907
|
"""Connect the print preview painter to the document viewer."""
|
809
|
-
|
810
|
-
printer.setOrientation(QPrinter.Portrait)
|
908
|
+
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
909
|
+
printer.setOrientation(QPrinter.Orientation.Portrait)
|
811
910
|
self.document().print(printer)
|
812
|
-
|
911
|
+
QApplication.restoreOverrideCursor()
|
912
|
+
return
|
913
|
+
|
914
|
+
@pyqtSlot(str)
|
915
|
+
def navigateTo(self, anchor: str) -> None:
|
916
|
+
"""Go to a specific #link in the document."""
|
917
|
+
logger.debug("Moving to anchor '#%s'", anchor)
|
918
|
+
self.setSource(QUrl(f"#{anchor}"))
|
813
919
|
return
|
814
920
|
|
815
921
|
##
|
@@ -821,7 +927,7 @@ class _PreviewWidget(QTextBrowser):
|
|
821
927
|
"""Update the build time and the fuzzy age."""
|
822
928
|
if self._docTime > 0:
|
823
929
|
strBuildTime = "%s (%s)" % (
|
824
|
-
datetime.fromtimestamp(self._docTime)
|
930
|
+
CONFIG.localDateTime(datetime.fromtimestamp(self._docTime)),
|
825
931
|
fuzzyTime(int(time()) - self._docTime)
|
826
932
|
)
|
827
933
|
else:
|
@@ -859,3 +965,181 @@ class _PreviewWidget(QTextBrowser):
|
|
859
965
|
return
|
860
966
|
|
861
967
|
# END Class _PreviewWidget
|
968
|
+
|
969
|
+
|
970
|
+
class _StatsWidget(QWidget):
|
971
|
+
|
972
|
+
def __init__(self, parent: QWidget) -> None:
|
973
|
+
super().__init__(parent=parent)
|
974
|
+
|
975
|
+
font = self.font()
|
976
|
+
font.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
977
|
+
self.setFont(font)
|
978
|
+
|
979
|
+
self.minWidget = QWidget(self)
|
980
|
+
self.maxWidget = QWidget(self)
|
981
|
+
|
982
|
+
self.toggleButton = NIconToggleButton(self, SHARED.theme.baseIconSize, "unfold")
|
983
|
+
self.toggleButton.toggled.connect(self._toggleView)
|
984
|
+
|
985
|
+
self._buildMinimal()
|
986
|
+
self._buildMaximal()
|
987
|
+
|
988
|
+
self.mainStack = QStackedWidget(self)
|
989
|
+
self.mainStack.addWidget(self.minWidget)
|
990
|
+
self.mainStack.addWidget(self.maxWidget)
|
991
|
+
|
992
|
+
self.outerBox = QHBoxLayout()
|
993
|
+
self.outerBox.addWidget(self.toggleButton, 0, QtAlignTop)
|
994
|
+
self.outerBox.addWidget(self.mainStack, 1, QtAlignTop)
|
995
|
+
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
996
|
+
|
997
|
+
self.setLayout(self.outerBox)
|
998
|
+
|
999
|
+
self._toggleView(False)
|
1000
|
+
|
1001
|
+
return
|
1002
|
+
|
1003
|
+
def updateStats(self, data: dict[str, int]) -> None:
|
1004
|
+
"""Update the stats values from a Tokenizer stats dict."""
|
1005
|
+
# Minimal
|
1006
|
+
self.minWordCount.setText("{0:n}".format(data.get("allWords", 0)))
|
1007
|
+
self.minCharCount.setText("{0:n}".format(data.get("allChars", 0)))
|
1008
|
+
|
1009
|
+
# Maximal
|
1010
|
+
self.maxTotalWords.setText("{0:n}".format(data.get("allWords", 0)))
|
1011
|
+
self.maxHeaderWords.setText("{0:n}".format(data.get("titleWords", 0)))
|
1012
|
+
self.maxTextWords.setText("{0:n}".format(data.get("textWords", 0)))
|
1013
|
+
self.maxTitleCount.setText("{0:n}".format(data.get("titleCount", 0)))
|
1014
|
+
self.maxParCount.setText("{0:n}".format(data.get("paragraphCount", 0)))
|
1015
|
+
|
1016
|
+
self.maxTotalChars.setText("{0:n}".format(data.get("allChars", 0)))
|
1017
|
+
self.maxHeaderChars.setText("{0:n}".format(data.get("titleChars", 0)))
|
1018
|
+
self.maxTextChars.setText("{0:n}".format(data.get("textChars", 0)))
|
1019
|
+
|
1020
|
+
self.maxTotalWordChars.setText("{0:n}".format(data.get("allWordChars", 0)))
|
1021
|
+
self.maxHeaderWordChars.setText("{0:n}".format(data.get("titleWordChars", 0)))
|
1022
|
+
self.maxTextWordChars.setText("{0:n}".format(data.get("textWordChars", 0)))
|
1023
|
+
|
1024
|
+
return
|
1025
|
+
|
1026
|
+
##
|
1027
|
+
# Private Slots
|
1028
|
+
##
|
1029
|
+
|
1030
|
+
@pyqtSlot(bool)
|
1031
|
+
def _toggleView(self, state: bool) -> None:
|
1032
|
+
"""Toggle minimal or maximal view."""
|
1033
|
+
ignored = QSizePolicy.Policy.Ignored
|
1034
|
+
expanded = QSizePolicy.Policy.Expanding
|
1035
|
+
if state:
|
1036
|
+
self.mainStack.setCurrentWidget(self.maxWidget)
|
1037
|
+
self.maxWidget.setSizePolicy(expanded, expanded)
|
1038
|
+
self.minWidget.setSizePolicy(ignored, ignored)
|
1039
|
+
else:
|
1040
|
+
self.mainStack.setCurrentWidget(self.minWidget)
|
1041
|
+
self.maxWidget.setSizePolicy(ignored, ignored)
|
1042
|
+
self.minWidget.setSizePolicy(expanded, expanded)
|
1043
|
+
self.maxWidget.adjustSize()
|
1044
|
+
self.minWidget.adjustSize()
|
1045
|
+
self.mainStack.adjustSize()
|
1046
|
+
self.adjustSize()
|
1047
|
+
return
|
1048
|
+
|
1049
|
+
##
|
1050
|
+
# Internal Functions
|
1051
|
+
##
|
1052
|
+
|
1053
|
+
def _buildMinimal(self) -> None:
|
1054
|
+
"""Build the minimal stats page."""
|
1055
|
+
mPx = CONFIG.pxInt(8)
|
1056
|
+
|
1057
|
+
self.lblWordCount = QLabel(self.tr("Words"), self)
|
1058
|
+
self.minWordCount = QLabel(self)
|
1059
|
+
|
1060
|
+
self.lblCharCount = QLabel(self.tr("Characters"), self)
|
1061
|
+
self.minCharCount = QLabel(self)
|
1062
|
+
|
1063
|
+
# Assemble
|
1064
|
+
self.minLayout = QHBoxLayout()
|
1065
|
+
self.minLayout.addWidget(self.lblWordCount)
|
1066
|
+
self.minLayout.addWidget(self.minWordCount)
|
1067
|
+
self.minLayout.addSpacing(mPx)
|
1068
|
+
self.minLayout.addWidget(self.lblCharCount)
|
1069
|
+
self.minLayout.addWidget(self.minCharCount)
|
1070
|
+
self.minLayout.addStretch(1)
|
1071
|
+
self.minLayout.setSpacing(mPx)
|
1072
|
+
self.minLayout.setContentsMargins(0, 0, 0, 0)
|
1073
|
+
|
1074
|
+
self.minWidget.setLayout(self.minLayout)
|
1075
|
+
|
1076
|
+
return
|
1077
|
+
|
1078
|
+
def _buildMaximal(self) -> None:
|
1079
|
+
"""Build the maximal stats page."""
|
1080
|
+
hPx = CONFIG.pxInt(12)
|
1081
|
+
vPx = CONFIG.pxInt(4)
|
1082
|
+
|
1083
|
+
# Left Column
|
1084
|
+
self.maxTotalWords = QLabel(self)
|
1085
|
+
self.maxHeaderWords = QLabel(self)
|
1086
|
+
self.maxTextWords = QLabel(self)
|
1087
|
+
self.maxTitleCount = QLabel(self)
|
1088
|
+
self.maxParCount = QLabel(self)
|
1089
|
+
|
1090
|
+
self.maxTotalWords.setAlignment(QtAlignRight)
|
1091
|
+
self.maxHeaderWords.setAlignment(QtAlignRight)
|
1092
|
+
self.maxTextWords.setAlignment(QtAlignRight)
|
1093
|
+
self.maxTitleCount.setAlignment(QtAlignRight)
|
1094
|
+
self.maxParCount.setAlignment(QtAlignRight)
|
1095
|
+
|
1096
|
+
self.leftForm = QFormLayout()
|
1097
|
+
self.leftForm.addRow(self.tr("Words"), self.maxTotalWords)
|
1098
|
+
self.leftForm.addRow(self.tr("Heading Words"), self.maxHeaderWords)
|
1099
|
+
self.leftForm.addRow(self.tr("Body Text Words"), self.maxTextWords)
|
1100
|
+
self.leftForm.addRow("", QLabel(self))
|
1101
|
+
self.leftForm.addRow(self.tr("Headings"), self.maxTitleCount)
|
1102
|
+
self.leftForm.addRow(self.tr("Paragraphs"), self.maxParCount)
|
1103
|
+
self.leftForm.setHorizontalSpacing(hPx)
|
1104
|
+
self.leftForm.setVerticalSpacing(vPx)
|
1105
|
+
|
1106
|
+
# Right Column
|
1107
|
+
self.maxTotalChars = QLabel(self)
|
1108
|
+
self.maxHeaderChars = QLabel(self)
|
1109
|
+
self.maxTextChars = QLabel(self)
|
1110
|
+
|
1111
|
+
self.maxTotalWordChars = QLabel(self)
|
1112
|
+
self.maxHeaderWordChars = QLabel(self)
|
1113
|
+
self.maxTextWordChars = QLabel(self)
|
1114
|
+
|
1115
|
+
self.maxTotalChars.setAlignment(QtAlignRight)
|
1116
|
+
self.maxHeaderChars.setAlignment(QtAlignRight)
|
1117
|
+
self.maxTextChars.setAlignment(QtAlignRight)
|
1118
|
+
|
1119
|
+
self.maxTotalWordChars.setAlignment(QtAlignRight)
|
1120
|
+
self.maxHeaderWordChars.setAlignment(QtAlignRight)
|
1121
|
+
self.maxTextWordChars.setAlignment(QtAlignRight)
|
1122
|
+
|
1123
|
+
self.rightForm = QFormLayout()
|
1124
|
+
self.rightForm.addRow(self.tr("Characters"), self.maxTotalChars)
|
1125
|
+
self.rightForm.addRow(self.tr("Heading Characters"), self.maxHeaderChars)
|
1126
|
+
self.rightForm.addRow(self.tr("Body Text Characters"), self.maxTextChars)
|
1127
|
+
self.rightForm.addRow(self.tr("Characters, No Spaces"), self.maxTotalWordChars)
|
1128
|
+
self.rightForm.addRow(self.tr("Heading Characters, No Spaces"), self.maxHeaderWordChars)
|
1129
|
+
self.rightForm.addRow(self.tr("Body Text Characters, No Spaces"), self.maxTextWordChars)
|
1130
|
+
self.rightForm.setHorizontalSpacing(hPx)
|
1131
|
+
self.rightForm.setVerticalSpacing(vPx)
|
1132
|
+
|
1133
|
+
# Assemble
|
1134
|
+
self.maxLayout = QHBoxLayout()
|
1135
|
+
self.maxLayout.addLayout(self.leftForm)
|
1136
|
+
self.maxLayout.addLayout(self.rightForm)
|
1137
|
+
self.maxLayout.addStretch(1)
|
1138
|
+
self.maxLayout.setSpacing(CONFIG.pxInt(32))
|
1139
|
+
self.maxLayout.setContentsMargins(0, 0, 0, 0)
|
1140
|
+
|
1141
|
+
self.maxWidget.setLayout(self.maxLayout)
|
1142
|
+
|
1143
|
+
return
|
1144
|
+
|
1145
|
+
# END Class _StatsWidget
|