novelWriter 2.4.3__py3-none-any.whl → 2.5__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 (123) hide show
  1. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/METADATA +4 -5
  2. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/RECORD +122 -112
  3. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +33 -39
  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_nl_NL.qm +0 -0
  13. novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
  14. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  15. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  16. novelwriter/assets/i18n/project_en_GB.json +1 -0
  17. novelwriter/assets/i18n/project_pl_PL.json +116 -0
  18. novelwriter/assets/i18n/project_pt_BR.json +74 -74
  19. novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
  20. novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
  21. novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
  22. novelwriter/assets/icons/typicons_light/icons.conf +2 -0
  23. novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
  24. novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
  25. novelwriter/assets/manual.pdf +0 -0
  26. novelwriter/assets/sample.zip +0 -0
  27. novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
  28. novelwriter/assets/syntax/default_dark.conf +32 -18
  29. novelwriter/assets/syntax/default_light.conf +24 -10
  30. novelwriter/assets/syntax/dracula.conf +44 -0
  31. novelwriter/assets/syntax/grey_dark.conf +5 -4
  32. novelwriter/assets/syntax/grey_light.conf +5 -4
  33. novelwriter/assets/syntax/light_owl.conf +7 -6
  34. novelwriter/assets/syntax/night_owl.conf +7 -6
  35. novelwriter/assets/syntax/snazzy.conf +42 -0
  36. novelwriter/assets/syntax/solarized_dark.conf +4 -3
  37. novelwriter/assets/syntax/solarized_light.conf +4 -3
  38. novelwriter/assets/syntax/tango.conf +27 -11
  39. novelwriter/assets/syntax/tomorrow.conf +6 -5
  40. novelwriter/assets/syntax/tomorrow_night.conf +7 -6
  41. novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
  42. novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
  43. novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
  44. novelwriter/assets/text/credits_en.htm +52 -41
  45. novelwriter/assets/themes/cyberpunk_night.conf +3 -0
  46. novelwriter/assets/themes/default_dark.conf +2 -0
  47. novelwriter/assets/themes/default_light.conf +2 -0
  48. novelwriter/assets/themes/dracula.conf +48 -0
  49. novelwriter/assets/themes/solarized_dark.conf +2 -0
  50. novelwriter/assets/themes/solarized_light.conf +2 -0
  51. novelwriter/common.py +33 -12
  52. novelwriter/config.py +184 -98
  53. novelwriter/constants.py +47 -35
  54. novelwriter/core/buildsettings.py +68 -69
  55. novelwriter/core/coretools.py +5 -23
  56. novelwriter/core/docbuild.py +52 -40
  57. novelwriter/core/document.py +3 -5
  58. novelwriter/core/index.py +115 -45
  59. novelwriter/core/item.py +8 -19
  60. novelwriter/core/options.py +2 -4
  61. novelwriter/core/project.py +37 -61
  62. novelwriter/core/projectdata.py +1 -3
  63. novelwriter/core/projectxml.py +12 -15
  64. novelwriter/core/sessions.py +3 -5
  65. novelwriter/core/spellcheck.py +4 -9
  66. novelwriter/core/status.py +211 -164
  67. novelwriter/core/storage.py +0 -8
  68. novelwriter/core/tohtml.py +139 -105
  69. novelwriter/core/tokenizer.py +278 -122
  70. novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
  71. novelwriter/core/toodt.py +257 -166
  72. novelwriter/core/toqdoc.py +419 -0
  73. novelwriter/core/tree.py +5 -7
  74. novelwriter/dialogs/about.py +11 -18
  75. novelwriter/dialogs/docmerge.py +17 -19
  76. novelwriter/dialogs/docsplit.py +17 -19
  77. novelwriter/dialogs/editlabel.py +6 -10
  78. novelwriter/dialogs/preferences.py +200 -164
  79. novelwriter/dialogs/projectsettings.py +225 -189
  80. novelwriter/dialogs/quotes.py +12 -9
  81. novelwriter/dialogs/wordlist.py +9 -15
  82. novelwriter/enum.py +35 -30
  83. novelwriter/error.py +8 -15
  84. novelwriter/extensions/configlayout.py +55 -21
  85. novelwriter/extensions/eventfilters.py +1 -5
  86. novelwriter/extensions/modified.py +70 -14
  87. novelwriter/extensions/novelselector.py +1 -3
  88. novelwriter/extensions/pagedsidebar.py +9 -12
  89. novelwriter/extensions/{circularprogress.py → progressbars.py} +30 -8
  90. novelwriter/extensions/statusled.py +40 -26
  91. novelwriter/extensions/switch.py +4 -6
  92. novelwriter/extensions/switchbox.py +7 -6
  93. novelwriter/extensions/versioninfo.py +3 -9
  94. novelwriter/gui/doceditor.py +120 -139
  95. novelwriter/gui/dochighlight.py +231 -186
  96. novelwriter/gui/docviewer.py +69 -108
  97. novelwriter/gui/docviewerpanel.py +3 -10
  98. novelwriter/gui/editordocument.py +1 -3
  99. novelwriter/gui/itemdetails.py +7 -11
  100. novelwriter/gui/mainmenu.py +22 -18
  101. novelwriter/gui/noveltree.py +11 -24
  102. novelwriter/gui/outline.py +15 -26
  103. novelwriter/gui/projtree.py +39 -65
  104. novelwriter/gui/search.py +10 -3
  105. novelwriter/gui/sidebar.py +2 -6
  106. novelwriter/gui/statusbar.py +29 -37
  107. novelwriter/gui/theme.py +26 -48
  108. novelwriter/guimain.py +162 -160
  109. novelwriter/shared.py +36 -19
  110. novelwriter/text/patterns.py +113 -0
  111. novelwriter/tools/dictionaries.py +10 -20
  112. novelwriter/tools/lipsum.py +10 -16
  113. novelwriter/tools/manusbuild.py +9 -11
  114. novelwriter/tools/manuscript.py +75 -149
  115. novelwriter/tools/manussettings.py +74 -76
  116. novelwriter/tools/noveldetails.py +16 -21
  117. novelwriter/tools/welcome.py +21 -26
  118. novelwriter/tools/writingstats.py +9 -12
  119. novelwriter/types.py +49 -4
  120. novelwriter/extensions/simpleprogress.py +0 -55
  121. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/LICENSE.md +0 -0
  122. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/entry_points.txt +0 -0
  123. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/top_level.txt +0 -0
@@ -25,21 +25,25 @@ from __future__ import annotations
25
25
 
26
26
  import logging
27
27
 
28
- from PyQt5.QtGui import QFontMetrics
29
28
  from PyQt5.QtCore import QSize, pyqtSlot
29
+ from PyQt5.QtGui import QFontMetrics
30
30
  from PyQt5.QtWidgets import (
31
- QDialog, QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
31
+ QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
32
32
  QListWidgetItem, QVBoxLayout, QWidget
33
33
  )
34
34
 
35
35
  from novelwriter import CONFIG
36
- from novelwriter.constants import trConst, nwQuotes
37
- from novelwriter.types import QtAlignCenter, QtAlignTop, QtDialogCancel, QtDialogOk, QtUserRole
36
+ from novelwriter.constants import nwQuotes, trConst
37
+ from novelwriter.extensions.modified import NDialog
38
+ from novelwriter.types import (
39
+ QtAccepted, QtAlignCenter, QtAlignTop, QtDialogCancel, QtDialogOk,
40
+ QtUserRole
41
+ )
38
42
 
39
43
  logger = logging.getLogger(__name__)
40
44
 
41
45
 
42
- class GuiQuoteSelect(QDialog):
46
+ class GuiQuoteSelect(NDialog):
43
47
 
44
48
  _selected = ""
45
49
 
@@ -50,6 +54,7 @@ class GuiQuoteSelect(QDialog):
50
54
 
51
55
  logger.debug("Create: GuiQuoteSelect")
52
56
  self.setObjectName("GuiQuoteSelect")
57
+ self.setWindowTitle(self.tr("Select Quote Style"))
53
58
 
54
59
  self.outerBox = QVBoxLayout()
55
60
  self.innerBox = QHBoxLayout()
@@ -125,8 +130,8 @@ class GuiQuoteSelect(QDialog):
125
130
  cls = GuiQuoteSelect(parent, current=current)
126
131
  cls.exec()
127
132
  quote = cls._selected
128
- accepted = cls.result() == QDialog.DialogCode.Accepted
129
- cls.deleteLater()
133
+ accepted = cls.result() == QtAccepted
134
+ cls.softDelete()
130
135
  return quote, accepted
131
136
 
132
137
  ##
@@ -141,5 +146,3 @@ class GuiQuoteSelect(QDialog):
141
146
  self.previewLabel.setText(quote)
142
147
  self._selected = quote
143
148
  return
144
-
145
- # END Class GuiQuoteSelect
@@ -25,35 +25,31 @@ from __future__ import annotations
25
25
 
26
26
  import logging
27
27
 
28
- from typing import TYPE_CHECKING
29
28
  from pathlib import Path
30
29
 
31
30
  from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
32
31
  from PyQt5.QtGui import QCloseEvent
33
32
  from PyQt5.QtWidgets import (
34
- QAbstractItemView, QApplication, QDialog, QDialogButtonBox, QFileDialog,
35
- QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout
33
+ QAbstractItemView, QApplication, QDialogButtonBox, QFileDialog,
34
+ QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout, QWidget
36
35
  )
37
36
 
38
37
  from novelwriter import CONFIG, SHARED
39
38
  from novelwriter.common import formatFileFilter
40
39
  from novelwriter.core.spellcheck import UserDictionary
41
40
  from novelwriter.extensions.configlayout import NColourLabel
42
- from novelwriter.extensions.modified import NIconToolButton
41
+ from novelwriter.extensions.modified import NDialog, NIconToolButton
43
42
  from novelwriter.types import QtDialogClose, QtDialogSave
44
43
 
45
- if TYPE_CHECKING: # pragma: no cover
46
- from novelwriter.guimain import GuiMain
47
-
48
44
  logger = logging.getLogger(__name__)
49
45
 
50
46
 
51
- class GuiWordList(QDialog):
47
+ class GuiWordList(NDialog):
52
48
 
53
49
  newWordListReady = pyqtSignal()
54
50
 
55
- def __init__(self, mainGui: GuiMain) -> None:
56
- super().__init__(parent=mainGui)
51
+ def __init__(self, parent: QWidget) -> None:
52
+ super().__init__(parent=parent)
57
53
 
58
54
  logger.debug("Create: GuiWordList")
59
55
  self.setObjectName("GuiWordList")
@@ -73,7 +69,7 @@ class GuiWordList(QDialog):
73
69
 
74
70
  # Header
75
71
  self.headLabel = NColourLabel(
76
- self.tr("Project Word List"), SHARED.theme.helpText, parent=self,
72
+ self.tr("Project Word List"), self, color=SHARED.theme.helpText,
77
73
  scale=NColourLabel.HEADER_SCALE
78
74
  )
79
75
 
@@ -113,7 +109,7 @@ class GuiWordList(QDialog):
113
109
  # Buttons
114
110
  self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogClose, self)
115
111
  self.buttonBox.accepted.connect(self._doSave)
116
- self.buttonBox.rejected.connect(self.close)
112
+ self.buttonBox.rejected.connect(self.reject)
117
113
 
118
114
  # Assemble
119
115
  self.outerBox = QVBoxLayout()
@@ -144,7 +140,7 @@ class GuiWordList(QDialog):
144
140
  """Capture the close event and perform cleanup."""
145
141
  self._saveGuiSettings()
146
142
  event.accept()
147
- self.deleteLater()
143
+ self.softDelete()
148
144
  return
149
145
 
150
146
  ##
@@ -257,5 +253,3 @@ class GuiWordList(QDialog):
257
253
  if (item := self.listBox.item(i)) and (word := item.text().strip()):
258
254
  result.append(word)
259
255
  return result
260
-
261
- # END Class GuiWordList
novelwriter/enum.py CHANGED
@@ -32,8 +32,6 @@ class nwItemType(Enum):
32
32
  FOLDER = 2
33
33
  FILE = 3
34
34
 
35
- # END Enum nwItemType
36
-
37
35
 
38
36
  class nwItemClass(Enum):
39
37
 
@@ -50,8 +48,6 @@ class nwItemClass(Enum):
50
48
  TEMPLATE = 10
51
49
  TRASH = 11
52
50
 
53
- # END Enum nwItemClass
54
-
55
51
 
56
52
  class nwItemLayout(Enum):
57
53
 
@@ -59,16 +55,17 @@ class nwItemLayout(Enum):
59
55
  DOCUMENT = 1
60
56
  NOTE = 2
61
57
 
62
- # END Enum nwItemLayout
63
-
64
58
 
65
59
  class nwComment(Enum):
66
60
 
67
61
  PLAIN = 0
68
- SYNOPSIS = 1
69
- SHORT = 2
70
-
71
- # END Enum nwComment
62
+ IGNORE = 1
63
+ SYNOPSIS = 2
64
+ SHORT = 3
65
+ NOTE = 4
66
+ FOOTNOTE = 5
67
+ COMMENT = 6
68
+ STORY = 7
72
69
 
73
70
 
74
71
  class nwTrinary(Enum):
@@ -77,16 +74,12 @@ class nwTrinary(Enum):
77
74
  NEUTRAL = 0
78
75
  POSITIVE = 1
79
76
 
80
- # END Enum nwTrinary
81
-
82
77
 
83
78
  class nwDocMode(Enum):
84
79
 
85
80
  VIEW = 0
86
81
  EDIT = 1
87
82
 
88
- # END Enum nwDocMode
89
-
90
83
 
91
84
  class nwDocAction(Enum):
92
85
 
@@ -129,8 +122,6 @@ class nwDocAction(Enum):
129
122
  SC_SUP = 36
130
123
  SC_SUB = 37
131
124
 
132
- # END Enum nwDocAction
133
-
134
125
 
135
126
  class nwDocInsert(Enum):
136
127
 
@@ -145,8 +136,7 @@ class nwDocInsert(Enum):
145
136
  VSPACE_S = 8
146
137
  VSPACE_M = 9
147
138
  LIPSUM = 10
148
-
149
- # END Enum nwDocInsert
139
+ FOOTNOTE = 11
150
140
 
151
141
 
152
142
  class nwView(Enum):
@@ -157,17 +147,12 @@ class nwView(Enum):
157
147
  OUTLINE = 3
158
148
  SEARCH = 4
159
149
 
160
- # END Enum nwView
161
-
162
150
 
163
- class nwWidget(Enum):
151
+ class nwFocus(Enum):
164
152
 
165
- TREE = 1
166
- EDITOR = 2
167
- VIEWER = 3
168
- OUTLINE = 4
169
-
170
- # END Enum nwWidget
153
+ TREE = 1
154
+ DOCUMENT = 2
155
+ OUTLINE = 3
171
156
 
172
157
 
173
158
  class nwOutline(Enum):
@@ -190,8 +175,6 @@ class nwOutline(Enum):
190
175
  CUSTOM = 15
191
176
  SYNOP = 16
192
177
 
193
- # END Enum nwOutline
194
-
195
178
 
196
179
  class nwBuildFmt(Enum):
197
180
 
@@ -204,4 +187,26 @@ class nwBuildFmt(Enum):
204
187
  J_HTML = 6
205
188
  J_NWD = 7
206
189
 
207
- # END Enum nwBuildFormat
190
+
191
+ class nwStatusShape(Enum):
192
+
193
+ SQUARE = 0
194
+ TRIANGLE = 1
195
+ NABLA = 2
196
+ DIAMOND = 3
197
+ PENTAGON = 4
198
+ HEXAGON = 5
199
+ STAR = 6
200
+ PACMAN = 7
201
+ CIRCLE_Q = 8
202
+ CIRCLE_H = 9
203
+ CIRCLE_T = 10
204
+ CIRCLE = 11
205
+ BARS_1 = 12
206
+ BARS_2 = 13
207
+ BARS_3 = 14
208
+ BARS_4 = 15
209
+ BLOCK_1 = 16
210
+ BLOCK_2 = 17
211
+ BLOCK_3 = 18
212
+ BLOCK_4 = 19
novelwriter/error.py CHANGED
@@ -23,17 +23,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
23
23
  """
24
24
  from __future__ import annotations
25
25
 
26
- import sys
27
- import random
28
26
  import logging
27
+ import random
28
+ import sys
29
29
 
30
30
  from typing import TYPE_CHECKING
31
31
 
32
32
  from PyQt5.QtCore import Qt, pyqtSlot
33
33
  from PyQt5.QtGui import QFont, QFontDatabase
34
34
  from PyQt5.QtWidgets import (
35
- QApplication, QWidget, QDialog, QGridLayout, QStyle, QPlainTextEdit,
36
- QLabel, QDialogButtonBox
35
+ QApplication, QDialog, QDialogButtonBox, QGridLayout, QLabel,
36
+ QPlainTextEdit, QStyle, QWidget
37
37
  )
38
38
 
39
39
  if TYPE_CHECKING: # pragma: no cover
@@ -42,10 +42,6 @@ if TYPE_CHECKING: # pragma: no cover
42
42
  logger = logging.getLogger(__name__)
43
43
 
44
44
 
45
- # =============================================================================================== #
46
- # Utility Functions
47
- # =============================================================================================== #
48
-
49
45
  def logException() -> None:
50
46
  """Log the content of an exception message."""
51
47
  exType, exValue, _ = sys.exc_info()
@@ -61,10 +57,6 @@ def formatException(exc: BaseException) -> str:
61
57
  return f"{type(exc).__name__}: {str(exc)}"
62
58
 
63
59
 
64
- # =============================================================================================== #
65
- # Error Handler
66
- # =============================================================================================== #
67
-
68
60
  class NWErrorMessage(QDialog):
69
61
 
70
62
  def __init__(self, parent: QWidget) -> None:
@@ -125,9 +117,11 @@ class NWErrorMessage(QDialog):
125
117
  error traceback.
126
118
  """
127
119
  from traceback import format_tb
120
+
121
+ from PyQt5.QtCore import PYQT_VERSION_STR, QT_VERSION_STR, QSysInfo
122
+
128
123
  from novelwriter import __version__
129
124
  from novelwriter.constants import nwConst
130
- from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QSysInfo
131
125
 
132
126
  self.msgHead.setText(
133
127
  "<p>An unhandled error has been encountered.</p>"
@@ -175,12 +169,11 @@ class NWErrorMessage(QDialog):
175
169
  self.close()
176
170
  return
177
171
 
178
- # END Class NWErrorMessage
179
-
180
172
 
181
173
  def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackType) -> None:
182
174
  """Function to catch unhandled global exceptions."""
183
175
  from traceback import print_tb
176
+
184
177
  from PyQt5.QtWidgets import QApplication
185
178
 
186
179
  logger.critical("%s: %s", exType.__name__, str(exValue))
@@ -27,8 +27,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
27
27
  """
28
28
  from __future__ import annotations
29
29
 
30
- from PyQt5.QtGui import QColor, QFont, QPalette
31
30
  from PyQt5.QtCore import Qt
31
+ from PyQt5.QtGui import QColor, QFont, QPalette
32
32
  from PyQt5.QtWidgets import (
33
33
  QAbstractButton, QFrame, QHBoxLayout, QLabel, QLayout, QScrollArea,
34
34
  QVBoxLayout, QWidget
@@ -64,8 +64,6 @@ class NFixedPage(QFrame):
64
64
  self.setLayout(layout)
65
65
  return
66
66
 
67
- # END Class NFixedPage
68
-
69
67
 
70
68
  class NScrollablePage(QScrollArea):
71
69
  """Extension: Scrollable Page Widget
@@ -89,8 +87,6 @@ class NScrollablePage(QScrollArea):
89
87
  self._widget.setLayout(layout)
90
88
  return
91
89
 
92
- # END Class NScrollablePage
93
-
94
90
 
95
91
  class NScrollableForm(QScrollArea):
96
92
  """Extension: Scrollable Form Widget
@@ -184,20 +180,30 @@ class NScrollableForm(QScrollArea):
184
180
  self._sections[identifier] = qLabel
185
181
  return
186
182
 
187
- def addRow(self, label: str, widget: QWidget, helpText: str = "", unit: str | None = None,
188
- button: QWidget | None = None, editable: str | None = None,
183
+ def addRow(self, label: str, widget: QWidget | list[QWidget], helpText: str = "",
184
+ unit: str | None = None, button: QWidget | None = None, editable: str | None = None,
189
185
  stretch: tuple[int, int] = (1, 0)) -> None:
190
186
  """Add a label and a widget as a new row of the form."""
191
187
  row = QHBoxLayout()
192
188
  row.setSpacing(CONFIG.pxInt(12))
193
189
 
190
+ if isinstance(widget, list):
191
+ wBox = QHBoxLayout()
192
+ wBox.setContentsMargins(0, 0, 0, 0)
193
+ for item in widget:
194
+ wBox.addWidget(item)
195
+ qWidget = QWidget(self)
196
+ qWidget.setLayout(wBox)
197
+ else:
198
+ qWidget = widget
199
+
194
200
  qLabel = QLabel(label, self)
195
201
  qLabel.setIndent(self._indent)
196
- qLabel.setBuddy(widget)
202
+ qLabel.setBuddy(qWidget)
197
203
 
198
204
  if helpText:
199
205
  qHelp = NColourLabel(
200
- str(helpText), color=self._helpCol, parent=self,
206
+ str(helpText), self, color=self._helpCol,
201
207
  scale=self._fontScale, wrap=True, indent=self._indent
202
208
  )
203
209
  labelBox = QVBoxLayout()
@@ -212,19 +218,19 @@ class NScrollableForm(QScrollArea):
212
218
 
213
219
  if isinstance(unit, str):
214
220
  box = QHBoxLayout()
215
- box.addWidget(widget, 1)
221
+ box.addWidget(qWidget, 1)
216
222
  box.addWidget(QLabel(unit, self), 0)
217
223
  row.addLayout(box, stretch[1])
218
224
  elif isinstance(button, QAbstractButton):
219
225
  box = QHBoxLayout()
220
- box.addWidget(widget, 1)
226
+ box.addWidget(qWidget, 1)
221
227
  box.addWidget(button, 0)
222
228
  row.addLayout(box, stretch[1])
223
229
  else:
224
- row.addWidget(widget, stretch[1])
230
+ row.addWidget(qWidget, stretch[1])
225
231
 
226
232
  self._layout.addLayout(row)
227
- self._index[label.strip()] = widget
233
+ self._index[label.strip()] = qWidget
228
234
  self._first = False
229
235
 
230
236
  return
@@ -235,8 +241,6 @@ class NScrollableForm(QScrollArea):
235
241
  self._layout.addStretch(1)
236
242
  return
237
243
 
238
- # END Class NScrollableForm
239
-
240
244
 
241
245
  class NColourLabel(QLabel):
242
246
  """Extension: A Coloured Label
@@ -248,11 +252,20 @@ class NColourLabel(QLabel):
248
252
  HELP_SCALE = DEFAULT_SCALE
249
253
  HEADER_SCALE = 1.25
250
254
 
251
- def __init__(self, text: str, color: QColor | None = None, parent: QWidget | None = None,
252
- scale: float = HELP_SCALE, wrap: bool = False, indent: int = 0,
253
- bold: bool = False) -> None:
255
+ _state = None
256
+
257
+ def __init__(
258
+ self, text: str, parent: QWidget, *,
259
+ color: QColor | None = None, faded: QColor | None = None,
260
+ scale: float = HELP_SCALE, wrap: bool = False, indent: int = 0,
261
+ bold: bool = False
262
+ ) -> None:
254
263
  super().__init__(text, parent=parent)
255
264
 
265
+ default = self.palette().windowText().color()
266
+ self._color = color or default
267
+ self._faded = faded or default
268
+
256
269
  font = self.font()
257
270
  font.setPointSizeF(scale*font.pointSizeF())
258
271
  font.setWeight(QFont.Weight.Bold if bold else QFont.Weight.Normal)
@@ -264,10 +277,33 @@ class NColourLabel(QLabel):
264
277
  self.setFont(font)
265
278
  self.setIndent(indent)
266
279
  self.setWordWrap(wrap)
280
+ self.setColorState(True)
267
281
 
268
282
  return
269
283
 
270
- # END Class NColourLabel
284
+ def setTextColors(self, *, color: QColor | None = None, faded: QColor | None = None) -> None:
285
+ """Set or update the text colours."""
286
+ self._color = color or self._color
287
+ self._faded = faded or self._faded
288
+ self._refeshTextColor()
289
+ return
290
+
291
+ def setColorState(self, state: bool) -> None:
292
+ """Change the colour state."""
293
+ if self._state is not state:
294
+ self._state = state
295
+ self._refeshTextColor()
296
+ return
297
+
298
+ def _refeshTextColor(self) -> None:
299
+ """Refresh the colour of the text on the label."""
300
+ palette = self.palette()
301
+ palette.setColor(
302
+ QPalette.ColorRole.WindowText,
303
+ self._color if self._state else self._faded,
304
+ )
305
+ self.setPalette(palette)
306
+ return
271
307
 
272
308
 
273
309
  class NWrappedWidgetBox(QHBoxLayout):
@@ -287,5 +323,3 @@ class NWrappedWidgetBox(QHBoxLayout):
287
323
  if after:
288
324
  self.addWidget(QLabel(after.lstrip()))
289
325
  return
290
-
291
- # END Class NWrappedWidgetBox
@@ -24,8 +24,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
24
24
  """
25
25
  from __future__ import annotations
26
26
 
27
- from PyQt5.QtGui import QStatusTipEvent, QWheelEvent
28
27
  from PyQt5.QtCore import QEvent, QObject
28
+ from PyQt5.QtGui import QStatusTipEvent, QWheelEvent
29
29
  from PyQt5.QtWidgets import QWidget
30
30
 
31
31
 
@@ -63,13 +63,9 @@ class WheelEventFilter(QObject):
63
63
  self._locked = False
64
64
  return False
65
65
 
66
- # END Class WheelEventFilter
67
-
68
66
 
69
67
  class StatusTipFilter(QObject):
70
68
 
71
69
  def eventFilter(self, obj: QObject, event: QEvent) -> bool:
72
70
  """Filter out status tip events on menus."""
73
71
  return True if isinstance(event, QStatusTipEvent) else super().eventFilter(obj, event)
74
-
75
- # END Class StatusTipFilter
@@ -6,6 +6,8 @@ File History:
6
6
  Created: 2024-02-01 [2.3b1] NComboBox
7
7
  Created: 2024-02-01 [2.3b1] NSpinBox
8
8
  Created: 2024-02-01 [2.3b1] NDoubleSpinBox
9
+ Created: 2024-05-01 [2.5b1] NToolDialog
10
+ Created: 2024-05-01 [2.5b1] NNonBlockingDialog
9
11
 
10
12
  This file is a part of novelWriter
11
13
  Copyright 2018–2024, Veronica Berglyd Olsen
@@ -25,11 +27,75 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
25
27
  """
26
28
  from __future__ import annotations
27
29
 
28
- from PyQt5.QtCore import QSize, Qt
30
+ from enum import Enum
31
+ from typing import TYPE_CHECKING
32
+
33
+ from PyQt5.QtCore import QSize, Qt, pyqtSlot
29
34
  from PyQt5.QtGui import QWheelEvent
30
- from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QToolButton, QWidget
35
+ from PyQt5.QtWidgets import (
36
+ QApplication, QComboBox, QDialog, QDoubleSpinBox, QSpinBox, QToolButton,
37
+ QWidget
38
+ )
39
+
40
+ from novelwriter import CONFIG, SHARED
41
+
42
+ if TYPE_CHECKING: # pragma: no cover
43
+ from novelwriter.guimain import GuiMain
44
+
45
+
46
+ class NDialog(QDialog):
47
+
48
+ def softDelete(self) -> None:
49
+ """Since calling deleteLater is sometimes not safe from Python,
50
+ as the C++ object can be deleted before the Python process is
51
+ done with the object, we instead set the dialog's parent to None
52
+ so that it gets garbage collected when it runs out of scope.
53
+ """
54
+ self.setParent(None) # type: ignore
55
+ return
56
+
57
+ @pyqtSlot()
58
+ def reject(self) -> None:
59
+ """Overload the reject slot and also call close."""
60
+ super().reject()
61
+ self.close()
62
+ return
63
+
64
+
65
+ class NToolDialog(NDialog):
66
+
67
+ def __init__(self, parent: GuiMain) -> None:
68
+ super().__init__(parent=parent)
69
+ self.setModal(False)
70
+ if CONFIG.osDarwin:
71
+ self.setWindowFlag(Qt.WindowType.Tool)
72
+ return
31
73
 
32
- from novelwriter import SHARED
74
+ def activateDialog(self) -> None:
75
+ """Helper function to activate dialog on various systems."""
76
+ self.show()
77
+ if CONFIG.osWindows:
78
+ self.activateWindow()
79
+ self.raise_()
80
+ QApplication.processEvents()
81
+ return
82
+
83
+
84
+ class NNonBlockingDialog(NDialog):
85
+
86
+ def __init__(self, parent: QWidget | None = None) -> None:
87
+ super().__init__(parent=parent)
88
+ self.setModal(True)
89
+ return
90
+
91
+ def activateDialog(self) -> None:
92
+ """Helper function to activate dialog on various systems."""
93
+ self.show()
94
+ if CONFIG.osWindows:
95
+ self.activateWindow()
96
+ self.raise_()
97
+ QApplication.processEvents()
98
+ return
33
99
 
34
100
 
35
101
  class NComboBox(QComboBox):
@@ -46,14 +112,12 @@ class NComboBox(QComboBox):
46
112
  event.ignore()
47
113
  return
48
114
 
49
- def setCurrentData(self, data: str, default: str) -> None:
115
+ def setCurrentData(self, data: str | int | Enum, default: str | int | Enum) -> None:
50
116
  """Set the current index from data, with a fallback."""
51
117
  idx = self.findData(data)
52
118
  self.setCurrentIndex(self.findData(default) if idx < 0 else idx)
53
119
  return
54
120
 
55
- # END Class NComboBox
56
-
57
121
 
58
122
  class NSpinBox(QSpinBox):
59
123
 
@@ -69,8 +133,6 @@ class NSpinBox(QSpinBox):
69
133
  event.ignore()
70
134
  return
71
135
 
72
- # END Class NSpinBox
73
-
74
136
 
75
137
  class NDoubleSpinBox(QDoubleSpinBox):
76
138
 
@@ -86,8 +148,6 @@ class NDoubleSpinBox(QDoubleSpinBox):
86
148
  event.ignore()
87
149
  return
88
150
 
89
- # END Class NDoubleSpinBox
90
-
91
151
 
92
152
  class NIconToolButton(QToolButton):
93
153
 
@@ -105,8 +165,6 @@ class NIconToolButton(QToolButton):
105
165
  self.setIcon(SHARED.theme.getIcon(iconKey))
106
166
  return
107
167
 
108
- # END Class NIconToolButton
109
-
110
168
 
111
169
  class NIconToggleButton(QToolButton):
112
170
 
@@ -126,5 +184,3 @@ class NIconToggleButton(QToolButton):
126
184
  iconSize = self.iconSize()
127
185
  self.setIcon(SHARED.theme.getToggleIcon(iconKey, (iconSize.width(), iconSize.height())))
128
186
  return
129
-
130
- # END Class NUnfoldButton
@@ -29,8 +29,8 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot
29
29
  from PyQt5.QtWidgets import QComboBox, QWidget
30
30
 
31
31
  from novelwriter import SHARED
32
- from novelwriter.enum import nwItemClass
33
32
  from novelwriter.constants import nwLabels
33
+ from novelwriter.enum import nwItemClass
34
34
 
35
35
  logger = logging.getLogger(__name__)
36
36
 
@@ -126,5 +126,3 @@ class NovelSelector(QComboBox):
126
126
  if not self._blockSignal:
127
127
  self.novelSelectionChanged.emit(self.currentData())
128
128
  return
129
-
130
- # END Class NovelSelector