novelWriter 2.2rc1__py3-none-any.whl → 2.3__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.
Files changed (162) hide show
  1. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/RECORD +149 -132
  3. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/WHEEL +1 -1
  4. novelWriter-2.3.dist-info/entry_points.txt +2 -0
  5. novelwriter/__init__.py +11 -6
  6. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  7. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  8. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  9. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  10. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  11. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  12. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  13. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  14. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  15. novelwriter/assets/i18n/project_de_DE.json +1 -0
  16. novelwriter/assets/i18n/project_en_US.json +1 -0
  17. novelwriter/assets/i18n/project_es_419.json +11 -0
  18. novelwriter/assets/i18n/project_fr_FR.json +11 -0
  19. novelwriter/assets/i18n/project_it_IT.json +11 -0
  20. novelwriter/assets/i18n/project_ja_JP.json +2 -1
  21. novelwriter/assets/i18n/project_nb_NO.json +1 -0
  22. novelwriter/assets/i18n/project_nl_NL.json +11 -0
  23. novelwriter/assets/i18n/project_pt_BR.json +11 -0
  24. novelwriter/assets/i18n/project_zh_CN.json +11 -0
  25. novelwriter/assets/icons/typicons_dark/icons.conf +11 -2
  26. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +6 -0
  27. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  28. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
  29. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
  30. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
  31. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
  32. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
  33. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
  34. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
  35. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
  36. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
  37. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  38. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  39. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  40. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +9 -0
  41. novelwriter/assets/icons/typicons_light/icons.conf +11 -2
  42. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +6 -0
  43. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  44. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
  45. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
  46. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
  47. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
  48. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
  49. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
  50. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
  51. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
  52. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
  53. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  54. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  55. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  56. novelwriter/assets/icons/typicons_light/typ_th-list.svg +9 -0
  57. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  58. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  59. novelwriter/assets/images/welcome-dark.jpg +0 -0
  60. novelwriter/assets/images/welcome-light.jpg +0 -0
  61. novelwriter/assets/manual.pdf +0 -0
  62. novelwriter/assets/sample.zip +0 -0
  63. novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
  64. novelwriter/assets/syntax/default_dark.conf +1 -0
  65. novelwriter/assets/syntax/default_light.conf +1 -0
  66. novelwriter/assets/syntax/grey_dark.conf +1 -0
  67. novelwriter/assets/syntax/grey_light.conf +1 -0
  68. novelwriter/assets/syntax/light_owl.conf +1 -0
  69. novelwriter/assets/syntax/night_owl.conf +1 -0
  70. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  71. novelwriter/assets/syntax/solarized_light.conf +1 -0
  72. novelwriter/assets/syntax/tango.conf +23 -0
  73. novelwriter/assets/syntax/tomorrow.conf +1 -0
  74. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  75. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  76. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  77. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  78. novelwriter/assets/text/credits_en.htm +4 -2
  79. novelwriter/assets/themes/cyberpunk_night.conf +29 -0
  80. novelwriter/assets/themes/default_dark.conf +2 -2
  81. novelwriter/assets/themes/default_light.conf +2 -2
  82. novelwriter/common.py +64 -66
  83. novelwriter/config.py +39 -44
  84. novelwriter/constants.py +39 -17
  85. novelwriter/core/buildsettings.py +8 -8
  86. novelwriter/core/coretools.py +198 -157
  87. novelwriter/core/docbuild.py +7 -4
  88. novelwriter/core/document.py +7 -7
  89. novelwriter/core/index.py +90 -57
  90. novelwriter/core/item.py +23 -5
  91. novelwriter/core/options.py +11 -10
  92. novelwriter/core/project.py +73 -47
  93. novelwriter/core/projectdata.py +3 -16
  94. novelwriter/core/projectxml.py +14 -42
  95. novelwriter/core/sessions.py +4 -3
  96. novelwriter/core/spellcheck.py +6 -4
  97. novelwriter/core/status.py +5 -4
  98. novelwriter/core/storage.py +183 -141
  99. novelwriter/core/tohtml.py +6 -4
  100. novelwriter/core/tokenizer.py +110 -83
  101. novelwriter/core/tomd.py +2 -2
  102. novelwriter/core/toodt.py +41 -31
  103. novelwriter/core/tree.py +5 -4
  104. novelwriter/dialogs/about.py +88 -179
  105. novelwriter/dialogs/docmerge.py +30 -20
  106. novelwriter/dialogs/docsplit.py +33 -22
  107. novelwriter/dialogs/editlabel.py +20 -8
  108. novelwriter/dialogs/preferences.py +562 -725
  109. novelwriter/dialogs/{projsettings.py → projectsettings.py} +301 -270
  110. novelwriter/dialogs/quotes.py +47 -36
  111. novelwriter/dialogs/wordlist.py +128 -59
  112. novelwriter/enum.py +25 -22
  113. novelwriter/error.py +2 -2
  114. novelwriter/extensions/circularprogress.py +12 -12
  115. novelwriter/extensions/configlayout.py +185 -146
  116. novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
  117. novelwriter/extensions/modified.py +81 -0
  118. novelwriter/extensions/novelselector.py +27 -13
  119. novelwriter/extensions/pagedsidebar.py +15 -20
  120. novelwriter/extensions/simpleprogress.py +8 -9
  121. novelwriter/extensions/statusled.py +9 -9
  122. novelwriter/extensions/switch.py +32 -64
  123. novelwriter/extensions/switchbox.py +2 -7
  124. novelwriter/extensions/versioninfo.py +153 -0
  125. novelwriter/gui/doceditor.py +250 -214
  126. novelwriter/gui/dochighlight.py +66 -94
  127. novelwriter/gui/docviewer.py +71 -98
  128. novelwriter/gui/docviewerpanel.py +140 -47
  129. novelwriter/gui/editordocument.py +3 -3
  130. novelwriter/gui/itemdetails.py +9 -9
  131. novelwriter/gui/mainmenu.py +47 -47
  132. novelwriter/gui/noveltree.py +53 -61
  133. novelwriter/gui/outline.py +100 -76
  134. novelwriter/gui/projtree.py +246 -112
  135. novelwriter/gui/sidebar.py +9 -8
  136. novelwriter/gui/statusbar.py +49 -7
  137. novelwriter/gui/theme.py +74 -76
  138. novelwriter/guimain.py +175 -330
  139. novelwriter/shared.py +68 -30
  140. novelwriter/tools/dictionaries.py +7 -8
  141. novelwriter/tools/lipsum.py +34 -28
  142. novelwriter/tools/manusbuild.py +3 -4
  143. novelwriter/tools/manuscript.py +25 -32
  144. novelwriter/tools/manussettings.py +194 -225
  145. novelwriter/tools/noveldetails.py +525 -0
  146. novelwriter/tools/welcome.py +819 -0
  147. novelwriter/tools/writingstats.py +26 -13
  148. novelWriter-2.2rc1.dist-info/entry_points.txt +0 -5
  149. novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
  150. novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
  151. novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
  152. novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
  153. novelwriter/assets/images/wizard-back.jpg +0 -0
  154. novelwriter/assets/text/gplv3_en.htm +0 -641
  155. novelwriter/assets/text/release_notes.htm +0 -17
  156. novelwriter/dialogs/projdetails.py +0 -525
  157. novelwriter/dialogs/projload.py +0 -298
  158. novelwriter/dialogs/updates.py +0 -182
  159. novelwriter/extensions/pageddialog.py +0 -130
  160. novelwriter/tools/projwizard.py +0 -478
  161. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/LICENSE.md +0 -0
  162. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,525 @@
1
+ """
2
+ novelWriter – GUI Novel Info
3
+ ============================
4
+
5
+ File History:
6
+ Created: 2024-01-18 [2.3b1] GuiNovelDetails
7
+
8
+ This file is a part of novelWriter
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
+
11
+ This program is free software: you can redistribute it and/or modify
12
+ it under the terms of the GNU General Public License as published by
13
+ the Free Software Foundation, either version 3 of the License, or
14
+ (at your option) any later version.
15
+
16
+ This program is distributed in the hope that it will be useful, but
17
+ WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19
+ General Public License for more details.
20
+
21
+ You should have received a copy of the GNU General Public License
22
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import math
27
+ import logging
28
+
29
+ from PyQt5.QtGui import QCloseEvent
30
+ from PyQt5.QtCore import QSize, Qt, pyqtSlot
31
+ from PyQt5.QtWidgets import (
32
+ QAbstractItemView, QDialog, QDialogButtonBox, QFormLayout, QGridLayout,
33
+ QHBoxLayout, QLabel, QSpinBox, QStackedWidget, QTreeWidget,
34
+ QTreeWidgetItem, QVBoxLayout, QWidget
35
+ )
36
+
37
+ from novelwriter import CONFIG, SHARED
38
+ from novelwriter.common import formatTime, numberToRoman
39
+ from novelwriter.constants import nwUnicode
40
+ from novelwriter.extensions.switch import NSwitch
41
+ from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollablePage
42
+ from novelwriter.extensions.pagedsidebar import NPagedSideBar
43
+ from novelwriter.extensions.novelselector import NovelSelector
44
+
45
+ logger = logging.getLogger(__name__)
46
+
47
+
48
+ class GuiNovelDetails(QDialog):
49
+
50
+ PAGE_OVERVIEW = 1
51
+ PAGE_CONTENTS = 2
52
+
53
+ def __init__(self, parent: QWidget) -> None:
54
+ super().__init__(parent=parent)
55
+
56
+ logger.debug("Create: GuiNovelDetails")
57
+ self.setObjectName("GuiNovelDetails")
58
+ self.setWindowTitle(self.tr("Novel Details"))
59
+
60
+ options = SHARED.project.options
61
+ self.setMinimumSize(CONFIG.pxInt(500), CONFIG.pxInt(400))
62
+ self.resize(
63
+ CONFIG.pxInt(options.getInt("GuiNovelDetails", "winWidth", CONFIG.pxInt(650))),
64
+ CONFIG.pxInt(options.getInt("GuiNovelDetails", "winHeight", CONFIG.pxInt(500)))
65
+ )
66
+
67
+ # Title
68
+ self.titleLabel = NColourLabel(
69
+ self.tr("Novel Details"), SHARED.theme.helpText,
70
+ parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4)
71
+ )
72
+
73
+ # Novel Selector
74
+ self.novelSelector = NovelSelector(self)
75
+ self.novelSelector.refreshNovelList()
76
+ self.novelSelector.setHandle(
77
+ options.getString("GuiNovelDetails", "novelRoot", self.novelSelector.firstHandle or "")
78
+ )
79
+
80
+ # SideBar
81
+ self.sidebar = NPagedSideBar(self)
82
+ self.sidebar.setLabelColor(SHARED.theme.helpText)
83
+ self.sidebar.addButton(self.tr("Overview"), self.PAGE_OVERVIEW)
84
+ self.sidebar.addButton(self.tr("Contents"), self.PAGE_CONTENTS)
85
+ self.sidebar.setSelected(self.PAGE_OVERVIEW)
86
+ self.sidebar.buttonClicked.connect(self._sidebarClicked)
87
+
88
+ # Content
89
+ self.overviewPage = _OverviewPage(self)
90
+ self.contentsPage = _ContentsPage(self)
91
+
92
+ self.mainStack = QStackedWidget(self)
93
+ self.mainStack.addWidget(self.overviewPage)
94
+ self.mainStack.addWidget(self.contentsPage)
95
+
96
+ # Buttons
97
+ self.buttonBox = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
98
+ self.buttonBox.rejected.connect(self.close)
99
+
100
+ # Assemble
101
+ self.topBox = QHBoxLayout()
102
+ self.topBox.addWidget(self.titleLabel)
103
+ self.topBox.addStretch(1)
104
+ self.topBox.addWidget(self.novelSelector, 1)
105
+
106
+ self.mainBox = QHBoxLayout()
107
+ self.mainBox.addWidget(self.sidebar)
108
+ self.mainBox.addWidget(self.mainStack)
109
+ self.mainBox.setContentsMargins(0, 0, 0, 0)
110
+
111
+ self.outerBox = QVBoxLayout()
112
+ self.outerBox.addLayout(self.topBox)
113
+ self.outerBox.addLayout(self.mainBox)
114
+ self.outerBox.addWidget(self.buttonBox)
115
+ self.outerBox.setSpacing(CONFIG.pxInt(8))
116
+
117
+ self.setLayout(self.outerBox)
118
+ self.setSizeGripEnabled(True)
119
+
120
+ # Connect Signals
121
+ self.novelSelector.novelSelectionChanged.connect(self.overviewPage.novelValueChanged)
122
+ self.novelSelector.novelSelectionChanged.connect(self.contentsPage.novelValueChanged)
123
+
124
+ logger.debug("Ready: GuiNovelDetails")
125
+
126
+ return
127
+
128
+ def __del__(self) -> None: # pragma: no cover
129
+ logger.debug("Delete: GuiNovelDetails")
130
+ return
131
+
132
+ ##
133
+ # Methods
134
+ ##
135
+
136
+ def updateValues(self) -> None:
137
+ """Load the dialogs initial values."""
138
+ self.overviewPage.updateProjectData()
139
+ self.overviewPage.novelValueChanged(self.novelSelector.handle)
140
+ self.contentsPage.novelValueChanged(self.novelSelector.handle)
141
+ return
142
+
143
+ ##
144
+ # Events
145
+ ##
146
+
147
+ def closeEvent(self, event: QCloseEvent) -> None:
148
+ """Capture the user closing the window and save settings."""
149
+ self._saveSettings()
150
+ event.accept()
151
+ self.deleteLater()
152
+ return
153
+
154
+ ##
155
+ # Private Slots
156
+ ##
157
+
158
+ @pyqtSlot(int)
159
+ def _sidebarClicked(self, pageId: int) -> None:
160
+ """Process a user request to switch page."""
161
+ if pageId == self.PAGE_OVERVIEW:
162
+ self.mainStack.setCurrentWidget(self.overviewPage)
163
+ elif pageId == self.PAGE_CONTENTS:
164
+ self.mainStack.setCurrentWidget(self.contentsPage)
165
+ return
166
+
167
+ ##
168
+ # Internal Functions
169
+ ##
170
+
171
+ def _saveSettings(self) -> None:
172
+ """Save the user GUI settings."""
173
+ winWidth = CONFIG.rpxInt(self.width())
174
+ winHeight = CONFIG.rpxInt(self.height())
175
+ novelRoot = self.novelSelector.handle
176
+
177
+ logger.debug("Saving State: GuiNovelDetails")
178
+ options = SHARED.project.options
179
+ options.setValue("GuiNovelDetails", "winWidth", winWidth)
180
+ options.setValue("GuiNovelDetails", "winHeight", winHeight)
181
+ options.setValue("GuiNovelDetails", "novelRoot", novelRoot)
182
+
183
+ self.contentsPage.saveSettings()
184
+
185
+ return
186
+
187
+ # END Class GuiNovelDetails
188
+
189
+
190
+ class _OverviewPage(NScrollablePage):
191
+
192
+ def __init__(self, parent: QWidget) -> None:
193
+ super().__init__(parent=parent)
194
+
195
+ mPx = CONFIG.pxInt(8)
196
+ sPx = CONFIG.pxInt(16)
197
+ hPx = CONFIG.pxInt(24)
198
+ vPx = CONFIG.pxInt(4)
199
+
200
+ # Project Info
201
+ self.projLabel = NColourLabel(
202
+ self.tr("Project"), SHARED.theme.helpText,
203
+ parent=self, scale=NColourLabel.HEADER_SCALE
204
+ )
205
+
206
+ self.projName = QLabel("", self)
207
+ self.projWords = QLabel("", self)
208
+ self.projNovels = QLabel("", self)
209
+ self.projNotes = QLabel("", self)
210
+ self.projRevisions = QLabel("", self)
211
+ self.projEditTime = QLabel("", self)
212
+
213
+ self.projForm = QFormLayout()
214
+ self.projForm.addRow("<b>{0}</b>".format(self.tr("Name")), self.projName)
215
+ self.projForm.addRow("<b>{0}</b>".format(self.tr("Revisions")), self.projRevisions)
216
+ self.projForm.addRow("<b>{0}</b>".format(self.tr("Editing Time")), self.projEditTime)
217
+ self.projForm.addRow("<b>{0}</b>".format(self.tr("Word Count")), self.projWords)
218
+ self.projForm.addRow("<b>\u2026 {0}</b>".format(self.tr("In Novels")), self.projNovels)
219
+ self.projForm.addRow("<b>\u2026 {0}</b>".format(self.tr("In Notes")), self.projNotes)
220
+ self.projForm.setContentsMargins(mPx, 0, 0, 0)
221
+ self.projForm.setHorizontalSpacing(hPx)
222
+ self.projForm.setVerticalSpacing(vPx)
223
+
224
+ # Novel Info
225
+ self.novelLabel = NColourLabel(
226
+ self.tr("Selected Novel"), SHARED.theme.helpText,
227
+ parent=self, scale=NColourLabel.HEADER_SCALE
228
+ )
229
+
230
+ self.novelName = QLabel("", self)
231
+ self.novelWords = QLabel("", self)
232
+ self.novelChapters = QLabel("", self)
233
+ self.novelScenes = QLabel("", self)
234
+
235
+ self.novelForm = QFormLayout()
236
+ self.novelForm.addRow("<b>{0}</b>".format(self.tr("Name")), self.novelName)
237
+ self.novelForm.addRow("<b>{0}</b>".format(self.tr("Word Count")), self.novelWords)
238
+ self.novelForm.addRow("<b>{0}</b>".format(self.tr("Chapters")), self.novelChapters)
239
+ self.novelForm.addRow("<b>{0}</b>".format(self.tr("Scenes")), self.novelScenes)
240
+ self.novelForm.setContentsMargins(mPx, 0, 0, 0)
241
+ self.novelForm.setHorizontalSpacing(hPx)
242
+ self.novelForm.setVerticalSpacing(vPx)
243
+
244
+ # Assemble
245
+ self.outerBox = QVBoxLayout()
246
+ self.outerBox.addWidget(self.projLabel)
247
+ self.outerBox.addLayout(self.projForm)
248
+ self.outerBox.addWidget(self.novelLabel)
249
+ self.outerBox.addLayout(self.novelForm)
250
+ self.outerBox.setSpacing(sPx)
251
+ self.outerBox.addStretch(1)
252
+
253
+ self.setCentralLayout(self.outerBox)
254
+
255
+ return
256
+
257
+ ##
258
+ # Methods
259
+ ##
260
+
261
+ def updateProjectData(self) -> None:
262
+ """Load information about the project."""
263
+ project = SHARED.project
264
+ project.updateWordCounts()
265
+ wcNovel, wcNotes = project.data.currCounts
266
+
267
+ self.projName.setText(project.data.name)
268
+ self.projRevisions.setText(f"{project.data.saveCount:n}")
269
+ self.projEditTime.setText(formatTime(project.currentEditTime))
270
+ self.projWords.setText(f"{wcNovel + wcNotes:n}")
271
+ self.projNovels.setText(f"{wcNovel:n}")
272
+ self.projNotes.setText(f"{wcNotes:n}")
273
+ return
274
+
275
+ ##
276
+ # Public Slots
277
+ ##
278
+
279
+ @pyqtSlot(str)
280
+ def novelValueChanged(self, tHandle: str) -> None:
281
+ """Refresh the data for the selected novel."""
282
+ project = SHARED.project
283
+ if nwItem := project.tree[tHandle]:
284
+ self.novelName.setText(nwItem.itemName)
285
+
286
+ nwCount = project.index.getNovelWordCount(rootHandle=tHandle)
287
+ self.novelWords.setText(f"{nwCount:n}")
288
+
289
+ hCounts = project.index.getNovelTitleCounts(rootHandle=tHandle)
290
+ self.novelChapters.setText(f"{hCounts[2]:n}")
291
+ self.novelScenes.setText(f"{hCounts[3]:n}")
292
+
293
+ return
294
+
295
+ # END Class _OverviewPage
296
+
297
+
298
+ class _ContentsPage(NFixedPage):
299
+
300
+ C_TITLE = 0
301
+ C_WORDS = 1
302
+ C_PAGES = 2
303
+ C_PAGE = 3
304
+ C_PROG = 4
305
+
306
+ def __init__(self, parent: QWidget) -> None:
307
+ super().__init__(parent=parent)
308
+
309
+ self._data = []
310
+ self._currentRoot = None
311
+
312
+ iPx = SHARED.theme.baseIconSize
313
+ hPx = CONFIG.pxInt(12)
314
+ vPx = CONFIG.pxInt(4)
315
+ options = SHARED.project.options
316
+
317
+ # Title
318
+ self.contentLabel = NColourLabel(
319
+ self.tr("Table of Contents"), SHARED.theme.helpText,
320
+ parent=self, scale=NColourLabel.HEADER_SCALE
321
+ )
322
+
323
+ # Contents Tree
324
+ self.tocTree = QTreeWidget(self)
325
+ self.tocTree.setIconSize(QSize(iPx, iPx))
326
+ self.tocTree.setIndentation(0)
327
+ self.tocTree.setColumnCount(6)
328
+ self.tocTree.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
329
+ self.tocTree.setHeaderLabels([
330
+ self.tr("Title"),
331
+ self.tr("Words"),
332
+ self.tr("Pages"),
333
+ self.tr("Page"),
334
+ self.tr("Progress"),
335
+ "",
336
+ ])
337
+
338
+ treeHeadItem = self.tocTree.headerItem()
339
+ if treeHeadItem:
340
+ treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignmentFlag.AlignRight)
341
+ treeHeadItem.setTextAlignment(self.C_PAGES, Qt.AlignmentFlag.AlignRight)
342
+ treeHeadItem.setTextAlignment(self.C_PAGE, Qt.AlignmentFlag.AlignRight)
343
+ treeHeadItem.setTextAlignment(self.C_PROG, Qt.AlignmentFlag.AlignRight)
344
+
345
+ treeHeader = self.tocTree.header()
346
+ treeHeader.setStretchLastSection(True)
347
+ treeHeader.setMinimumSectionSize(hPx)
348
+
349
+ wCol0 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol0", 200))
350
+ wCol1 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol1", 60))
351
+ wCol2 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol2", 60))
352
+ wCol3 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol3", 60))
353
+ wCol4 = CONFIG.pxInt(options.getInt("GuiNovelDetails", "widthCol4", 90))
354
+
355
+ self.tocTree.setColumnWidth(0, wCol0)
356
+ self.tocTree.setColumnWidth(1, wCol1)
357
+ self.tocTree.setColumnWidth(2, wCol2)
358
+ self.tocTree.setColumnWidth(3, wCol3)
359
+ self.tocTree.setColumnWidth(4, wCol4)
360
+ self.tocTree.setColumnWidth(5, hPx)
361
+
362
+ # Options
363
+ wordsPerPage = options.getInt("GuiNovelDetails", "wordsPerPage", 350)
364
+ countFrom = options.getInt("GuiNovelDetails", "countFrom", 1)
365
+ clearDouble = options.getBool("GuiNovelDetails", "clearDouble", True)
366
+
367
+ self.wpLabel = QLabel(self.tr("Words per page"))
368
+
369
+ self.wpValue = QSpinBox(self)
370
+ self.wpValue.setMinimum(10)
371
+ self.wpValue.setMaximum(1000)
372
+ self.wpValue.setSingleStep(10)
373
+ self.wpValue.setValue(wordsPerPage)
374
+ self.wpValue.valueChanged.connect(self._populateTree)
375
+
376
+ self.poLabel = QLabel(self.tr("First page offset"))
377
+
378
+ self.poValue = QSpinBox(self)
379
+ self.poValue.setMinimum(1)
380
+ self.poValue.setMaximum(9999)
381
+ self.poValue.setSingleStep(1)
382
+ self.poValue.setValue(countFrom)
383
+ self.poValue.valueChanged.connect(self._populateTree)
384
+
385
+ self.dblLabel = QLabel(self.tr("Chapters on odd pages"))
386
+
387
+ self.dblValue = NSwitch(self, height=iPx)
388
+ self.dblValue.setChecked(clearDouble)
389
+ self.dblValue.clicked.connect(self._populateTree)
390
+
391
+ self.optionsBox = QGridLayout()
392
+ self.optionsBox.addWidget(self.wpLabel, 0, 0)
393
+ self.optionsBox.addWidget(self.wpValue, 0, 1)
394
+ self.optionsBox.addWidget(self.dblLabel, 0, 3)
395
+ self.optionsBox.addWidget(self.dblValue, 0, 4)
396
+ self.optionsBox.addWidget(self.poLabel, 1, 0)
397
+ self.optionsBox.addWidget(self.poValue, 1, 1)
398
+ self.optionsBox.setHorizontalSpacing(hPx)
399
+ self.optionsBox.setVerticalSpacing(vPx)
400
+ self.optionsBox.setColumnStretch(2, 1)
401
+
402
+ # Assemble
403
+ self.outerBox = QVBoxLayout()
404
+ self.outerBox.addWidget(self.contentLabel)
405
+ self.outerBox.addWidget(self.tocTree)
406
+ self.outerBox.addLayout(self.optionsBox)
407
+
408
+ self.setCentralLayout(self.outerBox)
409
+
410
+ return
411
+
412
+ def saveSettings(self) -> None:
413
+ """Save the user GUI settings."""
414
+ widthCol0 = CONFIG.rpxInt(self.tocTree.columnWidth(0))
415
+ widthCol1 = CONFIG.rpxInt(self.tocTree.columnWidth(1))
416
+ widthCol2 = CONFIG.rpxInt(self.tocTree.columnWidth(2))
417
+ widthCol3 = CONFIG.rpxInt(self.tocTree.columnWidth(3))
418
+ widthCol4 = CONFIG.rpxInt(self.tocTree.columnWidth(4))
419
+
420
+ options = SHARED.project.options
421
+ options.setValue("GuiNovelDetails", "widthCol0", widthCol0)
422
+ options.setValue("GuiNovelDetails", "widthCol1", widthCol1)
423
+ options.setValue("GuiNovelDetails", "widthCol2", widthCol2)
424
+ options.setValue("GuiNovelDetails", "widthCol3", widthCol3)
425
+ options.setValue("GuiNovelDetails", "widthCol4", widthCol4)
426
+ options.setValue("GuiNovelDetails", "wordsPerPage", self.wpValue.value())
427
+ options.setValue("GuiNovelDetails", "countFrom", self.poValue.value())
428
+ options.setValue("GuiNovelDetails", "clearDouble", self.dblValue.isChecked())
429
+ return
430
+
431
+ ##
432
+ # Public Slots
433
+ ##
434
+
435
+ @pyqtSlot(str)
436
+ def novelValueChanged(self, tHandle: str) -> None:
437
+ """Refresh the tree with another root item."""
438
+ if tHandle != self._currentRoot:
439
+ self._prepareData(tHandle)
440
+ self._populateTree()
441
+ self._currentRoot = tHandle
442
+ return
443
+
444
+ ##
445
+ # Private Slots
446
+ ##
447
+
448
+ @pyqtSlot()
449
+ def _populateTree(self) -> None:
450
+ """Set the content of the chapter/page tree."""
451
+ dblPages = self.dblValue.isChecked()
452
+ wpPage = self.wpValue.value()
453
+ fstPage = self.poValue.value() - 1
454
+
455
+ pTotal = 0
456
+ tPages = 1
457
+
458
+ entries = []
459
+ for _, tLevel, tTitle, wCount in self._data:
460
+ pCount = math.ceil(wCount/wpPage)
461
+ if dblPages:
462
+ pCount += pCount%2
463
+
464
+ pTotal += pCount
465
+ entries.append((tLevel, tTitle, wCount, pCount))
466
+
467
+ pMax = pTotal - fstPage
468
+
469
+ self.tocTree.clear()
470
+ for tLevel, tTitle, wCount, pCount in entries:
471
+ newItem = QTreeWidgetItem()
472
+
473
+ if tPages <= fstPage:
474
+ progPage = numberToRoman(tPages, True)
475
+ progText = ""
476
+ else:
477
+ cPage = tPages - fstPage
478
+ pgProg = 100.0*(cPage - 1)/pMax if pMax > 0 else 0.0
479
+ progPage = f"{cPage:n}"
480
+ progText = f"{pgProg:.1f}{nwUnicode.U_THSP}%"
481
+
482
+ hDec = SHARED.theme.getHeaderDecoration(tLevel)
483
+ if tTitle.strip() == "":
484
+ tTitle = self.tr("Untitled")
485
+
486
+ newItem.setData(self.C_TITLE, Qt.DecorationRole, hDec)
487
+ newItem.setText(self.C_TITLE, tTitle)
488
+ newItem.setText(self.C_WORDS, f"{wCount:n}")
489
+ newItem.setText(self.C_PAGES, f"{pCount:n}")
490
+ newItem.setText(self.C_PAGE, progPage)
491
+ newItem.setText(self.C_PROG, progText)
492
+
493
+ newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
494
+ newItem.setTextAlignment(self.C_PAGES, Qt.AlignRight)
495
+ newItem.setTextAlignment(self.C_PAGE, Qt.AlignRight)
496
+ newItem.setTextAlignment(self.C_PROG, Qt.AlignRight)
497
+
498
+ # Make pages and titles/partitions stand out
499
+ if tLevel < 2:
500
+ bFont = newItem.font(self.C_TITLE)
501
+ if tLevel == 0:
502
+ bFont.setItalic(True)
503
+ else:
504
+ bFont.setBold(True)
505
+ bFont.setUnderline(True)
506
+ newItem.setFont(self.C_TITLE, bFont)
507
+
508
+ tPages += pCount
509
+
510
+ self.tocTree.addTopLevelItem(newItem)
511
+
512
+ return
513
+
514
+ ##
515
+ # Internal Functions
516
+ ##
517
+
518
+ def _prepareData(self, rootHandle: str | None) -> None:
519
+ """Extract the information from the project index."""
520
+ logger.debug("Populating ToC from handle '%s'", rootHandle)
521
+ self._data = SHARED.project.index.getTableOfContents(rootHandle, 2)
522
+ self._data.append(("", 0, self.tr("END"), 0))
523
+ return
524
+
525
+ # END Class _ContentsPage