novelWriter 2.2rc1__py3-none-any.whl → 2.3b1__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 (153) hide show
  1. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/RECORD +141 -129
  3. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +11 -6
  5. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  6. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  7. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  8. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  9. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  10. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  11. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  12. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  13. novelwriter/assets/i18n/project_de_DE.json +1 -0
  14. novelwriter/assets/i18n/project_en_US.json +1 -0
  15. novelwriter/assets/i18n/project_es_419.json +11 -0
  16. novelwriter/assets/i18n/project_fr_FR.json +11 -0
  17. novelwriter/assets/i18n/project_it_IT.json +11 -0
  18. novelwriter/assets/i18n/project_ja_JP.json +2 -1
  19. novelwriter/assets/i18n/project_nb_NO.json +1 -0
  20. novelwriter/assets/i18n/project_zh_CN.json +11 -0
  21. novelwriter/assets/icons/typicons_dark/icons.conf +9 -2
  22. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  23. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
  24. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
  25. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
  26. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
  27. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
  28. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
  29. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
  30. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
  31. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
  32. novelwriter/assets/icons/typicons_dark/typ_document-add-col.svg +8 -0
  33. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  34. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  35. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  36. novelwriter/assets/icons/typicons_light/icons.conf +9 -2
  37. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  38. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
  39. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
  40. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
  41. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
  42. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
  43. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
  44. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
  45. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
  46. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
  47. novelwriter/assets/icons/typicons_light/typ_document-add-col.svg +8 -0
  48. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  49. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  50. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  51. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  52. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  53. novelwriter/assets/images/welcome-dark.jpg +0 -0
  54. novelwriter/assets/images/welcome-light.jpg +0 -0
  55. novelwriter/assets/manual.pdf +0 -0
  56. novelwriter/assets/sample.zip +0 -0
  57. novelwriter/assets/syntax/default_dark.conf +1 -0
  58. novelwriter/assets/syntax/default_light.conf +1 -0
  59. novelwriter/assets/syntax/grey_dark.conf +1 -0
  60. novelwriter/assets/syntax/grey_light.conf +1 -0
  61. novelwriter/assets/syntax/light_owl.conf +1 -0
  62. novelwriter/assets/syntax/night_owl.conf +1 -0
  63. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  64. novelwriter/assets/syntax/solarized_light.conf +1 -0
  65. novelwriter/assets/syntax/tomorrow.conf +1 -0
  66. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  67. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  68. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  69. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  70. novelwriter/assets/text/credits_en.htm +4 -2
  71. novelwriter/assets/themes/default_dark.conf +2 -2
  72. novelwriter/assets/themes/default_light.conf +2 -2
  73. novelwriter/common.py +64 -66
  74. novelwriter/config.py +39 -44
  75. novelwriter/constants.py +39 -17
  76. novelwriter/core/buildsettings.py +8 -8
  77. novelwriter/core/coretools.py +194 -155
  78. novelwriter/core/docbuild.py +7 -4
  79. novelwriter/core/document.py +7 -7
  80. novelwriter/core/index.py +90 -57
  81. novelwriter/core/item.py +23 -5
  82. novelwriter/core/options.py +11 -10
  83. novelwriter/core/project.py +72 -47
  84. novelwriter/core/projectdata.py +3 -16
  85. novelwriter/core/projectxml.py +14 -42
  86. novelwriter/core/sessions.py +4 -3
  87. novelwriter/core/spellcheck.py +6 -4
  88. novelwriter/core/status.py +5 -4
  89. novelwriter/core/storage.py +179 -141
  90. novelwriter/core/tohtml.py +6 -4
  91. novelwriter/core/tokenizer.py +74 -46
  92. novelwriter/core/tomd.py +2 -2
  93. novelwriter/core/toodt.py +41 -31
  94. novelwriter/core/tree.py +5 -4
  95. novelwriter/dialogs/about.py +88 -179
  96. novelwriter/dialogs/docmerge.py +30 -20
  97. novelwriter/dialogs/docsplit.py +33 -22
  98. novelwriter/dialogs/editlabel.py +20 -8
  99. novelwriter/dialogs/preferences.py +562 -725
  100. novelwriter/dialogs/{projsettings.py → projectsettings.py} +301 -270
  101. novelwriter/dialogs/quotes.py +47 -36
  102. novelwriter/dialogs/wordlist.py +128 -59
  103. novelwriter/enum.py +25 -22
  104. novelwriter/error.py +2 -2
  105. novelwriter/extensions/circularprogress.py +12 -12
  106. novelwriter/extensions/configlayout.py +185 -146
  107. novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
  108. novelwriter/extensions/modified.py +81 -0
  109. novelwriter/extensions/novelselector.py +27 -13
  110. novelwriter/extensions/pagedsidebar.py +15 -20
  111. novelwriter/extensions/simpleprogress.py +8 -9
  112. novelwriter/extensions/statusled.py +9 -9
  113. novelwriter/extensions/switch.py +32 -64
  114. novelwriter/extensions/switchbox.py +2 -7
  115. novelwriter/extensions/versioninfo.py +153 -0
  116. novelwriter/gui/doceditor.py +250 -214
  117. novelwriter/gui/dochighlight.py +66 -94
  118. novelwriter/gui/docviewer.py +71 -98
  119. novelwriter/gui/docviewerpanel.py +140 -47
  120. novelwriter/gui/editordocument.py +3 -3
  121. novelwriter/gui/itemdetails.py +9 -9
  122. novelwriter/gui/mainmenu.py +47 -46
  123. novelwriter/gui/noveltree.py +53 -61
  124. novelwriter/gui/outline.py +100 -76
  125. novelwriter/gui/projtree.py +193 -67
  126. novelwriter/gui/sidebar.py +9 -8
  127. novelwriter/gui/statusbar.py +49 -7
  128. novelwriter/gui/theme.py +65 -74
  129. novelwriter/guimain.py +173 -330
  130. novelwriter/shared.py +68 -30
  131. novelwriter/tools/dictionaries.py +7 -8
  132. novelwriter/tools/lipsum.py +34 -28
  133. novelwriter/tools/manusbuild.py +3 -4
  134. novelwriter/tools/manuscript.py +25 -32
  135. novelwriter/tools/manussettings.py +194 -225
  136. novelwriter/tools/noveldetails.py +525 -0
  137. novelwriter/tools/welcome.py +802 -0
  138. novelwriter/tools/writingstats.py +26 -13
  139. novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
  140. novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
  141. novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
  142. novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
  143. novelwriter/assets/images/wizard-back.jpg +0 -0
  144. novelwriter/assets/text/gplv3_en.htm +0 -641
  145. novelwriter/assets/text/release_notes.htm +0 -17
  146. novelwriter/dialogs/projdetails.py +0 -525
  147. novelwriter/dialogs/projload.py +0 -298
  148. novelwriter/dialogs/updates.py +0 -182
  149. novelwriter/extensions/pageddialog.py +0 -130
  150. novelwriter/tools/projwizard.py +0 -478
  151. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/LICENSE.md +0 -0
  152. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/entry_points.txt +0 -0
  153. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/top_level.txt +0 -0
novelwriter/shared.py CHANGED
@@ -3,10 +3,11 @@ novelWriter – Shared Data Class
3
3
  ===============================
4
4
 
5
5
  File History:
6
- Created: 2023-08-10 [2.1rc1]
6
+ Created: 2023-08-10 [2.1rc1] SharedData
7
+ Created: 2023-08-14 [2.1rc1] _GuiAlert
7
8
 
8
9
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
10
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
11
 
11
12
  This program is free software: you can redistribute it and/or modify
12
13
  it under the terms of the GNU General Public License as published by
@@ -26,12 +27,14 @@ from __future__ import annotations
26
27
  import logging
27
28
 
28
29
  from time import time
29
- from typing import TYPE_CHECKING
30
+ from typing import TYPE_CHECKING, TypeVar
30
31
  from pathlib import Path
31
32
 
32
33
  from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal
33
- from PyQt5.QtWidgets import QMessageBox, QWidget
34
+ from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget
35
+ from novelwriter.common import formatFileFilter
34
36
 
37
+ from novelwriter.constants import nwFiles
35
38
  from novelwriter.core.spellcheck import NWSpellEnchant
36
39
 
37
40
  if TYPE_CHECKING: # pragma: no cover
@@ -41,11 +44,13 @@ if TYPE_CHECKING: # pragma: no cover
41
44
 
42
45
  logger = logging.getLogger(__name__)
43
46
 
47
+ NWWidget = TypeVar("NWWidget", bound=QWidget)
48
+
44
49
 
45
50
  class SharedData(QObject):
46
51
 
47
52
  __slots__ = (
48
- "_gui", "_theme", "_project", "_spelling", "_lockedBy", "_alert",
53
+ "_gui", "_theme", "_project", "_spelling", "_lockedBy", "_lastAlert",
49
54
  "_idleTime", "_idleRefTime",
50
55
  )
51
56
 
@@ -68,7 +73,7 @@ class SharedData(QObject):
68
73
 
69
74
  # Settings
70
75
  self._lockedBy = None
71
- self._alert = None
76
+ self._lastAlert = ""
72
77
  self._idleTime = 0.0
73
78
  self._idleRefTime = time()
74
79
 
@@ -122,9 +127,9 @@ class SharedData(QObject):
122
127
  return self._idleTime
123
128
 
124
129
  @property
125
- def alert(self) -> _GuiAlert | None:
126
- """Return a pointer to the last alert box."""
127
- return self._alert
130
+ def lastAlert(self) -> str:
131
+ """Return the last alert message."""
132
+ return self._lastAlert
128
133
 
129
134
  ##
130
135
  # Methods
@@ -214,6 +219,25 @@ class SharedData(QObject):
214
219
  QThreadPool.globalInstance().start(runnable, priority=priority)
215
220
  return
216
221
 
222
+ def getProjectPath(self, parent: QWidget, path: str | Path | None = None,
223
+ allowZip: bool = False) -> Path | None:
224
+ """Open the file dialog and select a novelWriter project file."""
225
+ label = (self.tr("novelWriter Project File or Zip File")
226
+ if allowZip else self.tr("novelWriter Project File"))
227
+ ext = f"{nwFiles.PROJ_FILE} *.zip" if allowZip else nwFiles.PROJ_FILE
228
+ ffilter = formatFileFilter([(label, ext), "*"])
229
+ selected, _ = QFileDialog.getOpenFileName(
230
+ parent, self.tr("Open Project"), str(path or ""), filter=ffilter
231
+ )
232
+ return Path(selected) if selected else None
233
+
234
+ def findTopLevelWidget(self, kind: type[NWWidget]) -> NWWidget | None:
235
+ """Find a top level widget."""
236
+ for widget in self.mainGui.children():
237
+ if isinstance(widget, kind):
238
+ return widget
239
+ return None
240
+
217
241
  ##
218
242
  # Signal Proxy
219
243
  ##
@@ -238,44 +262,53 @@ class SharedData(QObject):
238
262
 
239
263
  def info(self, text: str, info: str = "", details: str = "", log: bool = True) -> None:
240
264
  """Open an information alert box."""
241
- self._alert = _GuiAlert(self.mainGui, self.theme)
242
- self._alert.setMessage(text, info, details)
243
- self._alert.setAlertType(_GuiAlert.INFO, False)
265
+ alert = _GuiAlert(self.mainGui, self.theme)
266
+ alert.setMessage(text, info, details)
267
+ alert.setAlertType(_GuiAlert.INFO, False)
268
+ self._lastAlert = alert.logMessage
244
269
  if log:
245
- logger.info(self._alert.logMessage, stacklevel=2)
246
- self._alert.exec_()
270
+ logger.info(self._lastAlert, stacklevel=2)
271
+ alert.exec_()
272
+ alert.deleteLater()
247
273
  return
248
274
 
249
275
  def warn(self, text: str, info: str = "", details: str = "", log: bool = True) -> None:
250
276
  """Open a warning alert box."""
251
- self._alert = _GuiAlert(self.mainGui, self.theme)
252
- self._alert.setMessage(text, info, details)
253
- self._alert.setAlertType(_GuiAlert.WARN, False)
277
+ alert = _GuiAlert(self.mainGui, self.theme)
278
+ alert.setMessage(text, info, details)
279
+ alert.setAlertType(_GuiAlert.WARN, False)
280
+ self._lastAlert = alert.logMessage
254
281
  if log:
255
- logger.warning(self._alert.logMessage, stacklevel=2)
256
- self._alert.exec_()
282
+ logger.warning(self._lastAlert, stacklevel=2)
283
+ alert.exec_()
284
+ alert.deleteLater()
257
285
  return
258
286
 
259
287
  def error(self, text: str, info: str = "", details: str = "", log: bool = True,
260
288
  exc: Exception | None = None) -> None:
261
289
  """Open an error alert box."""
262
- self._alert = _GuiAlert(self.mainGui, self.theme)
263
- self._alert.setMessage(text, info, details)
264
- self._alert.setAlertType(_GuiAlert.ERROR, False)
290
+ alert = _GuiAlert(self.mainGui, self.theme)
291
+ alert.setMessage(text, info, details)
292
+ alert.setAlertType(_GuiAlert.ERROR, False)
265
293
  if exc:
266
- self._alert.setException(exc)
294
+ alert.setException(exc)
295
+ self._lastAlert = alert.logMessage
267
296
  if log:
268
- logger.error(self._alert.logMessage, stacklevel=2)
269
- self._alert.exec_()
297
+ logger.error(self._lastAlert, stacklevel=2)
298
+ alert.exec_()
299
+ alert.deleteLater()
270
300
  return
271
301
 
272
302
  def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
273
303
  """Open a question box."""
274
- self._alert = _GuiAlert(self.mainGui, self.theme)
275
- self._alert.setMessage(text, info, details)
276
- self._alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
277
- self._alert.exec_()
278
- return self._alert.result() == QMessageBox.Yes
304
+ alert = _GuiAlert(self.mainGui, self.theme)
305
+ alert.setMessage(text, info, details)
306
+ alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
307
+ self._lastAlert = alert.logMessage
308
+ alert.exec_()
309
+ isYes = alert.result() == QMessageBox.StandardButton.Yes
310
+ alert.deleteLater()
311
+ return isYes
279
312
 
280
313
  ##
281
314
  # Internal Functions
@@ -312,6 +345,11 @@ class _GuiAlert(QMessageBox):
312
345
  super().__init__(parent=parent)
313
346
  self._theme = theme
314
347
  self._message = ""
348
+ logger.debug("Ready: _GuiAlert")
349
+ return
350
+
351
+ def __del__(self) -> None: # pragma: no cover
352
+ logger.debug("Delete: _GuiAlert")
315
353
  return
316
354
 
317
355
  @property
@@ -3,10 +3,10 @@ novelWriter – GUI Dictionary Downloader
3
3
  =======================================
4
4
 
5
5
  File History:
6
- Created: 2023-11-19 [2.2rc1]
6
+ Created: 2023-11-19 [2.2rc1] GuiDictionaries
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
37
37
 
38
38
  from novelwriter import CONFIG, SHARED
39
39
  from novelwriter.error import formatException
40
- from novelwriter.common import openExternalPath, formatInt, getFileSize
40
+ from novelwriter.common import formatFileFilter, openExternalPath, formatInt, getFileSize
41
41
 
42
42
  logger = logging.getLogger(__name__)
43
43
 
@@ -180,12 +180,11 @@ class GuiDictionaries(QDialog):
180
180
  @pyqtSlot()
181
181
  def _doBrowseHunspell(self):
182
182
  """Browse for a Free/Libre Office dictionary."""
183
- extFilter = [
184
- self.tr("Free or Libre Office extension ({0})").format("*.sox *.oxt"),
185
- self.tr("All files ({0})").format("*"),
186
- ]
183
+ ffilter = formatFileFilter([
184
+ (self.tr("Free or Libre Office extension"), "*.sox *.oxt"), "*"
185
+ ])
187
186
  soxFile, _ = QFileDialog.getOpenFileName(
188
- self, self.tr("Browse Files"), "", filter=";;".join(extFilter)
187
+ self, self.tr("Browse Files"), "", filter=ffilter
189
188
  )
190
189
  if soxFile:
191
190
  path = Path(soxFile).absolute()
@@ -3,10 +3,10 @@ novelWriter – Lorem Ipsum Tool
3
3
  ==============================
4
4
 
5
5
  File History:
6
- Created: 2022-04-02 [2.0rc1]
6
+ Created: 2022-04-02 [2.0rc1] GuiLipsum
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -26,10 +26,10 @@ from __future__ import annotations
26
26
  import random
27
27
  import logging
28
28
 
29
- from PyQt5.QtCore import Qt
29
+ from PyQt5.QtCore import Qt, pyqtSlot
30
30
  from PyQt5.QtWidgets import (
31
- QDialog, QGridLayout, QHBoxLayout, QVBoxLayout, QLabel, QDialogButtonBox,
32
- QSpinBox
31
+ QDialog, QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, QSpinBox,
32
+ QVBoxLayout, QWidget
33
33
  )
34
34
 
35
35
  from novelwriter import CONFIG, SHARED
@@ -41,15 +41,15 @@ logger = logging.getLogger(__name__)
41
41
 
42
42
  class GuiLipsum(QDialog):
43
43
 
44
- def __init__(self, mainGui):
45
- super().__init__(parent=mainGui)
44
+ def __init__(self, parent: QWidget) -> None:
45
+ super().__init__(parent=parent)
46
46
 
47
47
  logger.debug("Create: GuiLipsum")
48
48
  self.setObjectName("GuiLipsum")
49
49
  if CONFIG.osDarwin:
50
50
  self.setWindowFlag(Qt.WindowType.Tool)
51
51
 
52
- self.mainGui = mainGui
52
+ self._lipsumText = ""
53
53
 
54
54
  self.setWindowTitle(self.tr("Insert Placeholder Text"))
55
55
 
@@ -92,14 +92,16 @@ class GuiLipsum(QDialog):
92
92
 
93
93
  # Buttons
94
94
  self.buttonBox = QDialogButtonBox()
95
- self.buttonBox.rejected.connect(self._doClose)
95
+ self.buttonBox.rejected.connect(self.close)
96
96
 
97
97
  self.btnClose = self.buttonBox.addButton(QDialogButtonBox.Close)
98
98
  self.btnClose.setAutoDefault(False)
99
99
 
100
- self.btnSave = self.buttonBox.addButton(self.tr("Insert"), QDialogButtonBox.ActionRole)
101
- self.btnSave.clicked.connect(self._doInsert)
102
- self.btnSave.setAutoDefault(False)
100
+ self.btnInsert = self.buttonBox.addButton(self.tr("Insert"), QDialogButtonBox.ActionRole)
101
+ self.btnInsert.clicked.connect(self._doInsert)
102
+ self.btnInsert.setAutoDefault(False)
103
+
104
+ self.rejected.connect(self.close)
103
105
 
104
106
  # Assemble
105
107
  self.outerBox = QVBoxLayout()
@@ -112,33 +114,37 @@ class GuiLipsum(QDialog):
112
114
 
113
115
  return
114
116
 
115
- def __del__(self): # pragma: no cover
117
+ def __del__(self) -> None: # pragma: no cover
116
118
  logger.debug("Delete: GuiLipsum")
117
119
  return
118
120
 
121
+ @property
122
+ def lipsumText(self) -> str:
123
+ """Return the generated text."""
124
+ return self._lipsumText
125
+
126
+ @classmethod
127
+ def getLipsum(cls, parent: QWidget) -> str:
128
+ """Pop the dialog and return the lipsum text."""
129
+ cls = GuiLipsum(parent)
130
+ cls.exec_()
131
+ text = cls.lipsumText
132
+ cls.deleteLater()
133
+ return text
134
+
119
135
  ##
120
- # Slots
136
+ # Private Slots
121
137
  ##
122
138
 
123
- def _doInsert(self):
124
- """Load the text and insert it in the open document.
125
- """
139
+ @pyqtSlot()
140
+ def _doInsert(self) -> None:
141
+ """Generate the text."""
126
142
  lipsumFile = CONFIG.assetPath("text") / "lipsum.txt"
127
143
  lipsumText = readTextFile(lipsumFile).splitlines()
128
-
129
144
  if self.randSwitch.isChecked():
130
145
  random.shuffle(lipsumText)
131
-
132
146
  pCount = self.paraCount.value()
133
- inText = "\n\n".join(lipsumText[0:pCount]) + "\n\n"
134
-
135
- self.mainGui.docEditor.insertText(inText)
136
-
137
- return
138
-
139
- def _doClose(self):
140
- """Close the dialog window without doing anything.
141
- """
147
+ self._lipsumText = "\n\n".join(lipsumText[0:pCount]) + "\n\n"
142
148
  self.close()
143
149
  return
144
150
 
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2023-05-24 [2.1b1] GuiManuscriptBuild
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -331,7 +331,7 @@ class GuiManuscriptBuild(QDialog):
331
331
  self._build.setLastBuildName(bName)
332
332
  self._build.setLastFormat(bFormat)
333
333
 
334
- QTimer.singleShot(1000, self._resetProgress)
334
+ QTimer.singleShot(3000, self._resetProgress)
335
335
 
336
336
  return True
337
337
 
@@ -344,8 +344,6 @@ class GuiManuscriptBuild(QDialog):
344
344
 
345
345
  def _saveSettings(self):
346
346
  """Save the user GUI settings."""
347
- logger.debug("Saving GuiManuscriptBuild settings")
348
-
349
347
  winWidth = CONFIG.rpxInt(self.width())
350
348
  winHeight = CONFIG.rpxInt(self.height())
351
349
 
@@ -353,6 +351,7 @@ class GuiManuscriptBuild(QDialog):
353
351
  fmtWidth = CONFIG.rpxInt(mainSplit[0])
354
352
  sumWidth = CONFIG.rpxInt(mainSplit[1])
355
353
 
354
+ logger.debug("Saving State: GuiManuscriptBuild")
356
355
  pOptions = SHARED.project.options
357
356
  pOptions.setValue("GuiManuscriptBuild", "winWidth", winWidth)
358
357
  pOptions.setValue("GuiManuscriptBuild", "winHeight", winHeight)
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2023-05-13 [2.1b1] GuiManuscript
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -66,7 +66,7 @@ class GuiManuscript(QDialog):
66
66
 
67
67
  D_KEY = Qt.ItemDataRole.UserRole
68
68
 
69
- def __init__(self, mainGui: GuiMain):
69
+ def __init__(self, mainGui: GuiMain) -> None:
70
70
  super().__init__(parent=mainGui)
71
71
 
72
72
  logger.debug("Create: GuiManuscript")
@@ -173,7 +173,7 @@ class GuiManuscript(QDialog):
173
173
  self.btnBuild.clicked.connect(self._buildManuscript)
174
174
 
175
175
  self.btnClose = QPushButton(self.tr("Close"))
176
- self.btnClose.clicked.connect(self._doClose)
176
+ self.btnClose.clicked.connect(self.close)
177
177
 
178
178
  self.processBox = QGridLayout()
179
179
  self.processBox.addWidget(self.btnPreview, 0, 0)
@@ -217,11 +217,11 @@ class GuiManuscript(QDialog):
217
217
 
218
218
  return
219
219
 
220
- def __del__(self): # pragma: no cover
220
+ def __del__(self) -> None: # pragma: no cover
221
221
  logger.debug("Delete: GuiManuscript")
222
222
  return
223
223
 
224
- def loadContent(self):
224
+ def loadContent(self) -> None:
225
225
  """Load dialog content from project data."""
226
226
  if len(self._builds) == 0:
227
227
  build = BuildSettings()
@@ -255,7 +255,7 @@ class GuiManuscript(QDialog):
255
255
  # Events
256
256
  ##
257
257
 
258
- def closeEvent(self, event: QCloseEvent):
258
+ def closeEvent(self, event: QCloseEvent) -> None:
259
259
  """Capture the user closing the window so we can save GUI
260
260
  settings. We also check that we don't have a build settings
261
261
  dialog open.
@@ -274,7 +274,7 @@ class GuiManuscript(QDialog):
274
274
  ##
275
275
 
276
276
  @pyqtSlot()
277
- def _createNewBuild(self):
277
+ def _createNewBuild(self) -> None:
278
278
  """Open the build settings dialog for a new build."""
279
279
  build = BuildSettings()
280
280
  build.setName(self.tr("My Manuscript"))
@@ -282,7 +282,7 @@ class GuiManuscript(QDialog):
282
282
  return
283
283
 
284
284
  @pyqtSlot()
285
- def _editSelectedBuild(self):
285
+ def _editSelectedBuild(self) -> None:
286
286
  """Edit the currently selected build settings entry."""
287
287
  build = self._getSelectedBuild()
288
288
  if build is not None:
@@ -299,7 +299,7 @@ class GuiManuscript(QDialog):
299
299
  return
300
300
 
301
301
  @pyqtSlot()
302
- def _deleteSelectedBuild(self):
302
+ def _deleteSelectedBuild(self) -> None:
303
303
  """Delete the currently selected build settings entry."""
304
304
  build = self._getSelectedBuild()
305
305
  if build is not None:
@@ -309,7 +309,7 @@ class GuiManuscript(QDialog):
309
309
  return
310
310
 
311
311
  @pyqtSlot(BuildSettings)
312
- def _processNewSettings(self, build: BuildSettings):
312
+ def _processNewSettings(self, build: BuildSettings) -> None:
313
313
  """Process new build settings from the settings dialog."""
314
314
  self._builds.setBuild(build)
315
315
  self._updateBuildItem(build)
@@ -319,7 +319,7 @@ class GuiManuscript(QDialog):
319
319
  return
320
320
 
321
321
  @pyqtSlot()
322
- def _generatePreview(self):
322
+ def _generatePreview(self) -> None:
323
323
  """Run the document builder on the current build settings for
324
324
  the preview widget.
325
325
  """
@@ -359,7 +359,7 @@ class GuiManuscript(QDialog):
359
359
  return
360
360
 
361
361
  @pyqtSlot()
362
- def _buildManuscript(self):
362
+ def _buildManuscript(self) -> None:
363
363
  """Open the build dialog and build the manuscript."""
364
364
  build = self._getSelectedBuild()
365
365
  if isinstance(build, BuildSettings):
@@ -373,24 +373,18 @@ class GuiManuscript(QDialog):
373
373
  return
374
374
 
375
375
  @pyqtSlot()
376
- def _printDocument(self):
376
+ def _printDocument(self) -> None:
377
377
  """Open the print preview dialog."""
378
- thePreview = QPrintPreviewDialog(self)
379
- thePreview.paintRequested.connect(self.docPreview.printPreview)
380
- thePreview.exec_()
381
- return
382
-
383
- @pyqtSlot()
384
- def _doClose(self):
385
- """Forward the close button to the default close method."""
386
- self.close()
378
+ preview = QPrintPreviewDialog(self)
379
+ preview.paintRequested.connect(self.docPreview.printPreview)
380
+ preview.exec_()
387
381
  return
388
382
 
389
383
  ##
390
384
  # Internal Functions
391
385
  ##
392
386
 
393
- def _updatePreview(self, data: dict, build: BuildSettings):
387
+ def _updatePreview(self, data: dict, build: BuildSettings) -> None:
394
388
  """Update the preview widget and set relevant values."""
395
389
  self.docPreview.setContent(data)
396
390
  self.docPreview.setBuildName(build.name)
@@ -415,10 +409,8 @@ class GuiManuscript(QDialog):
415
409
  return build
416
410
  return None
417
411
 
418
- def _saveSettings(self):
412
+ def _saveSettings(self) -> None:
419
413
  """Save the user GUI settings."""
420
- logger.debug("Saving GuiManuscript settings")
421
-
422
414
  buildOrder = []
423
415
  for i in range(self.buildList.count()):
424
416
  if item := self.buildList.item(i):
@@ -442,6 +434,7 @@ class GuiManuscript(QDialog):
442
434
  detailsWidth = CONFIG.rpxInt(self.buildDetails.getColumnWidth())
443
435
  detailsExpanded = self.buildDetails.getExpandedState()
444
436
 
437
+ logger.debug("Saving State: GuiManuscript")
445
438
  pOptions = SHARED.project.options
446
439
  pOptions.setValue("GuiManuscript", "winWidth", winWidth)
447
440
  pOptions.setValue("GuiManuscript", "winHeight", winHeight)
@@ -455,7 +448,7 @@ class GuiManuscript(QDialog):
455
448
 
456
449
  return
457
450
 
458
- def _openSettingsDialog(self, build: BuildSettings):
451
+ def _openSettingsDialog(self, build: BuildSettings) -> None:
459
452
  """Open the build settings dialog."""
460
453
  for obj in self.mainGui.children():
461
454
  # Don't open a second dialog if one exists
@@ -476,7 +469,7 @@ class GuiManuscript(QDialog):
476
469
 
477
470
  return
478
471
 
479
- def _updateBuildsList(self):
472
+ def _updateBuildsList(self) -> None:
480
473
  """Update the list of available builds."""
481
474
  self.buildList.clear()
482
475
  for key, name in self._builds.builds():
@@ -488,7 +481,7 @@ class GuiManuscript(QDialog):
488
481
  self._buildMap[key] = bItem
489
482
  return
490
483
 
491
- def _updateBuildItem(self, build: BuildSettings):
484
+ def _updateBuildItem(self, build: BuildSettings) -> None:
492
485
  """Update the entry of a specific build item."""
493
486
  bItem = self._buildMap.get(build.buildID, None)
494
487
  if isinstance(bItem, QListWidgetItem):
@@ -611,7 +604,7 @@ class _DetailsWidget(QWidget):
611
604
  for key in entries:
612
605
  sub = QTreeWidgetItem()
613
606
  sub.setText(0, build.getLabel(key))
614
- sub.setText(1, hFmt.apply(build.getStr(key), title))
607
+ sub.setText(1, hFmt.apply(build.getStr(key), title, 0))
615
608
  item.addChild(sub)
616
609
  for key in ["headings.hideScene", "headings.hideSection"]:
617
610
  sub = QTreeWidgetItem()
@@ -778,8 +771,8 @@ class _PreviewWidget(QTextBrowser):
778
771
  self.setHtml(html)
779
772
  qApp.processEvents()
780
773
  while self.find("!!tab!!"):
781
- theCursor = self.textCursor()
782
- theCursor.insertText("\t")
774
+ cursor = self.textCursor()
775
+ cursor.insertText("\t")
783
776
 
784
777
  self.verticalScrollBar().setValue(sPos)
785
778
  self._docTime = checkInt(data.get("time"), 0)