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.
Files changed (107) hide show
  1. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/METADATA +5 -6
  2. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/RECORD +102 -95
  3. novelwriter/__init__.py +7 -7
  4. novelwriter/assets/icons/none.svg +4 -0
  5. novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
  6. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
  7. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
  8. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
  9. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
  10. novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
  11. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
  12. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
  13. novelwriter/assets/icons/typicons_light/icons.conf +4 -0
  14. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
  15. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
  16. novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
  17. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
  18. novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
  19. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
  20. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
  21. novelwriter/assets/manual.pdf +0 -0
  22. novelwriter/assets/sample.zip +0 -0
  23. novelwriter/assets/syntax/default_dark.conf +1 -0
  24. novelwriter/assets/syntax/default_light.conf +1 -0
  25. novelwriter/assets/syntax/grey_dark.conf +1 -0
  26. novelwriter/assets/syntax/grey_light.conf +1 -0
  27. novelwriter/assets/syntax/light_owl.conf +1 -0
  28. novelwriter/assets/syntax/night_owl.conf +1 -0
  29. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  30. novelwriter/assets/syntax/solarized_light.conf +1 -0
  31. novelwriter/assets/syntax/tomorrow.conf +1 -0
  32. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  33. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  34. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  35. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  36. novelwriter/assets/text/credits_en.htm +25 -23
  37. novelwriter/common.py +7 -2
  38. novelwriter/config.py +43 -16
  39. novelwriter/constants.py +5 -6
  40. novelwriter/core/buildsettings.py +60 -40
  41. novelwriter/core/coretools.py +97 -13
  42. novelwriter/core/docbuild.py +74 -7
  43. novelwriter/core/document.py +24 -3
  44. novelwriter/core/index.py +31 -112
  45. novelwriter/core/project.py +10 -15
  46. novelwriter/core/sessions.py +2 -2
  47. novelwriter/core/status.py +6 -5
  48. novelwriter/core/storage.py +8 -2
  49. novelwriter/core/tohtml.py +22 -25
  50. novelwriter/core/tokenizer.py +416 -232
  51. novelwriter/core/tomd.py +17 -8
  52. novelwriter/core/toodt.py +385 -350
  53. novelwriter/core/tree.py +8 -8
  54. novelwriter/dialogs/about.py +9 -11
  55. novelwriter/dialogs/docmerge.py +17 -14
  56. novelwriter/dialogs/docsplit.py +20 -19
  57. novelwriter/dialogs/editlabel.py +5 -4
  58. novelwriter/dialogs/preferences.py +31 -39
  59. novelwriter/dialogs/projectsettings.py +29 -26
  60. novelwriter/dialogs/quotes.py +10 -9
  61. novelwriter/dialogs/wordlist.py +15 -12
  62. novelwriter/enum.py +17 -14
  63. novelwriter/error.py +13 -11
  64. novelwriter/extensions/circularprogress.py +12 -8
  65. novelwriter/extensions/configlayout.py +1 -3
  66. novelwriter/extensions/modified.py +51 -2
  67. novelwriter/extensions/pagedsidebar.py +16 -14
  68. novelwriter/extensions/simpleprogress.py +3 -1
  69. novelwriter/extensions/statusled.py +3 -1
  70. novelwriter/extensions/switch.py +10 -9
  71. novelwriter/extensions/switchbox.py +14 -13
  72. novelwriter/extensions/versioninfo.py +1 -1
  73. novelwriter/gui/doceditor.py +413 -478
  74. novelwriter/gui/dochighlight.py +33 -29
  75. novelwriter/gui/docviewer.py +162 -175
  76. novelwriter/gui/docviewerpanel.py +20 -37
  77. novelwriter/gui/editordocument.py +15 -4
  78. novelwriter/gui/itemdetails.py +51 -54
  79. novelwriter/gui/mainmenu.py +37 -16
  80. novelwriter/gui/noveltree.py +30 -36
  81. novelwriter/gui/outline.py +114 -92
  82. novelwriter/gui/projtree.py +60 -66
  83. novelwriter/gui/search.py +362 -0
  84. novelwriter/gui/sidebar.py +36 -45
  85. novelwriter/gui/statusbar.py +14 -14
  86. novelwriter/gui/theme.py +93 -28
  87. novelwriter/guimain.py +207 -200
  88. novelwriter/shared.py +31 -6
  89. novelwriter/text/counting.py +137 -0
  90. novelwriter/tools/dictionaries.py +13 -12
  91. novelwriter/tools/lipsum.py +20 -17
  92. novelwriter/tools/manusbuild.py +35 -27
  93. novelwriter/tools/manuscript.py +374 -90
  94. novelwriter/tools/manussettings.py +261 -124
  95. novelwriter/tools/noveldetails.py +20 -18
  96. novelwriter/tools/welcome.py +48 -44
  97. novelwriter/tools/writingstats.py +61 -55
  98. novelwriter/types.py +90 -0
  99. novelwriter/core/__init__.py +0 -3
  100. novelwriter/dialogs/__init__.py +0 -3
  101. novelwriter/extensions/__init__.py +0 -3
  102. novelwriter/gui/__init__.py +0 -3
  103. novelwriter/tools/__init__.py +0 -3
  104. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/LICENSE.md +0 -0
  105. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/WHEEL +0 -0
  106. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/entry_points.txt +0 -0
  107. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/top_level.txt +0 -0
novelwriter/shared.py CHANGED
@@ -30,7 +30,7 @@ from time import time
30
30
  from typing import TYPE_CHECKING, TypeVar
31
31
  from pathlib import Path
32
32
 
33
- from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal
33
+ from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal
34
34
  from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget
35
35
  from novelwriter.common import formatFileFilter
36
36
 
@@ -57,10 +57,12 @@ class SharedData(QObject):
57
57
  projectStatusChanged = pyqtSignal(bool)
58
58
  projectStatusMessage = pyqtSignal(str)
59
59
  spellLanguageChanged = pyqtSignal(str, str)
60
+ focusModeChanged = pyqtSignal(bool)
60
61
  indexScannedText = pyqtSignal(str)
61
62
  indexChangedTags = pyqtSignal(list, list)
62
63
  indexCleared = pyqtSignal()
63
64
  indexAvailable = pyqtSignal()
65
+ mainClockTick = pyqtSignal()
64
66
 
65
67
  def __init__(self) -> None:
66
68
  super().__init__()
@@ -76,6 +78,11 @@ class SharedData(QObject):
76
78
  self._lastAlert = ""
77
79
  self._idleTime = 0.0
78
80
  self._idleRefTime = time()
81
+ self._focusMode = False
82
+
83
+ self._clock = QTimer(self)
84
+ self._clock.setInterval(1000)
85
+ self._clock.timeout.connect(lambda: self.mainClockTick.emit())
79
86
 
80
87
  return
81
88
 
@@ -111,6 +118,11 @@ class SharedData(QObject):
111
118
  raise Exception("SharedData class not fully initialised")
112
119
  return self._spelling
113
120
 
121
+ @property
122
+ def focusMode(self) -> bool:
123
+ """Return the Focus Mode state."""
124
+ return self._focusMode
125
+
114
126
  @property
115
127
  def hasProject(self) -> bool:
116
128
  """Return True if the project instance is populated."""
@@ -131,6 +143,17 @@ class SharedData(QObject):
131
143
  """Return the last alert message."""
132
144
  return self._lastAlert
133
145
 
146
+ ##
147
+ # Setters
148
+ ##
149
+
150
+ def setFocusMode(self, state: bool) -> None:
151
+ """Set focus mode on or off."""
152
+ if state is not self._focusMode:
153
+ self._focusMode = state
154
+ self.focusModeChanged.emit(state)
155
+ return
156
+
134
157
  ##
135
158
  # Methods
136
159
  ##
@@ -140,6 +163,7 @@ class SharedData(QObject):
140
163
  soon as the Main GUI is created to ensure the SHARED singleton
141
164
  has the properties needed for operation.
142
165
  """
166
+ self._clock.start()
143
167
  self._gui = gui
144
168
  self._theme = theme
145
169
  self._resetProject()
@@ -268,7 +292,7 @@ class SharedData(QObject):
268
292
  self._lastAlert = alert.logMessage
269
293
  if log:
270
294
  logger.info(self._lastAlert, stacklevel=2)
271
- alert.exec_()
295
+ alert.exec()
272
296
  alert.deleteLater()
273
297
  return
274
298
 
@@ -280,7 +304,7 @@ class SharedData(QObject):
280
304
  self._lastAlert = alert.logMessage
281
305
  if log:
282
306
  logger.warning(self._lastAlert, stacklevel=2)
283
- alert.exec_()
307
+ alert.exec()
284
308
  alert.deleteLater()
285
309
  return
286
310
 
@@ -295,7 +319,7 @@ class SharedData(QObject):
295
319
  self._lastAlert = alert.logMessage
296
320
  if log:
297
321
  logger.error(self._lastAlert, stacklevel=2)
298
- alert.exec_()
322
+ alert.exec()
299
323
  alert.deleteLater()
300
324
  return
301
325
 
@@ -305,7 +329,7 @@ class SharedData(QObject):
305
329
  alert.setMessage(text, info, details)
306
330
  alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
307
331
  self._lastAlert = alert.logMessage
308
- alert.exec_()
332
+ alert.exec()
309
333
  isYes = alert.result() == QMessageBox.StandardButton.Yes
310
334
  alert.deleteLater()
311
335
  return isYes
@@ -323,6 +347,7 @@ class SharedData(QObject):
323
347
  self._project = NWProject()
324
348
  self._spelling = NWSpellEnchant(self._project)
325
349
  self.updateSpellCheckLanguage()
350
+ self._focusMode = False
326
351
  return
327
352
 
328
353
  def _resetIdleTimer(self) -> None:
@@ -379,7 +404,7 @@ class _GuiAlert(QMessageBox):
379
404
  self.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
380
405
  else:
381
406
  self.setStandardButtons(QMessageBox.Ok)
382
- pSz = 2*self._theme.baseIconSize
407
+ pSz = 2*self._theme.baseIconHeight
383
408
  if level == self.INFO:
384
409
  self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz)))
385
410
  self.setWindowTitle(self.tr("Information"))
@@ -0,0 +1,137 @@
1
+ """
2
+ novelWriter – Text Counting Functions
3
+ =====================================
4
+
5
+ File History:
6
+ Created: 2019-04-22 [0.0.1] standardCounter
7
+ Rewritten: 2024-02-27 [2.4b1] preProcessText, standardCounter
8
+ Created: 2024-02-27 [2.4b1] bodyTextCounter
9
+
10
+ This file is a part of novelWriter
11
+ Copyright 2018–2024, Veronica Berglyd Olsen
12
+
13
+ This program is free software: you can redistribute it and/or modify
14
+ it under the terms of the GNU General Public License as published by
15
+ the Free Software Foundation, either version 3 of the License, or
16
+ (at your option) any later version.
17
+
18
+ This program is distributed in the hope that it will be useful, but
19
+ WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21
+ General Public License for more details.
22
+
23
+ You should have received a copy of the GNU General Public License
24
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import re
29
+
30
+ from novelwriter.constants import nwRegEx, nwUnicode
31
+
32
+ RX_SC = re.compile(nwRegEx.FMT_SC)
33
+ RX_LO = re.compile(r"(?i)(?<!\\)(\[(?:vspace|newpage|new page)(:\d+)?)(?<!\\)(\])")
34
+
35
+
36
+ def preProcessText(text: str, keepHeaders: bool = True) -> list[str]:
37
+ """Strip formatting codes from the text and split into lines."""
38
+ if not isinstance(text, str):
39
+ return []
40
+
41
+ # We need to treat dashes as word separators for counting words.
42
+ # The check+replace approach is much faster than direct replace for
43
+ # large texts, and a bit slower for small texts, but in the latter
44
+ # case it doesn't really matter.
45
+ if nwUnicode.U_ENDASH in text:
46
+ text = text.replace(nwUnicode.U_ENDASH, " ")
47
+ if nwUnicode.U_EMDASH in text:
48
+ text = text.replace(nwUnicode.U_EMDASH, " ")
49
+
50
+ ignore = "%@" if keepHeaders else "%@#"
51
+
52
+ result = []
53
+ for line in text.splitlines():
54
+ line = line.rstrip()
55
+ if line:
56
+ if line[0] in ignore:
57
+ continue
58
+ if line[0] == ">":
59
+ line = line.lstrip(">").lstrip(" ")
60
+ if line[-1] == "<":
61
+ line = line.rstrip("<").rstrip(" ")
62
+ if "[" in line:
63
+ # Strip shortcodes and special formatting
64
+ # RegEx is slow, so we do this only when necessary
65
+ line = RX_SC.sub("", line)
66
+ line = RX_LO.sub("", line)
67
+
68
+ result.append(line)
69
+
70
+ return result
71
+
72
+
73
+ def standardCounter(text: str) -> tuple[int, int, int]:
74
+ """A counter that counts paragraphs, words and characters.
75
+ This is the standard counter that includes headings in the word and
76
+ character counts.
77
+ """
78
+ cCount = 0
79
+ wCount = 0
80
+ pCount = 0
81
+ prevEmpty = True
82
+
83
+ for line in preProcessText(text):
84
+
85
+ countPara = True
86
+ if not line:
87
+ prevEmpty = True
88
+ continue
89
+
90
+ if line[0] == "#":
91
+ if line[:5] == "#### ":
92
+ line = line[5:]
93
+ countPara = False
94
+ elif line[:4] == "### ":
95
+ line = line[4:]
96
+ countPara = False
97
+ elif line[:3] == "## ":
98
+ line = line[3:]
99
+ countPara = False
100
+ elif line[:2] == "# ":
101
+ line = line[2:]
102
+ countPara = False
103
+ elif line[:3] == "#! ":
104
+ line = line[3:]
105
+ countPara = False
106
+ elif line[:4] == "##! ":
107
+ line = line[4:]
108
+ countPara = False
109
+ elif line[:5] == "###! ":
110
+ line = line[5:]
111
+ countPara = False
112
+
113
+ wCount += len(line.split())
114
+ cCount += len(line)
115
+ if countPara and prevEmpty:
116
+ pCount += 1
117
+
118
+ prevEmpty = not countPara
119
+
120
+ return cCount, wCount, pCount
121
+
122
+
123
+ def bodyTextCounter(text: str) -> tuple[int, int, int]:
124
+ """A counter that counts body text words, characters, and characters
125
+ without white spaces.
126
+ """
127
+ wCount = 0
128
+ cCount = 0
129
+ sCount = 0
130
+
131
+ for line in preProcessText(text, keepHeaders=False):
132
+ words = line.split()
133
+ wCount += len(words)
134
+ cCount += len(line)
135
+ sCount += len("".join(words))
136
+
137
+ return wCount, cCount, sCount
@@ -28,16 +28,18 @@ import logging
28
28
  from pathlib import Path
29
29
  from zipfile import ZipFile
30
30
 
31
- from PyQt5.QtGui import QCloseEvent, QTextCursor
32
31
  from PyQt5.QtCore import pyqtSlot
32
+ from PyQt5.QtGui import QCloseEvent, QTextCursor
33
33
  from PyQt5.QtWidgets import (
34
- QDialog, QDialogButtonBox, QFileDialog, QFrame, QHBoxLayout, QLabel,
35
- QLineEdit, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget, qApp
34
+ QApplication, QDialog, QDialogButtonBox, QFileDialog, QFrame, QHBoxLayout,
35
+ QLabel, QLineEdit, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget
36
36
  )
37
37
 
38
38
  from novelwriter import CONFIG, SHARED
39
- from novelwriter.error import formatException
40
39
  from novelwriter.common import formatFileFilter, openExternalPath, formatInt, getFileSize
40
+ from novelwriter.error import formatException
41
+ from novelwriter.extensions.modified import NIconToolButton
42
+ from novelwriter.types import QtDialogClose
41
43
 
42
44
  logger = logging.getLogger(__name__)
43
45
 
@@ -54,6 +56,7 @@ class GuiDictionaries(QDialog):
54
56
  self._installPath = None
55
57
  self._currDicts = set()
56
58
 
59
+ iSz = SHARED.theme.baseIconSize
57
60
  iPx = CONFIG.pxInt(4)
58
61
  mPx = CONFIG.pxInt(8)
59
62
  sPx = CONFIG.pxInt(16)
@@ -68,12 +71,11 @@ class GuiDictionaries(QDialog):
68
71
  self.tr("Download a dictionary from one of the links, and add it below."),
69
72
  f"&nbsp;\u203a <a href='{foUrl}'>{foUrl}</a>",
70
73
  f"&nbsp;\u203a <a href='{loUrl}'>{loUrl}</a>",
71
- ]))
74
+ ]), self)
72
75
  self.huInfo.setOpenExternalLinks(True)
73
76
  self.huInfo.setWordWrap(True)
74
77
  self.huInput = QLineEdit(self)
75
- self.huBrowse = QPushButton(self)
76
- self.huBrowse.setIcon(SHARED.theme.getIcon("browse"))
78
+ self.huBrowse = NIconToolButton(self, iSz, "browse")
77
79
  self.huBrowse.clicked.connect(self._doBrowseHunspell)
78
80
  self.huImport = QPushButton(self.tr("Add Dictionary"), self)
79
81
  self.huImport.setIcon(SHARED.theme.getIcon("add"))
@@ -88,11 +90,10 @@ class GuiDictionaries(QDialog):
88
90
  self.huAddBox.addWidget(self.huImport)
89
91
 
90
92
  # Install Path
91
- self.inInfo = QLabel(self.tr("Dictionary install location"))
93
+ self.inInfo = QLabel(self.tr("Dictionary install location"), self)
92
94
  self.inPath = QLineEdit(self)
93
95
  self.inPath.setReadOnly(True)
94
- self.inBrowse = QPushButton(self)
95
- self.inBrowse.setIcon(SHARED.theme.getIcon("browse"))
96
+ self.inBrowse = NIconToolButton(self, iSz, "browse")
96
97
  self.inBrowse.clicked.connect(self._doOpenInstallLocation)
97
98
 
98
99
  self.inBox = QHBoxLayout()
@@ -107,7 +108,7 @@ class GuiDictionaries(QDialog):
107
108
  self.infoBox.setFrameStyle(QFrame.Shape.NoFrame)
108
109
 
109
110
  # Buttons
110
- self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
111
+ self.buttonBox = QDialogButtonBox(QtDialogClose, self)
111
112
  self.buttonBox.rejected.connect(self._doClose)
112
113
 
113
114
  # Assemble
@@ -158,7 +159,7 @@ class GuiDictionaries(QDialog):
158
159
  "Additional dictionaries found: {0}"
159
160
  ).format(len(self._currDicts)))
160
161
 
161
- qApp.processEvents()
162
+ QApplication.processEvents()
162
163
  self.adjustSize()
163
164
 
164
165
  return True
@@ -35,6 +35,7 @@ from PyQt5.QtWidgets import (
35
35
  from novelwriter import CONFIG, SHARED
36
36
  from novelwriter.common import readTextFile
37
37
  from novelwriter.extensions.switch import NSwitch
38
+ from novelwriter.types import QtAlignLeft, QtAlignRight, QtRoleAction, QtDialogClose
38
39
 
39
40
  logger = logging.getLogger(__name__)
40
41
 
@@ -53,13 +54,14 @@ class GuiLipsum(QDialog):
53
54
 
54
55
  self.setWindowTitle(self.tr("Insert Placeholder Text"))
55
56
 
57
+ vSp = CONFIG.pxInt(4)
58
+ nPx = CONFIG.pxInt(64)
59
+
56
60
  self.innerBox = QHBoxLayout()
57
61
  self.innerBox.setSpacing(CONFIG.pxInt(16))
58
62
 
59
63
  # Icon
60
- nPx = CONFIG.pxInt(64)
61
- vSp = CONFIG.pxInt(4)
62
- self.docIcon = QLabel()
64
+ self.docIcon = QLabel(self)
63
65
  self.docIcon.setPixmap(SHARED.theme.getPixmap("proj_document", (nPx, nPx)))
64
66
 
65
67
  self.leftBox = QVBoxLayout()
@@ -69,35 +71,36 @@ class GuiLipsum(QDialog):
69
71
  self.innerBox.addLayout(self.leftBox)
70
72
 
71
73
  # Form
72
- self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Insert Lorem Ipsum Text")))
74
+ self.headLabel = QLabel(self.tr("Insert Lorem Ipsum Text"))
75
+ self.headLabel.setFont(SHARED.theme.guiFontB)
73
76
 
74
- self.paraLabel = QLabel(self.tr("Number of paragraphs"))
75
- self.paraCount = QSpinBox()
77
+ self.paraLabel = QLabel(self.tr("Number of paragraphs"), self)
78
+ self.paraCount = QSpinBox(self)
76
79
  self.paraCount.setMinimum(1)
77
80
  self.paraCount.setMaximum(100)
78
81
  self.paraCount.setValue(5)
79
82
 
80
- self.randLabel = QLabel(self.tr("Randomise order"))
81
- self.randSwitch = NSwitch()
83
+ self.randLabel = QLabel(self.tr("Randomise order"), self)
84
+ self.randSwitch = NSwitch(self)
82
85
 
83
86
  self.formBox = QGridLayout()
84
- self.formBox.addWidget(self.headLabel, 0, 0, 1, 2, Qt.AlignLeft)
85
- self.formBox.addWidget(self.paraLabel, 1, 0, 1, 1, Qt.AlignLeft)
86
- self.formBox.addWidget(self.paraCount, 1, 1, 1, 1, Qt.AlignRight)
87
- self.formBox.addWidget(self.randLabel, 2, 0, 1, 1, Qt.AlignLeft)
88
- self.formBox.addWidget(self.randSwitch, 2, 1, 1, 1, Qt.AlignRight)
87
+ self.formBox.addWidget(self.headLabel, 0, 0, 1, 2, QtAlignLeft)
88
+ self.formBox.addWidget(self.paraLabel, 1, 0, 1, 1, QtAlignLeft)
89
+ self.formBox.addWidget(self.paraCount, 1, 1, 1, 1, QtAlignRight)
90
+ self.formBox.addWidget(self.randLabel, 2, 0, 1, 1, QtAlignLeft)
91
+ self.formBox.addWidget(self.randSwitch, 2, 1, 1, 1, QtAlignRight)
89
92
  self.formBox.setVerticalSpacing(vSp)
90
93
  self.formBox.setRowStretch(3, 1)
91
94
  self.innerBox.addLayout(self.formBox)
92
95
 
93
96
  # Buttons
94
- self.buttonBox = QDialogButtonBox()
97
+ self.buttonBox = QDialogButtonBox(self)
95
98
  self.buttonBox.rejected.connect(self.close)
96
99
 
97
- self.btnClose = self.buttonBox.addButton(QDialogButtonBox.Close)
100
+ self.btnClose = self.buttonBox.addButton(QtDialogClose)
98
101
  self.btnClose.setAutoDefault(False)
99
102
 
100
- self.btnInsert = self.buttonBox.addButton(self.tr("Insert"), QDialogButtonBox.ActionRole)
103
+ self.btnInsert = self.buttonBox.addButton(self.tr("Insert"), QtRoleAction)
101
104
  self.btnInsert.clicked.connect(self._doInsert)
102
105
  self.btnInsert.setAutoDefault(False)
103
106
 
@@ -127,7 +130,7 @@ class GuiLipsum(QDialog):
127
130
  def getLipsum(cls, parent: QWidget) -> str:
128
131
  """Pop the dialog and return the lipsum text."""
129
132
  cls = GuiLipsum(parent)
130
- cls.exec_()
133
+ cls.exec()
131
134
  text = cls.lipsumText
132
135
  cls.deleteLater()
133
136
  return text
@@ -27,8 +27,8 @@ import logging
27
27
 
28
28
  from pathlib import Path
29
29
 
30
+ from PyQt5.QtCore import QTimer, pyqtSlot
30
31
  from PyQt5.QtGui import QCloseEvent
31
- from PyQt5.QtCore import QSize, QTimer, Qt, pyqtSlot
32
32
  from PyQt5.QtWidgets import (
33
33
  QAbstractButton, QAbstractItemView, QDialog, QDialogButtonBox, QFileDialog,
34
34
  QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
@@ -36,13 +36,17 @@ from PyQt5.QtWidgets import (
36
36
  )
37
37
 
38
38
  from novelwriter import CONFIG, SHARED
39
- from novelwriter.enum import nwBuildFmt
40
39
  from novelwriter.common import makeFileNameSafe, openExternalPath
41
40
  from novelwriter.constants import nwLabels
42
- from novelwriter.core.item import NWItem
43
- from novelwriter.core.docbuild import NWBuildDocument
44
41
  from novelwriter.core.buildsettings import BuildSettings
42
+ from novelwriter.core.docbuild import NWBuildDocument
43
+ from novelwriter.core.item import NWItem
44
+ from novelwriter.enum import nwBuildFmt
45
+ from novelwriter.extensions.modified import NIconToolButton
45
46
  from novelwriter.extensions.simpleprogress import NProgressSimple
47
+ from novelwriter.types import (
48
+ QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole
49
+ )
46
50
 
47
51
  logger = logging.getLogger(__name__)
48
52
 
@@ -54,7 +58,7 @@ class GuiManuscriptBuild(QDialog):
54
58
  independently of the Manuscript Build Tool.
55
59
  """
56
60
 
57
- D_KEY = Qt.ItemDataRole.UserRole
61
+ D_KEY = QtUserRole
58
62
 
59
63
  def __init__(self, parent: QWidget, build: BuildSettings):
60
64
  super().__init__(parent=parent)
@@ -69,7 +73,8 @@ class GuiManuscriptBuild(QDialog):
69
73
  self.setMinimumWidth(CONFIG.pxInt(500))
70
74
  self.setMinimumHeight(CONFIG.pxInt(300))
71
75
 
72
- iPx = SHARED.theme.baseIconSize
76
+ iSz = SHARED.theme.baseIconSize
77
+ bSz = SHARED.theme.buttonIconSize
73
78
  sp4 = CONFIG.pxInt(4)
74
79
  sp8 = CONFIG.pxInt(8)
75
80
  sp16 = CONFIG.pxInt(16)
@@ -85,9 +90,9 @@ class GuiManuscriptBuild(QDialog):
85
90
  # Output Format
86
91
  # =============
87
92
 
88
- self.lblFormat = QLabel(self.tr("Output Format"))
89
- self.listFormats = QListWidget()
90
- self.listFormats.setIconSize(QSize(iPx, iPx))
93
+ self.lblFormat = QLabel(self.tr("Output Format"), self)
94
+ self.listFormats = QListWidget(self)
95
+ self.listFormats.setIconSize(iSz)
91
96
  current = None
92
97
  for key in nwBuildFmt:
93
98
  item = QListWidgetItem()
@@ -104,17 +109,17 @@ class GuiManuscriptBuild(QDialog):
104
109
  self.formatBox.addWidget(self.listFormats, 1)
105
110
  self.formatBox.setContentsMargins(0, 0, 0, 0)
106
111
 
107
- self.formatWidget = QWidget()
112
+ self.formatWidget = QWidget(self)
108
113
  self.formatWidget.setLayout(self.formatBox)
109
114
  self.formatWidget.setContentsMargins(0, 0, 0, 0)
110
115
 
111
116
  # Table of Contents
112
117
  # =================
113
118
 
114
- self.lblContent = QLabel(self.tr("Table of Contents"))
119
+ self.lblContent = QLabel(self.tr("Table of Contents"), self)
115
120
 
116
121
  self.listContent = QListWidget(self)
117
- self.listContent.setIconSize(QSize(iPx, iPx))
122
+ self.listContent.setIconSize(iSz)
118
123
  self.listContent.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
119
124
 
120
125
  self.contentBox = QVBoxLayout()
@@ -122,7 +127,7 @@ class GuiManuscriptBuild(QDialog):
122
127
  self.contentBox.addWidget(self.listContent, 0)
123
128
  self.contentBox.setContentsMargins(0, 0, 0, 0)
124
129
 
125
- self.contentWidget = QWidget()
130
+ self.contentWidget = QWidget(self)
126
131
  self.contentWidget.setLayout(self.contentBox)
127
132
  self.contentWidget.setContentsMargins(0, 0, 0, 0)
128
133
 
@@ -134,14 +139,14 @@ class GuiManuscriptBuild(QDialog):
134
139
  font.setUnderline(True)
135
140
  font.setPointSizeF(1.5*font.pointSizeF())
136
141
 
137
- self.lblMain = QLabel(self._build.name)
142
+ self.lblMain = QLabel(self._build.name, self)
138
143
  self.lblMain.setWordWrap(True)
139
144
  self.lblMain.setFont(font)
140
145
 
141
146
  # Build Path
142
- self.lblPath = QLabel(self.tr("Path"))
147
+ self.lblPath = QLabel(self.tr("Path"), self)
143
148
  self.buildPath = QLineEdit(self)
144
- self.btnBrowse = QPushButton(SHARED.theme.getIcon("browse"), "")
149
+ self.btnBrowse = NIconToolButton(self, iSz, "browse")
145
150
 
146
151
  self.pathBox = QHBoxLayout()
147
152
  self.pathBox.addWidget(self.buildPath)
@@ -149,9 +154,9 @@ class GuiManuscriptBuild(QDialog):
149
154
  self.pathBox.setSpacing(sp8)
150
155
 
151
156
  # Build Name
152
- self.lblName = QLabel(self.tr("File Name"))
157
+ self.lblName = QLabel(self.tr("File Name"), self)
153
158
  self.buildName = QLineEdit(self)
154
- self.btnReset = QPushButton(SHARED.theme.getIcon("revert"), "")
159
+ self.btnReset = NIconToolButton(self, iSz, "revert")
155
160
  self.btnReset.setToolTip(self.tr("Reset file name to default"))
156
161
 
157
162
  self.nameBox = QHBoxLayout()
@@ -176,16 +181,19 @@ class GuiManuscriptBuild(QDialog):
176
181
  self.buildBox.setVerticalSpacing(sp4)
177
182
 
178
183
  # Dialog Buttons
179
- self.btnOpen = QPushButton(SHARED.theme.getIcon("browse"), self.tr("Open Folder"))
180
- self.btnBuild = QPushButton(SHARED.theme.getIcon("export"), self.tr("&Build"))
181
- self.dlgButtons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
182
- self.dlgButtons.addButton(self.btnOpen, QDialogButtonBox.ButtonRole.ActionRole)
183
- self.dlgButtons.addButton(self.btnBuild, QDialogButtonBox.ButtonRole.ActionRole)
184
+ self.btnOpen = QPushButton(SHARED.theme.getIcon("browse"), self.tr("Open Folder"), self)
185
+ self.btnOpen.setIconSize(bSz)
186
+ self.btnBuild = QPushButton(SHARED.theme.getIcon("export"), self.tr("&Build"), self)
187
+ self.btnBuild.setIconSize(bSz)
188
+
189
+ self.dlgButtons = QDialogButtonBox(QtDialogClose, self)
190
+ self.dlgButtons.addButton(self.btnOpen, QtRoleAction)
191
+ self.dlgButtons.addButton(self.btnBuild, QtRoleAction)
184
192
 
185
193
  # Assemble GUI
186
194
  # ============
187
195
 
188
- self.mainSplit = QSplitter()
196
+ self.mainSplit = QSplitter(self)
189
197
  self.mainSplit.addWidget(self.formatWidget)
190
198
  self.mainSplit.addWidget(self.contentWidget)
191
199
  self.mainSplit.setHandleWidth(sp16)
@@ -199,7 +207,7 @@ class GuiManuscriptBuild(QDialog):
199
207
  ])
200
208
 
201
209
  self.outerBox = QVBoxLayout()
202
- self.outerBox.addWidget(self.lblMain, 0, Qt.AlignCenter)
210
+ self.outerBox.addWidget(self.lblMain, 0, QtAlignCenter)
203
211
  self.outerBox.addSpacing(sp16)
204
212
  self.outerBox.addWidget(self.mainSplit, 1)
205
213
  self.outerBox.addSpacing(sp4)
@@ -255,12 +263,12 @@ class GuiManuscriptBuild(QDialog):
255
263
  def _dialogButtonClicked(self, button: QAbstractButton):
256
264
  """Handle button clicks from the dialog button box."""
257
265
  role = self.dlgButtons.buttonRole(button)
258
- if role == QDialogButtonBox.ActionRole:
266
+ if role == QtRoleAction:
259
267
  if button == self.btnBuild:
260
268
  self._runBuild()
261
269
  elif button == self.btnOpen:
262
270
  self._openOutputFolder()
263
- elif role == QDialogButtonBox.RejectRole:
271
+ elif role == QtRoleReject:
264
272
  self.close()
265
273
  return
266
274