novelWriter 2.2.1__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 (125) hide show
  1. {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/RECORD +116 -101
  3. novelWriter-2.3.dist-info/entry_points.txt +2 -0
  4. novelwriter/__init__.py +4 -4
  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_it_IT.qm +0 -0
  9. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  10. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  11. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  12. novelwriter/assets/i18n/project_nl_NL.json +11 -0
  13. novelwriter/assets/i18n/project_pt_BR.json +11 -0
  14. novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
  15. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +6 -0
  16. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  17. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  18. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  19. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  20. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +9 -0
  21. novelwriter/assets/icons/typicons_light/icons.conf +8 -0
  22. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +6 -0
  23. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  24. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  25. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  26. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  27. novelwriter/assets/icons/typicons_light/typ_th-list.svg +9 -0
  28. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  29. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  30. novelwriter/assets/images/welcome-dark.jpg +0 -0
  31. novelwriter/assets/images/welcome-light.jpg +0 -0
  32. novelwriter/assets/manual.pdf +0 -0
  33. novelwriter/assets/sample.zip +0 -0
  34. novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
  35. novelwriter/assets/syntax/default_dark.conf +1 -0
  36. novelwriter/assets/syntax/default_light.conf +1 -0
  37. novelwriter/assets/syntax/grey_dark.conf +1 -0
  38. novelwriter/assets/syntax/grey_light.conf +1 -0
  39. novelwriter/assets/syntax/light_owl.conf +1 -0
  40. novelwriter/assets/syntax/night_owl.conf +1 -0
  41. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  42. novelwriter/assets/syntax/solarized_light.conf +1 -0
  43. novelwriter/assets/syntax/tango.conf +23 -0
  44. novelwriter/assets/syntax/tomorrow.conf +1 -0
  45. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  46. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  47. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  48. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  49. novelwriter/assets/text/credits_en.htm +4 -2
  50. novelwriter/assets/themes/cyberpunk_night.conf +29 -0
  51. novelwriter/assets/themes/default_dark.conf +2 -2
  52. novelwriter/assets/themes/default_light.conf +2 -2
  53. novelwriter/common.py +48 -37
  54. novelwriter/config.py +36 -41
  55. novelwriter/constants.py +38 -16
  56. novelwriter/core/buildsettings.py +7 -7
  57. novelwriter/core/coretools.py +196 -156
  58. novelwriter/core/docbuild.py +6 -3
  59. novelwriter/core/document.py +6 -6
  60. novelwriter/core/index.py +89 -56
  61. novelwriter/core/item.py +21 -3
  62. novelwriter/core/options.py +8 -7
  63. novelwriter/core/project.py +70 -44
  64. novelwriter/core/projectdata.py +1 -14
  65. novelwriter/core/projectxml.py +13 -41
  66. novelwriter/core/sessions.py +2 -1
  67. novelwriter/core/spellcheck.py +2 -1
  68. novelwriter/core/status.py +2 -1
  69. novelwriter/core/storage.py +182 -140
  70. novelwriter/core/tohtml.py +4 -2
  71. novelwriter/core/tokenizer.py +109 -82
  72. novelwriter/core/toodt.py +40 -30
  73. novelwriter/core/tree.py +3 -2
  74. novelwriter/dialogs/about.py +70 -160
  75. novelwriter/dialogs/docmerge.py +6 -5
  76. novelwriter/dialogs/docsplit.py +6 -6
  77. novelwriter/dialogs/editlabel.py +1 -1
  78. novelwriter/dialogs/preferences.py +553 -703
  79. novelwriter/dialogs/{projsettings.py → projectsettings.py} +288 -262
  80. novelwriter/dialogs/quotes.py +27 -23
  81. novelwriter/dialogs/wordlist.py +96 -40
  82. novelwriter/enum.py +20 -18
  83. novelwriter/error.py +1 -1
  84. novelwriter/extensions/circularprogress.py +11 -11
  85. novelwriter/extensions/configlayout.py +185 -134
  86. novelwriter/extensions/modified.py +81 -0
  87. novelwriter/extensions/novelselector.py +26 -12
  88. novelwriter/extensions/pagedsidebar.py +14 -16
  89. novelwriter/extensions/simpleprogress.py +5 -5
  90. novelwriter/extensions/statusled.py +8 -8
  91. novelwriter/extensions/switch.py +31 -63
  92. novelwriter/extensions/switchbox.py +1 -1
  93. novelwriter/extensions/versioninfo.py +153 -0
  94. novelwriter/gui/doceditor.py +178 -150
  95. novelwriter/gui/dochighlight.py +63 -92
  96. novelwriter/gui/docviewer.py +49 -51
  97. novelwriter/gui/docviewerpanel.py +72 -24
  98. novelwriter/gui/itemdetails.py +7 -7
  99. novelwriter/gui/mainmenu.py +14 -19
  100. novelwriter/gui/noveltree.py +9 -8
  101. novelwriter/gui/outline.py +98 -75
  102. novelwriter/gui/projtree.py +241 -106
  103. novelwriter/gui/sidebar.py +3 -4
  104. novelwriter/gui/statusbar.py +3 -4
  105. novelwriter/gui/theme.py +69 -70
  106. novelwriter/guimain.py +51 -156
  107. novelwriter/shared.py +15 -1
  108. novelwriter/tools/dictionaries.py +5 -6
  109. novelwriter/tools/manuscript.py +6 -6
  110. novelwriter/tools/manussettings.py +192 -221
  111. novelwriter/tools/noveldetails.py +525 -0
  112. novelwriter/tools/welcome.py +819 -0
  113. novelwriter/tools/writingstats.py +9 -9
  114. novelWriter-2.2.1.dist-info/entry_points.txt +0 -5
  115. novelwriter/assets/images/wizard-back.jpg +0 -0
  116. novelwriter/assets/text/gplv3_en.htm +0 -641
  117. novelwriter/assets/text/release_notes.htm +0 -60
  118. novelwriter/dialogs/projdetails.py +0 -518
  119. novelwriter/dialogs/projload.py +0 -294
  120. novelwriter/dialogs/updates.py +0 -172
  121. novelwriter/extensions/pageddialog.py +0 -130
  122. novelwriter/tools/projwizard.py +0 -478
  123. {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/LICENSE.md +0 -0
  124. {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/WHEEL +0 -0
  125. {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,10 @@ novelWriter – Custom Widget: Config Layout
3
3
  ==========================================
4
4
 
5
5
  File History:
6
- Created: 2020-05-03 [0.4.5]
6
+ Created: 2020-05-03 [0.4.5] NColourLabel
7
+ Created: 2024-01-08 [2.3b1] NScrollableForm
8
+ Created: 2024-01-26 [2.3b1] NScrollablePage
9
+ Created: 2024-01-26 [2.3b1] NFixedPage
7
10
 
8
11
  This file is a part of novelWriter
9
12
  Copyright 2018–2024, Veronica Berglyd Olsen
@@ -23,198 +26,246 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
23
26
  """
24
27
  from __future__ import annotations
25
28
 
26
- from PyQt5.QtGui import QColor, QPalette
29
+ from PyQt5.QtGui import QColor, QFont, QPalette
27
30
  from PyQt5.QtCore import Qt
28
31
  from PyQt5.QtWidgets import (
29
- QAbstractButton, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QSizePolicy,
32
+ QAbstractButton, QFrame, QHBoxLayout, QLabel, QLayout, QScrollArea,
30
33
  QVBoxLayout, QWidget
31
34
  )
32
35
 
33
36
  from novelwriter import CONFIG
34
37
 
35
- FONT_SCALE = 0.9
38
+ DEFAULT_SCALE = 0.9
39
+ RIGHT_TOP = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
40
+ LEFT_TOP = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
36
41
 
37
42
 
38
- class NConfigLayout(QGridLayout):
43
+ class NFixedPage(QFrame):
44
+ """Extension: Fixed Page Widget
39
45
 
40
- def __init__(self) -> None:
41
- super().__init__()
46
+ A custom widget that holds a layout. This is just a wrapper around a
47
+ QFrame that sets the same frame style as the other Page widgets.
48
+ """
42
49
 
43
- self._nextRow = 0
44
- self._helpCol = QColor(0, 0, 0)
45
- self._fontScale = FONT_SCALE
46
- self._itemMap = {}
50
+ def __init__(self, parent: QWidget) -> None:
51
+ super().__init__(parent=parent)
52
+ self.setFrameShadow(QFrame.Shadow.Sunken)
53
+ self.setFrameShape(QFrame.Shape.StyledPanel)
54
+ return
47
55
 
48
- wSp = CONFIG.pxInt(8)
49
- self.setHorizontalSpacing(wSp)
50
- self.setVerticalSpacing(wSp)
51
- self.setColumnStretch(0, 1)
56
+ def setCentralLayout(self, layout: QLayout) -> None:
57
+ """Set a layout as the central object."""
58
+ self.setLayout(layout)
59
+ return
52
60
 
61
+ def setCentralWidget(self, widget: QWidget) -> None:
62
+ """Set a layout as the central object."""
63
+ layout = QHBoxLayout()
64
+ layout.addWidget(widget)
65
+ self.setLayout(layout)
53
66
  return
54
67
 
55
- ##
56
- # Getters and Setters
57
- ##
68
+ # END Class NFixedPage
58
69
 
59
- def setHelpTextStyle(self, color: QColor | list | tuple,
60
- fontScale: float = FONT_SCALE) -> None:
61
- """Set the text color for the help text."""
62
- self._helpCol = color if isinstance(color, QColor) else QColor(*color)
63
- self._fontScale = fontScale
70
+
71
+ class NScrollablePage(QScrollArea):
72
+ """Extension: Scrollable Page Widget
73
+
74
+ A custom widget that holds a layout within a scrollable area.
75
+ """
76
+
77
+ def __init__(self, parent: QWidget) -> None:
78
+ super().__init__(parent=parent)
79
+ self._widget = QWidget(self)
80
+ self.setWidget(self._widget)
81
+ self.setWidgetResizable(True)
82
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
83
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
84
+ self.setFrameShadow(QFrame.Shadow.Sunken)
85
+ self.setFrameShape(QFrame.Shape.StyledPanel)
64
86
  return
65
87
 
66
- def setHelpText(self, row: int, text: str) -> None:
67
- """Set the text for the help label."""
68
- if row in self._itemMap:
69
- qHelp = self._itemMap[row][1]
70
- if isinstance(qHelp, NHelpLabel):
71
- qHelp.setText(text)
88
+ def setCentralLayout(self, layout: QLayout) -> None:
89
+ """Set the central layout of the scroll page."""
90
+ self._widget.setLayout(layout)
72
91
  return
73
92
 
74
- ##
75
- # Class Methods
76
- ##
93
+ # END Class NScrollablePage
77
94
 
78
- def addGroupLabel(self, label: str) -> None:
79
- """Add a text label to separate groups of settings."""
80
- hM = CONFIG.pxInt(4)
81
- qLabel = QLabel("<b>%s</b>" % label)
82
- qLabel.setContentsMargins(0, hM, 0, hM)
83
- self.addWidget(qLabel, self._nextRow, 0, 1, 2, Qt.AlignLeft)
84
- self.setRowStretch(self._nextRow, 0)
85
- self.setRowStretch(self._nextRow + 1, 1)
86
- self._nextRow += 1
87
- return
88
95
 
89
- def addRow(self, label: str, widget: QWidget, helpText: str | None = None,
90
- unit: str | None = None, button: QWidget | None = None) -> int:
91
- """Add a label and a widget as a new row of the grid."""
92
- wSp = CONFIG.pxInt(8)
93
- qLabel = QLabel(label)
94
- qLabel.setIndent(wSp)
95
- qLabel.setBuddy(widget)
96
+ class NScrollableForm(QScrollArea):
97
+ """Extension: Scrollable Form Widget
96
98
 
97
- qHelp = None
98
- if helpText is not None:
99
- qHelp = NHelpLabel(str(helpText), self._helpCol, self._fontScale)
100
- qHelp.setIndent(wSp)
101
- labelBox = QVBoxLayout()
102
- labelBox.addWidget(qLabel)
103
- labelBox.addWidget(qHelp)
104
- labelBox.setSpacing(0)
105
- labelBox.addStretch(1)
106
- self.addLayout(labelBox, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
107
- else:
108
- self.addWidget(qLabel, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
99
+ A custom widget that creates a form within a scrollable area.
100
+ """
109
101
 
110
- if isinstance(unit, str):
111
- controlBox = QHBoxLayout()
112
- controlBox.addWidget(widget, 0, Qt.AlignVCenter)
113
- controlBox.addWidget(QLabel(unit), 0, Qt.AlignVCenter)
114
- controlBox.setSpacing(wSp)
115
- self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
102
+ def __init__(self, parent: QWidget) -> None:
103
+ super().__init__(parent=parent)
104
+ self._helpCol = QColor(0, 0, 0)
105
+ self._fontScale = DEFAULT_SCALE
106
+ self._first = True
107
+ self._indent = CONFIG.pxInt(12)
116
108
 
117
- elif isinstance(button, QAbstractButton):
118
- controlBox = QHBoxLayout()
119
- controlBox.addWidget(widget, 0, Qt.AlignVCenter)
120
- controlBox.addWidget(button, 0, Qt.AlignVCenter)
121
- controlBox.setSpacing(wSp)
122
- self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
109
+ self._sections: dict[int, QLabel] = {}
110
+ self._editable: dict[str, NColourLabel] = {}
111
+ self._index: dict[str, QWidget] = {}
123
112
 
124
- else:
125
- if isinstance(widget, QLineEdit):
126
- qLayout = QHBoxLayout()
127
- qLayout.addWidget(widget)
128
- self.addLayout(qLayout, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
129
- else:
130
- self.addWidget(widget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
113
+ self._layout = QVBoxLayout()
114
+ self._layout.setSpacing(CONFIG.pxInt(12))
131
115
 
132
- self.setRowStretch(self._nextRow, 0)
133
- self.setRowStretch(self._nextRow+1, 1)
116
+ self._widget = QWidget(self)
117
+ self._widget.setLayout(self._layout)
134
118
 
135
- self._itemMap[self._nextRow] = (qLabel, qHelp, widget)
136
- self._nextRow += 1
119
+ self.setWidget(self._widget)
120
+ self.setWidgetResizable(True)
121
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
122
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
123
+ self.setFrameShadow(QFrame.Shadow.Sunken)
124
+ self.setFrameShape(QFrame.Shape.StyledPanel)
137
125
 
138
- return self._nextRow - 1
126
+ return
139
127
 
140
- # END Class NConfigLayout
128
+ ##
129
+ # Properties
130
+ ##
141
131
 
132
+ @property
133
+ def labels(self) -> list[str]:
134
+ return list(self._index.keys())
142
135
 
143
- class NSimpleLayout(QGridLayout):
144
- """Similar to NConfigLayout, but only has a label + widget two
145
- column layout.
146
- """
136
+ ##
137
+ # Setters
138
+ ##
147
139
 
148
- def __init__(self) -> None:
149
- super().__init__()
150
- self._nextRow = 0
140
+ def setHelpTextStyle(self, color: QColor, scale: float = DEFAULT_SCALE) -> None:
141
+ """Set the text color for the help text."""
142
+ self._helpCol = color
143
+ self._fontScale = scale
144
+ return
151
145
 
152
- wSp = CONFIG.pxInt(8)
153
- self.setHorizontalSpacing(wSp)
154
- self.setVerticalSpacing(wSp)
155
- self.setColumnStretch(0, 1)
146
+ def setHelpText(self, key: str, text: str) -> None:
147
+ """Set the text for the help label."""
148
+ if qHelp := self._editable.get(key):
149
+ qHelp.setText(text)
150
+ return
156
151
 
152
+ def setRowIndent(self, indent: int) -> None:
153
+ """Set the indentation of each row."""
154
+ self._indent = max(indent, 0)
157
155
  return
158
156
 
159
157
  ##
160
158
  # Methods
161
159
  ##
162
160
 
163
- def addGroupLabel(self, label: str) -> None:
161
+ def scrollToSection(self, identifier: int) -> None:
162
+ """Scroll to the requested section identifier."""
163
+ if identifier in self._sections:
164
+ yPos = self._sections[identifier].pos().y() - CONFIG.pxInt(8)
165
+ self.verticalScrollBar().setValue(yPos)
166
+ return
167
+
168
+ def scrollToLabel(self, label: str) -> None:
169
+ """Scroll to the requested label."""
170
+ if label in self._index:
171
+ yPos = self._index[label].pos().y() - CONFIG.pxInt(8)
172
+ self.verticalScrollBar().setValue(yPos)
173
+ return
174
+
175
+ def addGroupLabel(self, label: str, identifier: int | None = None) -> None:
164
176
  """Add a text label to separate groups of settings."""
165
177
  hM = CONFIG.pxInt(4)
166
- qLabel = QLabel("<b>%s</b>" % label)
178
+ qLabel = QLabel(f"<b>{label}</b>", self)
167
179
  qLabel.setContentsMargins(0, hM, 0, hM)
168
- self.addWidget(qLabel, self._nextRow, 0, 1, 2, Qt.AlignLeft)
169
- self.setRowStretch(self._nextRow, 0)
170
- self.setRowStretch(self._nextRow + 1, 1)
171
- self._nextRow += 1
180
+ if not self._first:
181
+ self._layout.addSpacing(5*hM)
182
+ self._layout.addWidget(qLabel)
183
+ self._first = False
184
+ if identifier is not None:
185
+ self._sections[identifier] = qLabel
172
186
  return
173
187
 
174
- def addRow(self, label: str, widget: QWidget) -> None:
175
- """Add a label and a widget as a new row of the grid."""
176
- wSp = CONFIG.pxInt(8)
177
- qLabel = QLabel(label)
178
- qLabel.setIndent(wSp)
179
- self.addWidget(qLabel, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
180
-
181
- if isinstance(widget, QLineEdit):
182
- qLayout = QHBoxLayout()
183
- qLayout.addWidget(widget)
184
- self.addLayout(qLayout, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
185
- else:
186
- self.addWidget(widget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
188
+ def addRow(self, label: str, widget: QWidget, helpText: str = "", unit: str | None = None,
189
+ button: QWidget | None = None, editable: str | None = None,
190
+ stretch: tuple[int, int] = (1, 0)) -> None:
191
+ """Add a label and a widget as a new row of the form."""
192
+ row = QHBoxLayout()
193
+ row.setSpacing(CONFIG.pxInt(12))
187
194
 
195
+ qLabel = QLabel(label, self)
196
+ qLabel.setIndent(self._indent)
188
197
  qLabel.setBuddy(widget)
189
198
 
190
- self.setRowStretch(self._nextRow, 0)
191
- self.setRowStretch(self._nextRow+1, 1)
192
- self._nextRow += 1
199
+ if helpText:
200
+ qHelp = NColourLabel(
201
+ str(helpText), color=self._helpCol, parent=self,
202
+ scale=self._fontScale, wrap=True, indent=self._indent
203
+ )
204
+ labelBox = QVBoxLayout()
205
+ labelBox.addWidget(qLabel, 0)
206
+ labelBox.addWidget(qHelp, 1)
207
+ labelBox.setSpacing(0)
208
+ row.addLayout(labelBox, stretch[0])
209
+ if editable:
210
+ self._editable[editable] = qHelp
211
+ else:
212
+ row.addWidget(qLabel, stretch[0])
213
+
214
+ if isinstance(unit, str):
215
+ box = QHBoxLayout()
216
+ box.addWidget(widget, 1)
217
+ box.addWidget(QLabel(unit, self), 0)
218
+ row.addLayout(box, stretch[1])
219
+ elif isinstance(button, QAbstractButton):
220
+ box = QHBoxLayout()
221
+ box.addWidget(widget, 1)
222
+ box.addWidget(button, 0)
223
+ row.addLayout(box, stretch[1])
224
+ else:
225
+ row.addWidget(widget, stretch[1])
226
+
227
+ self._layout.addLayout(row)
228
+ self._index[label.strip()] = widget
229
+ self._first = False
193
230
 
194
231
  return
195
232
 
196
- # END Class NSimpleLayout
233
+ def finalise(self) -> None:
234
+ """Finalise the layout when the form is built."""
235
+ self._layout.addSpacing(CONFIG.pxInt(20))
236
+ self._layout.addStretch(1)
237
+ return
238
+
239
+ # END Class NScrollableForm
197
240
 
198
241
 
199
- class NHelpLabel(QLabel):
242
+ class NColourLabel(QLabel):
243
+ """Extension: A Coloured Label
200
244
 
201
- def __init__(self, text: str, color: QColor | list | tuple,
202
- fontSize: float = FONT_SCALE) -> None:
203
- super().__init__(text)
245
+ A custom widget that draws a label in a specific colour, and
246
+ optionally at a specific size, and word wrapped.
247
+ """
204
248
 
205
- qCol = color if isinstance(color, QColor) else QColor(*color)
249
+ HELP_SCALE = DEFAULT_SCALE
250
+ HEADER_SCALE = 1.25
206
251
 
207
- lblCol = self.palette()
208
- lblCol.setColor(QPalette.WindowText, qCol)
209
- self.setPalette(lblCol)
252
+ def __init__(self, text: str, color: QColor | None = None, parent: QWidget | None = None,
253
+ scale: float = HELP_SCALE, wrap: bool = False, indent: int = 0,
254
+ bold: bool = False) -> None:
255
+ super().__init__(text, parent=parent)
210
256
 
211
- lblFont = self.font()
212
- lblFont.setPointSizeF(fontSize*lblFont.pointSizeF())
213
- self.setFont(lblFont)
257
+ font = self.font()
258
+ font.setPointSizeF(scale*font.pointSizeF())
259
+ font.setWeight(QFont.Weight.Bold if bold else QFont.Weight.Normal)
260
+ if color:
261
+ colour = self.palette()
262
+ colour.setColor(QPalette.WindowText, color)
263
+ self.setPalette(colour)
214
264
 
215
- self.setWordWrap(True)
216
- self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
265
+ self.setFont(font)
266
+ self.setIndent(indent)
267
+ self.setWordWrap(wrap)
217
268
 
218
269
  return
219
270
 
220
- # END Class NHelpLabel
271
+ # END Class NColourLabel
@@ -0,0 +1,81 @@
1
+ """
2
+ novelWriter – Custom Widget: Modified Widgets
3
+ =============================================
4
+
5
+ File History:
6
+ Created: 2024-02-01 [2.3b1] NComboBox
7
+ Created: 2024-02-01 [2.3b1] NSpinBox
8
+ Created: 2024-02-01 [2.3b1] NDoubleSpinBox
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
+ from PyQt5.QtCore import Qt
29
+ from PyQt5.QtGui import QWheelEvent
30
+ from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QWidget
31
+
32
+
33
+ class NComboBox(QComboBox):
34
+
35
+ def __init__(self, parent: QWidget | None = None) -> None:
36
+ super().__init__(parent=parent)
37
+ self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
38
+ return
39
+
40
+ def wheelEvent(self, event: QWheelEvent) -> None:
41
+ if self.hasFocus():
42
+ super().wheelEvent(event)
43
+ else:
44
+ event.ignore()
45
+ return
46
+
47
+ # END Class NComboBox
48
+
49
+
50
+ class NSpinBox(QSpinBox):
51
+
52
+ def __init__(self, parent: QWidget | None = None) -> None:
53
+ super().__init__(parent=parent)
54
+ self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
55
+ return
56
+
57
+ def wheelEvent(self, event: QWheelEvent) -> None:
58
+ if self.hasFocus():
59
+ super().wheelEvent(event)
60
+ else:
61
+ event.ignore()
62
+ return
63
+
64
+ # END Class NSpinBox
65
+
66
+
67
+ class NDoubleSpinBox(QDoubleSpinBox):
68
+
69
+ def __init__(self, parent: QWidget | None = None) -> None:
70
+ super().__init__(parent=parent)
71
+ self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
72
+ return
73
+
74
+ def wheelEvent(self, event: QWheelEvent) -> None:
75
+ if self.hasFocus():
76
+ super().wheelEvent(event)
77
+ else:
78
+ event.ignore()
79
+ return
80
+
81
+ # END Class NDoubleSpinBox
@@ -3,7 +3,7 @@ novelWriter – Custom Widget: Novel Selector
3
3
  ===========================================
4
4
 
5
5
  File History:
6
- Created: 2022-11-17 [2.0]
6
+ Created: 2022-11-17 [2.0] NovelSelector
7
7
 
8
8
  This file is a part of novelWriter
9
9
  Copyright 2018–2024, Veronica Berglyd Olsen
@@ -43,6 +43,8 @@ class NovelSelector(QComboBox):
43
43
  super().__init__(parent=parent)
44
44
  self._blockSignal = False
45
45
  self._firstHandle = None
46
+ self._includeAll = False
47
+ self._listFormat = None
46
48
  self.currentIndexChanged.connect(self._indexChanged)
47
49
  return
48
50
 
@@ -64,17 +66,29 @@ class NovelSelector(QComboBox):
64
66
 
65
67
  def setHandle(self, tHandle: str | None, blockSignal: bool = True) -> None:
66
68
  """Set the currently selected handle."""
67
- self._blockSignal = blockSignal
68
- if tHandle is None:
69
- index = self.count() - 1
70
- else:
71
- index = self.findData(tHandle)
72
- if index >= 0:
69
+ if (index := self.findData(tHandle) if tHandle else (self.count() - 1)) >= 0:
70
+ self._blockSignal = blockSignal
73
71
  self.setCurrentIndex(index)
74
- self._blockSignal = False
72
+ self._blockSignal = False
73
+ return
74
+
75
+ def setIncludeAll(self, value: bool) -> None:
76
+ """Set flag to add an "All Novel Folders" option."""
77
+ self._includeAll = value
78
+ return
79
+
80
+ def setListFormat(self, value: str | None) -> None:
81
+ """Set a format string for the list entries."""
82
+ if value is None or "{0}" in value:
83
+ self._listFormat = value
75
84
  return
76
85
 
77
- def updateList(self, includeAll: bool = False, prefix: str | None = None) -> None:
86
+ ##
87
+ # Public Slots
88
+ ##
89
+
90
+ @pyqtSlot()
91
+ def refreshNovelList(self) -> None:
78
92
  """Rebuild the list of novel items."""
79
93
  self._blockSignal = True
80
94
  self._firstHandle = None
@@ -83,8 +97,8 @@ class NovelSelector(QComboBox):
83
97
  icon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])
84
98
  handle = self.currentData()
85
99
  for tHandle, nwItem in SHARED.project.tree.iterRoots(nwItemClass.NOVEL):
86
- if prefix:
87
- name = prefix.format(nwItem.itemName)
100
+ if self._listFormat:
101
+ name = self._listFormat.format(nwItem.itemName)
88
102
  self.addItem(name, tHandle)
89
103
  else:
90
104
  name = nwItem.itemName
@@ -92,7 +106,7 @@ class NovelSelector(QComboBox):
92
106
  if self._firstHandle is None:
93
107
  self._firstHandle = tHandle
94
108
 
95
- if includeAll:
109
+ if self._includeAll:
96
110
  self.insertSeparator(self.count())
97
111
  self.addItem(icon, self.tr("All Novel Folders"), "")
98
112
 
@@ -26,7 +26,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
26
26
  from __future__ import annotations
27
27
 
28
28
  from PyQt5.QtGui import QColor, QPaintEvent, QPainter, QPolygon
29
- from PyQt5.QtCore import QPoint, QRectF, Qt, pyqtSignal, pyqtSlot
29
+ from PyQt5.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot
30
30
  from PyQt5.QtWidgets import (
31
31
  QAbstractButton, QAction, QButtonGroup, QLabel, QSizePolicy, QStyle,
32
32
  QStyleOptionToolButton, QToolBar, QToolButton, QWidget
@@ -45,10 +45,9 @@ class NPagedSideBar(QToolBar):
45
45
  def __init__(self, parent: QWidget) -> None:
46
46
  super().__init__(parent=parent)
47
47
 
48
- self._buttons = []
49
- self._actions = []
50
48
  self._labelCol = None
51
49
  self._spacerHeight = self.fontMetrics().height() // 2
50
+ self._buttons: dict[int, _NPagedToolButton] = {}
52
51
 
53
52
  self._group = QButtonGroup(self)
54
53
  self._group.setExclusive(True)
@@ -63,17 +62,13 @@ class NPagedSideBar(QToolBar):
63
62
 
64
63
  return
65
64
 
66
- def setLabelColor(self, color: list | QColor) -> None:
67
- """Set the text color for the labels."""
68
- self._labelCol = color if isinstance(color, QColor) else QColor(*color)
69
- return
65
+ def button(self, buttonId: int) -> _NPagedToolButton:
66
+ """Return a specific button."""
67
+ return self._buttons[buttonId]
70
68
 
71
- def addSeparator(self) -> None:
72
- """Add a spacer widget."""
73
- spacer = QWidget(self)
74
- spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
75
- spacer.setFixedHeight(self._spacerHeight)
76
- self.insertWidget(self._stretchAction, spacer)
69
+ def setLabelColor(self, color: QColor) -> None:
70
+ """Set the text color for the labels."""
71
+ self._labelCol = color
77
72
  return
78
73
 
79
74
  def addLabel(self, text: str) -> None:
@@ -91,8 +86,7 @@ class NPagedSideBar(QToolBar):
91
86
  action = self.insertWidget(self._stretchAction, button)
92
87
  self._group.addButton(button, id=buttonId)
93
88
 
94
- self._buttons.append(button)
95
- self._actions.append(action)
89
+ self._buttons[buttonId] = button
96
90
 
97
91
  return action
98
92
 
@@ -136,6 +130,10 @@ class _NPagedToolButton(QToolButton):
136
130
 
137
131
  return
138
132
 
133
+ def sizeHint(self) -> QSize:
134
+ """Return a size hint that includes the arrow."""
135
+ return super().sizeHint() + QSize(4*self._aH, 0)
136
+
139
137
  def paintEvent(self, event: QPaintEvent) -> None:
140
138
  """Overload the paint event to draw a simple, left aligned text
141
139
  label, with a highlight when selected and a transparent base
@@ -162,7 +160,7 @@ class _NPagedToolButton(QToolButton):
162
160
  if self.isChecked():
163
161
  backCol = palette.highlight()
164
162
  paint.setBrush(backCol)
165
- paint.setOpacity(0.5)
163
+ paint.setOpacity(0.35)
166
164
  paint.drawRoundedRect(0, 0, width, height, self._cR, self._cR)
167
165
  textCol = palette.highlightedText().color()
168
166
  else:
@@ -43,11 +43,11 @@ class NProgressSimple(QProgressBar):
43
43
  """Custom painter for the progress bar."""
44
44
  if (value := self.value()) > 0:
45
45
  progress = ceil(self.width()*float(value)/self.maximum())
46
- qPaint = QPainter(self)
47
- qPaint.setRenderHint(QPainter.Antialiasing, True)
48
- qPaint.setPen(self.palette().highlight().color())
49
- qPaint.setBrush(self.palette().highlight())
50
- qPaint.drawRect(0, 0, progress, self.height())
46
+ painter = QPainter(self)
47
+ painter.setRenderHint(QPainter.Antialiasing, True)
48
+ painter.setPen(self.palette().highlight().color())
49
+ painter.setBrush(self.palette().highlight())
50
+ painter.drawRect(0, 0, progress, self.height())
51
51
  return
52
52
 
53
53
  # END Class NProgressSimple
@@ -24,6 +24,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
24
24
  from __future__ import annotations
25
25
 
26
26
  import logging
27
+
27
28
  from typing import Literal
28
29
 
29
30
  from PyQt5.QtGui import QColor, QPaintEvent, QPainter
@@ -64,14 +65,13 @@ class StatusLED(QAbstractButton):
64
65
  return
65
66
 
66
67
  def paintEvent(self, event: QPaintEvent) -> None:
67
- """Drawing the LED."""
68
- qPalette = self.palette()
69
- qPaint = QPainter(self)
70
- qPaint.setRenderHint(QPainter.Antialiasing, True)
71
- qPaint.setPen(qPalette.dark().color())
72
- qPaint.setBrush(self._theCol)
73
- qPaint.setOpacity(1.0)
74
- qPaint.drawEllipse(1, 1, self.width() - 2, self.height() - 2)
68
+ """Draw the LED."""
69
+ painter = QPainter(self)
70
+ painter.setRenderHint(QPainter.Antialiasing, True)
71
+ painter.setPen(self.palette().dark().color())
72
+ painter.setBrush(self._theCol)
73
+ painter.setOpacity(1.0)
74
+ painter.drawEllipse(1, 1, self.width() - 2, self.height() - 2)
75
75
  return
76
76
 
77
77
  # END Class StatusLED