novelWriter 2.2rc1__py3-none-any.whl → 2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/RECORD +149 -132
  3. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/WHEEL +1 -1
  4. novelWriter-2.3.dist-info/entry_points.txt +2 -0
  5. novelwriter/__init__.py +11 -6
  6. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  7. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  8. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  9. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  10. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  11. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  12. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  13. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  14. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  15. novelwriter/assets/i18n/project_de_DE.json +1 -0
  16. novelwriter/assets/i18n/project_en_US.json +1 -0
  17. novelwriter/assets/i18n/project_es_419.json +11 -0
  18. novelwriter/assets/i18n/project_fr_FR.json +11 -0
  19. novelwriter/assets/i18n/project_it_IT.json +11 -0
  20. novelwriter/assets/i18n/project_ja_JP.json +2 -1
  21. novelwriter/assets/i18n/project_nb_NO.json +1 -0
  22. novelwriter/assets/i18n/project_nl_NL.json +11 -0
  23. novelwriter/assets/i18n/project_pt_BR.json +11 -0
  24. novelwriter/assets/i18n/project_zh_CN.json +11 -0
  25. novelwriter/assets/icons/typicons_dark/icons.conf +11 -2
  26. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +6 -0
  27. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  28. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
  29. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
  30. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
  31. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
  32. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
  33. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
  34. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
  35. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
  36. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
  37. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  38. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  39. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  40. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +9 -0
  41. novelwriter/assets/icons/typicons_light/icons.conf +11 -2
  42. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +6 -0
  43. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  44. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
  45. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
  46. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
  47. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
  48. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
  49. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
  50. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
  51. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
  52. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
  53. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  54. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  55. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  56. novelwriter/assets/icons/typicons_light/typ_th-list.svg +9 -0
  57. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  58. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  59. novelwriter/assets/images/welcome-dark.jpg +0 -0
  60. novelwriter/assets/images/welcome-light.jpg +0 -0
  61. novelwriter/assets/manual.pdf +0 -0
  62. novelwriter/assets/sample.zip +0 -0
  63. novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
  64. novelwriter/assets/syntax/default_dark.conf +1 -0
  65. novelwriter/assets/syntax/default_light.conf +1 -0
  66. novelwriter/assets/syntax/grey_dark.conf +1 -0
  67. novelwriter/assets/syntax/grey_light.conf +1 -0
  68. novelwriter/assets/syntax/light_owl.conf +1 -0
  69. novelwriter/assets/syntax/night_owl.conf +1 -0
  70. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  71. novelwriter/assets/syntax/solarized_light.conf +1 -0
  72. novelwriter/assets/syntax/tango.conf +23 -0
  73. novelwriter/assets/syntax/tomorrow.conf +1 -0
  74. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  75. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  76. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  77. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  78. novelwriter/assets/text/credits_en.htm +4 -2
  79. novelwriter/assets/themes/cyberpunk_night.conf +29 -0
  80. novelwriter/assets/themes/default_dark.conf +2 -2
  81. novelwriter/assets/themes/default_light.conf +2 -2
  82. novelwriter/common.py +64 -66
  83. novelwriter/config.py +39 -44
  84. novelwriter/constants.py +39 -17
  85. novelwriter/core/buildsettings.py +8 -8
  86. novelwriter/core/coretools.py +198 -157
  87. novelwriter/core/docbuild.py +7 -4
  88. novelwriter/core/document.py +7 -7
  89. novelwriter/core/index.py +90 -57
  90. novelwriter/core/item.py +23 -5
  91. novelwriter/core/options.py +11 -10
  92. novelwriter/core/project.py +73 -47
  93. novelwriter/core/projectdata.py +3 -16
  94. novelwriter/core/projectxml.py +14 -42
  95. novelwriter/core/sessions.py +4 -3
  96. novelwriter/core/spellcheck.py +6 -4
  97. novelwriter/core/status.py +5 -4
  98. novelwriter/core/storage.py +183 -141
  99. novelwriter/core/tohtml.py +6 -4
  100. novelwriter/core/tokenizer.py +110 -83
  101. novelwriter/core/tomd.py +2 -2
  102. novelwriter/core/toodt.py +41 -31
  103. novelwriter/core/tree.py +5 -4
  104. novelwriter/dialogs/about.py +88 -179
  105. novelwriter/dialogs/docmerge.py +30 -20
  106. novelwriter/dialogs/docsplit.py +33 -22
  107. novelwriter/dialogs/editlabel.py +20 -8
  108. novelwriter/dialogs/preferences.py +562 -725
  109. novelwriter/dialogs/{projsettings.py → projectsettings.py} +301 -270
  110. novelwriter/dialogs/quotes.py +47 -36
  111. novelwriter/dialogs/wordlist.py +128 -59
  112. novelwriter/enum.py +25 -22
  113. novelwriter/error.py +2 -2
  114. novelwriter/extensions/circularprogress.py +12 -12
  115. novelwriter/extensions/configlayout.py +185 -146
  116. novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
  117. novelwriter/extensions/modified.py +81 -0
  118. novelwriter/extensions/novelselector.py +27 -13
  119. novelwriter/extensions/pagedsidebar.py +15 -20
  120. novelwriter/extensions/simpleprogress.py +8 -9
  121. novelwriter/extensions/statusled.py +9 -9
  122. novelwriter/extensions/switch.py +32 -64
  123. novelwriter/extensions/switchbox.py +2 -7
  124. novelwriter/extensions/versioninfo.py +153 -0
  125. novelwriter/gui/doceditor.py +250 -214
  126. novelwriter/gui/dochighlight.py +66 -94
  127. novelwriter/gui/docviewer.py +71 -98
  128. novelwriter/gui/docviewerpanel.py +140 -47
  129. novelwriter/gui/editordocument.py +3 -3
  130. novelwriter/gui/itemdetails.py +9 -9
  131. novelwriter/gui/mainmenu.py +47 -47
  132. novelwriter/gui/noveltree.py +53 -61
  133. novelwriter/gui/outline.py +100 -76
  134. novelwriter/gui/projtree.py +246 -112
  135. novelwriter/gui/sidebar.py +9 -8
  136. novelwriter/gui/statusbar.py +49 -7
  137. novelwriter/gui/theme.py +74 -76
  138. novelwriter/guimain.py +175 -330
  139. novelwriter/shared.py +68 -30
  140. novelwriter/tools/dictionaries.py +7 -8
  141. novelwriter/tools/lipsum.py +34 -28
  142. novelwriter/tools/manusbuild.py +3 -4
  143. novelwriter/tools/manuscript.py +25 -32
  144. novelwriter/tools/manussettings.py +194 -225
  145. novelwriter/tools/noveldetails.py +525 -0
  146. novelwriter/tools/welcome.py +819 -0
  147. novelwriter/tools/writingstats.py +26 -13
  148. novelWriter-2.2rc1.dist-info/entry_points.txt +0 -5
  149. novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
  150. novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
  151. novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
  152. novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
  153. novelwriter/assets/images/wizard-back.jpg +0 -0
  154. novelwriter/assets/text/gplv3_en.htm +0 -641
  155. novelwriter/assets/text/release_notes.htm +0 -17
  156. novelwriter/dialogs/projdetails.py +0 -525
  157. novelwriter/dialogs/projload.py +0 -298
  158. novelwriter/dialogs/updates.py +0 -182
  159. novelwriter/extensions/pageddialog.py +0 -130
  160. novelwriter/tools/projwizard.py +0 -478
  161. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/LICENSE.md +0 -0
  162. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/top_level.txt +0 -0
@@ -3,10 +3,13 @@ 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
- Copyright 2018–2023, Veronica Berglyd Olsen
12
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
13
 
11
14
  This program is free software: you can redistribute it and/or modify
12
15
  it under the terms of the GNU General Public License as published by
@@ -23,210 +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
- if isinstance(color, QColor):
63
- self._helpCol = color
64
- else:
65
- self._helpCol = QColor(*color)
66
- self._fontScale = fontScale
67
- return
68
70
 
69
- def setHelpText(self, row: int, text: str) -> None:
70
- """Set the text for the help label."""
71
- if row in self._itemMap:
72
- qHelp = self._itemMap[row][1]
73
- if isinstance(qHelp, NHelpLabel):
74
- qHelp.setText(text)
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)
75
86
  return
76
87
 
77
- def setLabelText(self, row: int, text: str) -> None:
78
- """Set the text for the main label."""
79
- if row in self._itemMap:
80
- self._itemMap[row](0).setText(text)
88
+ def setCentralLayout(self, layout: QLayout) -> None:
89
+ """Set the central layout of the scroll page."""
90
+ self._widget.setLayout(layout)
81
91
  return
82
92
 
83
- ##
84
- # Class Methods
85
- ##
93
+ # END Class NScrollablePage
86
94
 
87
- def addGroupLabel(self, label: str) -> None:
88
- """Add a text label to separate groups of settings."""
89
- hM = CONFIG.pxInt(4)
90
- qLabel = QLabel("<b>%s</b>" % label)
91
- qLabel.setContentsMargins(0, hM, 0, hM)
92
- self.addWidget(qLabel, self._nextRow, 0, 1, 2, Qt.AlignLeft)
93
- self.setRowStretch(self._nextRow, 0)
94
- self.setRowStretch(self._nextRow + 1, 1)
95
- self._nextRow += 1
96
- return
97
95
 
98
- def addRow(self, label: str, widget: QWidget, helpText: str | None = None,
99
- unit: str | None = None, button: QWidget | None = None) -> int:
100
- """Add a label and a widget as a new row of the grid."""
101
- wSp = CONFIG.pxInt(8)
102
- qLabel = QLabel(label)
103
- qLabel.setIndent(wSp)
104
- qLabel.setBuddy(widget)
96
+ class NScrollableForm(QScrollArea):
97
+ """Extension: Scrollable Form Widget
105
98
 
106
- qHelp = None
107
- if helpText is not None:
108
- qHelp = NHelpLabel(str(helpText), self._helpCol, self._fontScale)
109
- qHelp.setIndent(wSp)
110
- labelBox = QVBoxLayout()
111
- labelBox.addWidget(qLabel)
112
- labelBox.addWidget(qHelp)
113
- labelBox.setSpacing(0)
114
- labelBox.addStretch(1)
115
- self.addLayout(labelBox, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
116
- else:
117
- 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
+ """
118
101
 
119
- if isinstance(unit, str):
120
- controlBox = QHBoxLayout()
121
- controlBox.addWidget(widget, 0, Qt.AlignVCenter)
122
- controlBox.addWidget(QLabel(unit), 0, Qt.AlignVCenter)
123
- controlBox.setSpacing(wSp)
124
- 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)
125
108
 
126
- elif isinstance(button, QAbstractButton):
127
- controlBox = QHBoxLayout()
128
- controlBox.addWidget(widget, 0, Qt.AlignVCenter)
129
- controlBox.addWidget(button, 0, Qt.AlignVCenter)
130
- controlBox.setSpacing(wSp)
131
- 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] = {}
132
112
 
133
- else:
134
- if isinstance(widget, QLineEdit):
135
- qLayout = QHBoxLayout()
136
- qLayout.addWidget(widget)
137
- self.addLayout(qLayout, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
138
- else:
139
- self.addWidget(widget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
113
+ self._layout = QVBoxLayout()
114
+ self._layout.setSpacing(CONFIG.pxInt(12))
140
115
 
141
- self.setRowStretch(self._nextRow, 0)
142
- self.setRowStretch(self._nextRow+1, 1)
116
+ self._widget = QWidget(self)
117
+ self._widget.setLayout(self._layout)
143
118
 
144
- self._itemMap[self._nextRow] = (qLabel, qHelp, widget)
145
- 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)
146
125
 
147
- return self._nextRow - 1
126
+ return
148
127
 
149
- # END Class NConfigLayout
128
+ ##
129
+ # Properties
130
+ ##
150
131
 
132
+ @property
133
+ def labels(self) -> list[str]:
134
+ return list(self._index.keys())
151
135
 
152
- class NSimpleLayout(QGridLayout):
153
- """Similar to NConfigLayout, but only has a label + widget two
154
- column layout.
155
- """
136
+ ##
137
+ # Setters
138
+ ##
156
139
 
157
- def __init__(self) -> None:
158
- super().__init__()
159
- 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
160
145
 
161
- wSp = CONFIG.pxInt(8)
162
- self.setHorizontalSpacing(wSp)
163
- self.setVerticalSpacing(wSp)
164
- 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
165
151
 
152
+ def setRowIndent(self, indent: int) -> None:
153
+ """Set the indentation of each row."""
154
+ self._indent = max(indent, 0)
166
155
  return
167
156
 
168
157
  ##
169
158
  # Methods
170
159
  ##
171
160
 
172
- 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:
173
176
  """Add a text label to separate groups of settings."""
174
177
  hM = CONFIG.pxInt(4)
175
- qLabel = QLabel("<b>%s</b>" % label)
178
+ qLabel = QLabel(f"<b>{label}</b>", self)
176
179
  qLabel.setContentsMargins(0, hM, 0, hM)
177
- self.addWidget(qLabel, self._nextRow, 0, 1, 2, Qt.AlignLeft)
178
- self.setRowStretch(self._nextRow, 0)
179
- self.setRowStretch(self._nextRow + 1, 1)
180
- 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
181
186
  return
182
187
 
183
- def addRow(self, label: str, widget: QWidget) -> None:
184
- """Add a label and a widget as a new row of the grid."""
185
- wSp = CONFIG.pxInt(8)
186
- qLabel = QLabel(label)
187
- qLabel.setIndent(wSp)
188
- self.addWidget(qLabel, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
189
-
190
- if isinstance(widget, QLineEdit):
191
- qLayout = QHBoxLayout()
192
- qLayout.addWidget(widget)
193
- self.addLayout(qLayout, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
194
- else:
195
- 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))
196
194
 
195
+ qLabel = QLabel(label, self)
196
+ qLabel.setIndent(self._indent)
197
197
  qLabel.setBuddy(widget)
198
198
 
199
- self.setRowStretch(self._nextRow, 0)
200
- self.setRowStretch(self._nextRow+1, 1)
201
- 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])
202
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
230
+
231
+ return
232
+
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)
203
237
  return
204
238
 
205
- # END Class NSimpleLayout
239
+ # END Class NScrollableForm
206
240
 
207
241
 
208
- class NHelpLabel(QLabel):
242
+ class NColourLabel(QLabel):
243
+ """Extension: A Coloured Label
209
244
 
210
- def __init__(self, text: str, color: QColor | list | tuple,
211
- fontSize: float = FONT_SCALE) -> None:
212
- 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
+ """
213
248
 
214
- if isinstance(color, QColor):
215
- qCol = color
216
- else:
217
- qCol = QColor(*color)
249
+ HELP_SCALE = DEFAULT_SCALE
250
+ HEADER_SCALE = 1.25
218
251
 
219
- lblCol = self.palette()
220
- lblCol.setColor(QPalette.WindowText, qCol)
221
- 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)
222
256
 
223
- lblFont = self.font()
224
- lblFont.setPointSizeF(fontSize*lblFont.pointSizeF())
225
- 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)
226
264
 
227
- self.setWordWrap(True)
228
- self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
265
+ self.setFont(font)
266
+ self.setIndent(indent)
267
+ self.setWordWrap(wrap)
229
268
 
230
269
  return
231
270
 
232
- # END Class NHelpLabel
271
+ # END Class NColourLabel
@@ -1,12 +1,13 @@
1
1
  """
2
- novelWriter – Custom Object: Wheel Event Filter
3
- ===============================================
2
+ novelWriter – Custom Objects: Event Filters
3
+ ===========================================
4
4
 
5
5
  File History:
6
- Created: 2023-08-31 [2.1rc1]
6
+ Created: 2023-08-31 [2.1rc1] WheelEventFilter
7
+ Created: 2023-11-28 [2.2] StatusTipFilter
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
@@ -23,7 +24,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
23
24
  """
24
25
  from __future__ import annotations
25
26
 
26
- from PyQt5.QtGui import QWheelEvent
27
+ from PyQt5.QtGui import QStatusTipEvent, QWheelEvent
27
28
  from PyQt5.QtCore import QEvent, QObject
28
29
  from PyQt5.QtWidgets import QWidget
29
30
 
@@ -63,3 +64,12 @@ class WheelEventFilter(QObject):
63
64
  return False
64
65
 
65
66
  # END Class WheelEventFilter
67
+
68
+
69
+ class StatusTipFilter(QObject):
70
+
71
+ def eventFilter(self, obj: QObject, event: QEvent) -> bool:
72
+ """Filter out status tip events on menus."""
73
+ return True if isinstance(event, QStatusTipEvent) else super().eventFilter(obj, event)
74
+
75
+ # END Class StatusTipFilter
@@ -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,10 +3,10 @@ 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
- 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
@@ -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