novelWriter 2.2.1__py3-none-any.whl → 2.3b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/RECORD +102 -92
  3. novelwriter/__init__.py +4 -4
  4. novelwriter/assets/icons/typicons_dark/icons.conf +6 -0
  5. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  6. novelwriter/assets/icons/typicons_dark/typ_document-add-col.svg +8 -0
  7. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  8. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  9. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  10. novelwriter/assets/icons/typicons_light/icons.conf +6 -0
  11. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  12. novelwriter/assets/icons/typicons_light/typ_document-add-col.svg +8 -0
  13. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  14. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  15. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  16. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  17. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  18. novelwriter/assets/images/welcome-dark.jpg +0 -0
  19. novelwriter/assets/images/welcome-light.jpg +0 -0
  20. novelwriter/assets/manual.pdf +0 -0
  21. novelwriter/assets/sample.zip +0 -0
  22. novelwriter/assets/syntax/default_dark.conf +1 -0
  23. novelwriter/assets/syntax/default_light.conf +1 -0
  24. novelwriter/assets/syntax/grey_dark.conf +1 -0
  25. novelwriter/assets/syntax/grey_light.conf +1 -0
  26. novelwriter/assets/syntax/light_owl.conf +1 -0
  27. novelwriter/assets/syntax/night_owl.conf +1 -0
  28. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  29. novelwriter/assets/syntax/solarized_light.conf +1 -0
  30. novelwriter/assets/syntax/tomorrow.conf +1 -0
  31. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  32. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  33. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  34. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  35. novelwriter/assets/text/credits_en.htm +4 -2
  36. novelwriter/assets/themes/default_dark.conf +2 -2
  37. novelwriter/assets/themes/default_light.conf +2 -2
  38. novelwriter/common.py +48 -37
  39. novelwriter/config.py +36 -41
  40. novelwriter/constants.py +38 -16
  41. novelwriter/core/buildsettings.py +7 -7
  42. novelwriter/core/coretools.py +192 -154
  43. novelwriter/core/docbuild.py +6 -3
  44. novelwriter/core/document.py +6 -6
  45. novelwriter/core/index.py +89 -56
  46. novelwriter/core/item.py +21 -3
  47. novelwriter/core/options.py +8 -7
  48. novelwriter/core/project.py +69 -44
  49. novelwriter/core/projectdata.py +1 -14
  50. novelwriter/core/projectxml.py +13 -41
  51. novelwriter/core/sessions.py +2 -1
  52. novelwriter/core/spellcheck.py +2 -1
  53. novelwriter/core/status.py +2 -1
  54. novelwriter/core/storage.py +178 -140
  55. novelwriter/core/tohtml.py +4 -2
  56. novelwriter/core/tokenizer.py +73 -45
  57. novelwriter/core/toodt.py +40 -30
  58. novelwriter/core/tree.py +3 -2
  59. novelwriter/dialogs/about.py +70 -160
  60. novelwriter/dialogs/docmerge.py +6 -5
  61. novelwriter/dialogs/docsplit.py +6 -6
  62. novelwriter/dialogs/editlabel.py +1 -1
  63. novelwriter/dialogs/preferences.py +553 -703
  64. novelwriter/dialogs/{projsettings.py → projectsettings.py} +288 -262
  65. novelwriter/dialogs/quotes.py +27 -23
  66. novelwriter/dialogs/wordlist.py +96 -40
  67. novelwriter/enum.py +20 -18
  68. novelwriter/error.py +1 -1
  69. novelwriter/extensions/circularprogress.py +11 -11
  70. novelwriter/extensions/configlayout.py +185 -134
  71. novelwriter/extensions/modified.py +81 -0
  72. novelwriter/extensions/novelselector.py +26 -12
  73. novelwriter/extensions/pagedsidebar.py +14 -16
  74. novelwriter/extensions/simpleprogress.py +5 -5
  75. novelwriter/extensions/statusled.py +8 -8
  76. novelwriter/extensions/switch.py +31 -63
  77. novelwriter/extensions/switchbox.py +1 -1
  78. novelwriter/extensions/versioninfo.py +153 -0
  79. novelwriter/gui/doceditor.py +178 -150
  80. novelwriter/gui/dochighlight.py +63 -92
  81. novelwriter/gui/docviewer.py +49 -51
  82. novelwriter/gui/docviewerpanel.py +72 -24
  83. novelwriter/gui/itemdetails.py +7 -7
  84. novelwriter/gui/mainmenu.py +14 -18
  85. novelwriter/gui/noveltree.py +9 -8
  86. novelwriter/gui/outline.py +98 -75
  87. novelwriter/gui/projtree.py +188 -61
  88. novelwriter/gui/sidebar.py +3 -4
  89. novelwriter/gui/statusbar.py +3 -4
  90. novelwriter/gui/theme.py +60 -68
  91. novelwriter/guimain.py +49 -156
  92. novelwriter/shared.py +15 -1
  93. novelwriter/tools/dictionaries.py +5 -6
  94. novelwriter/tools/manuscript.py +6 -6
  95. novelwriter/tools/manussettings.py +192 -221
  96. novelwriter/tools/noveldetails.py +525 -0
  97. novelwriter/tools/welcome.py +802 -0
  98. novelwriter/tools/writingstats.py +9 -9
  99. novelwriter/assets/images/wizard-back.jpg +0 -0
  100. novelwriter/assets/text/gplv3_en.htm +0 -641
  101. novelwriter/assets/text/release_notes.htm +0 -60
  102. novelwriter/dialogs/projdetails.py +0 -518
  103. novelwriter/dialogs/projload.py +0 -294
  104. novelwriter/dialogs/updates.py +0 -172
  105. novelwriter/extensions/pageddialog.py +0 -130
  106. novelwriter/tools/projwizard.py +0 -478
  107. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/LICENSE.md +0 -0
  108. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/WHEEL +0 -0
  109. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/entry_points.txt +0 -0
  110. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,8 @@ novelWriter – GUI Project Settings
3
3
  ==================================
4
4
 
5
5
  File History:
6
- Created: 2018-09-29 [0.0.1] GuiProjectSettings
6
+ Created: 2018-09-29 [0.0.1] GuiProjectSettings
7
+ Rewritten: 2024-01-26 [2.3b1] GuiProjectSettings
7
8
 
8
9
  This file is a part of novelWriter
9
10
  Copyright 2018–2024, Veronica Berglyd Olsen
@@ -25,75 +26,105 @@ from __future__ import annotations
25
26
 
26
27
  import logging
27
28
 
28
- from typing import TYPE_CHECKING
29
-
30
- from PyQt5.QtGui import QCloseEvent, QIcon, QPixmap, QColor
29
+ from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
31
30
  from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
32
31
  from PyQt5.QtWidgets import (
33
- QColorDialog, QComboBox, QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit,
34
- QPushButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, qApp
32
+ QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout, QLineEdit,
33
+ QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
34
+ QWidget, qApp
35
35
  )
36
36
 
37
37
  from novelwriter import CONFIG, SHARED
38
38
  from novelwriter.common import simplified
39
39
  from novelwriter.extensions.switch import NSwitch
40
- from novelwriter.extensions.pageddialog import NPagedDialog
41
- from novelwriter.extensions.configlayout import NConfigLayout
42
-
43
- if TYPE_CHECKING: # pragma: no cover
44
- from novelwriter.guimain import GuiMain
40
+ from novelwriter.extensions.modified import NComboBox
41
+ from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
42
+ from novelwriter.extensions.pagedsidebar import NPagedSideBar
45
43
 
46
44
  logger = logging.getLogger(__name__)
47
45
 
48
46
 
49
- class GuiProjectSettings(NPagedDialog):
47
+ class GuiProjectSettings(QDialog):
50
48
 
51
- TAB_MAIN = 0
52
- TAB_STATUS = 1
53
- TAB_IMPORT = 2
54
- TAB_REPLACE = 3
49
+ PAGE_SETTINGS = 0
50
+ PAGE_STATUS = 1
51
+ PAGE_IMPORT = 2
52
+ PAGE_REPLACE = 3
55
53
 
56
- newProjectSettingsReady = pyqtSignal()
54
+ newProjectSettingsReady = pyqtSignal(bool)
57
55
 
58
- def __init__(self, mainGui: GuiMain, focusTab: int = TAB_MAIN) -> None:
59
- super().__init__(parent=mainGui)
56
+ def __init__(self, parent: QWidget, gotoPage: int = PAGE_SETTINGS) -> None:
57
+ super().__init__(parent=parent)
60
58
 
61
59
  logger.debug("Create: GuiProjectSettings")
62
60
  self.setObjectName("GuiProjectSettings")
63
-
64
- self.mainGui = mainGui
65
- SHARED.project.countStatus()
66
61
  self.setWindowTitle(self.tr("Project Settings"))
67
62
 
68
- wW = CONFIG.pxInt(570)
69
- wH = CONFIG.pxInt(375)
70
- pOptions = SHARED.project.options
71
-
72
- self.setMinimumWidth(wW)
73
- self.setMinimumHeight(wH)
63
+ options = SHARED.project.options
64
+ self.setMinimumSize(CONFIG.pxInt(500), CONFIG.pxInt(400))
74
65
  self.resize(
75
- CONFIG.pxInt(pOptions.getInt("GuiProjectSettings", "winWidth", wW)),
76
- CONFIG.pxInt(pOptions.getInt("GuiProjectSettings", "winHeight", wH))
66
+ CONFIG.pxInt(options.getInt("GuiProjectSettings", "winWidth", CONFIG.pxInt(650))),
67
+ CONFIG.pxInt(options.getInt("GuiProjectSettings", "winHeight", CONFIG.pxInt(500)))
77
68
  )
78
69
 
79
- self.tabMain = GuiProjectEditMain(self)
80
- self.tabStatus = GuiProjectEditStatus(self, True)
81
- self.tabImport = GuiProjectEditStatus(self, False)
82
- self.tabReplace = GuiProjectEditReplace(self)
83
-
84
- self.addTab(self.tabMain, self.tr("Settings"))
85
- self.addTab(self.tabStatus, self.tr("Status"))
86
- self.addTab(self.tabImport, self.tr("Importance"))
87
- self.addTab(self.tabReplace, self.tr("Auto-Replace"))
70
+ # Title
71
+ self.titleLabel = NColourLabel(
72
+ self.tr("Project Settings"), SHARED.theme.helpText,
73
+ parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4)
74
+ )
88
75
 
89
- self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
76
+ # SideBar
77
+ self.sidebar = NPagedSideBar(self)
78
+ self.sidebar.setLabelColor(SHARED.theme.helpText)
79
+ self.sidebar.addButton(self.tr("Settings"), self.PAGE_SETTINGS)
80
+ self.sidebar.addButton(self.tr("Status"), self.PAGE_STATUS)
81
+ self.sidebar.addButton(self.tr("Importance"), self.PAGE_IMPORT)
82
+ self.sidebar.addButton(self.tr("Auto-Replace"), self.PAGE_REPLACE)
83
+ self.sidebar.buttonClicked.connect(self._sidebarClicked)
84
+
85
+ # Buttons
86
+ self.buttonBox = QDialogButtonBox(
87
+ QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel
88
+ )
90
89
  self.buttonBox.accepted.connect(self._doSave)
91
90
  self.buttonBox.rejected.connect(self.close)
92
- self.rejected.connect(self.close)
93
- self.addControls(self.buttonBox)
94
91
 
95
- # Focus Tab
96
- self._focusTab(focusTab)
92
+ # Content
93
+ SHARED.project.countStatus()
94
+
95
+ self.settingsPage = _SettingsPage(self)
96
+ self.statusPage = _StatusPage(self, True)
97
+ self.importPage = _StatusPage(self, False)
98
+ self.replacePage = _ReplacePage(self)
99
+
100
+ self.mainStack = QStackedWidget(self)
101
+ self.mainStack.addWidget(self.settingsPage)
102
+ self.mainStack.addWidget(self.statusPage)
103
+ self.mainStack.addWidget(self.importPage)
104
+ self.mainStack.addWidget(self.replacePage)
105
+
106
+ # Assemble
107
+ self.topBox = QHBoxLayout()
108
+ self.topBox.addWidget(self.titleLabel)
109
+ self.topBox.addStretch(1)
110
+
111
+ self.mainBox = QHBoxLayout()
112
+ self.mainBox.addWidget(self.sidebar)
113
+ self.mainBox.addWidget(self.mainStack)
114
+ self.mainBox.setContentsMargins(0, 0, 0, 0)
115
+
116
+ self.outerBox = QVBoxLayout()
117
+ self.outerBox.addLayout(self.topBox)
118
+ self.outerBox.addLayout(self.mainBox)
119
+ self.outerBox.addWidget(self.buttonBox)
120
+ self.outerBox.setSpacing(CONFIG.pxInt(8))
121
+
122
+ self.setLayout(self.outerBox)
123
+ self.setSizeGripEnabled(True)
124
+
125
+ # Jump to Specified Page
126
+ self.sidebar.setSelected(gotoPage)
127
+ self._sidebarClicked(gotoPage)
97
128
 
98
129
  logger.debug("Ready: GuiProjectSettings")
99
130
 
@@ -103,9 +134,13 @@ class GuiProjectSettings(NPagedDialog):
103
134
  logger.debug("Delete: GuiProjectSettings")
104
135
  return
105
136
 
137
+ ##
138
+ # Events
139
+ ##
140
+
106
141
  def closeEvent(self, event: QCloseEvent) -> None:
107
- """Capture the close event and perform cleanup."""
108
- self._saveGuiSettings()
142
+ """Capture the user closing the window and save settings."""
143
+ self._saveSettings()
109
144
  event.accept()
110
145
  self.deleteLater()
111
146
  return
@@ -114,40 +149,52 @@ class GuiProjectSettings(NPagedDialog):
114
149
  # Private Slots
115
150
  ##
116
151
 
152
+ @pyqtSlot(int)
153
+ def _sidebarClicked(self, pageId: int) -> None:
154
+ """Process a user request to switch page."""
155
+ if pageId == self.PAGE_SETTINGS:
156
+ self.mainStack.setCurrentWidget(self.settingsPage)
157
+ elif pageId == self.PAGE_STATUS:
158
+ self.mainStack.setCurrentWidget(self.statusPage)
159
+ elif pageId == self.PAGE_IMPORT:
160
+ self.mainStack.setCurrentWidget(self.importPage)
161
+ elif pageId == self.PAGE_REPLACE:
162
+ self.mainStack.setCurrentWidget(self.replacePage)
163
+ return
164
+
117
165
  @pyqtSlot()
118
166
  def _doSave(self) -> None:
119
167
  """Save settings and close dialog."""
120
168
  project = SHARED.project
121
- projName = self.tabMain.editName.text()
122
- bookTitle = self.tabMain.editTitle.text()
123
- bookAuthor = self.tabMain.editAuthor.text()
124
- projLang = self.tabMain.projLang.currentData()
125
- spellLang = self.tabMain.spellLang.currentData()
126
- doBackup = not self.tabMain.doBackup.isChecked()
169
+ projName = self.settingsPage.projName.text()
170
+ projAuthor = self.settingsPage.projAuthor.text()
171
+ projLang = self.settingsPage.projLang.currentData()
172
+ spellLang = self.settingsPage.spellLang.currentData()
173
+ doBackup = not self.settingsPage.doBackup.isChecked()
127
174
 
128
175
  project.data.setName(projName)
129
- project.data.setTitle(bookTitle)
130
- project.data.setAuthor(bookAuthor)
176
+ project.data.setAuthor(projAuthor)
131
177
  project.data.setDoBackup(doBackup)
132
178
  project.data.setSpellLang(spellLang)
133
179
  project.setProjectLang(projLang)
134
180
 
135
- if self.tabStatus.colChanged:
136
- newList, delList = self.tabStatus.getNewList()
181
+ rebuildTrees = False
182
+
183
+ if self.statusPage.wasChanged:
184
+ newList, delList = self.statusPage.getNewList()
137
185
  project.setStatusColours(newList, delList)
186
+ rebuildTrees = True
138
187
 
139
- if self.tabImport.colChanged:
140
- newList, delList = self.tabImport.getNewList()
188
+ if self.importPage.wasChanged:
189
+ newList, delList = self.importPage.getNewList()
141
190
  project.setImportColours(newList, delList)
191
+ rebuildTrees = True
142
192
 
143
- if self.tabStatus.colChanged or self.tabImport.colChanged:
144
- self.mainGui.rebuildTrees()
145
-
146
- if self.tabReplace.arChanged:
147
- newList = self.tabReplace.getNewList()
193
+ if self.replacePage.wasChanged:
194
+ newList = self.replacePage.getNewList()
148
195
  project.data.setAutoReplace(newList)
149
196
 
150
- self.newProjectSettingsReady.emit()
197
+ self.newProjectSettingsReady.emit(rebuildTrees)
151
198
  qApp.processEvents()
152
199
  self.close()
153
200
 
@@ -157,134 +204,103 @@ class GuiProjectSettings(NPagedDialog):
157
204
  # Internal Functions
158
205
  ##
159
206
 
160
- def _focusTab(self, tab: int) -> None:
161
- """Change which is the focused tab."""
162
- if tab == self.TAB_MAIN:
163
- self.setCurrentWidget(self.tabMain)
164
- elif tab == self.TAB_STATUS:
165
- self.setCurrentWidget(self.tabStatus)
166
- elif tab == self.TAB_IMPORT:
167
- self.setCurrentWidget(self.tabImport)
168
- elif tab == self.TAB_REPLACE:
169
- self.setCurrentWidget(self.tabReplace)
170
- return
171
-
172
- def _saveGuiSettings(self) -> None:
207
+ def _saveSettings(self) -> None:
173
208
  """Save GUI settings."""
174
209
  winWidth = CONFIG.rpxInt(self.width())
175
210
  winHeight = CONFIG.rpxInt(self.height())
176
- replaceColW = CONFIG.rpxInt(self.tabReplace.listBox.columnWidth(0))
177
- statusColW = CONFIG.rpxInt(self.tabStatus.listBox.columnWidth(0))
178
- importColW = CONFIG.rpxInt(self.tabImport.listBox.columnWidth(0))
211
+ statusColW = CONFIG.rpxInt(self.statusPage.columnWidth())
212
+ importColW = CONFIG.rpxInt(self.importPage.columnWidth())
213
+ replaceColW = CONFIG.rpxInt(self.replacePage.columnWidth())
179
214
 
180
215
  logger.debug("Saving State: GuiProjectSettings")
181
- pOptions = SHARED.project.options
182
- pOptions.setValue("GuiProjectSettings", "winWidth", winWidth)
183
- pOptions.setValue("GuiProjectSettings", "winHeight", winHeight)
184
- pOptions.setValue("GuiProjectSettings", "replaceColW", replaceColW)
185
- pOptions.setValue("GuiProjectSettings", "statusColW", statusColW)
186
- pOptions.setValue("GuiProjectSettings", "importColW", importColW)
216
+ options = SHARED.project.options
217
+ options.setValue("GuiProjectSettings", "winWidth", winWidth)
218
+ options.setValue("GuiProjectSettings", "winHeight", winHeight)
219
+ options.setValue("GuiProjectSettings", "statusColW", statusColW)
220
+ options.setValue("GuiProjectSettings", "importColW", importColW)
221
+ options.setValue("GuiProjectSettings", "replaceColW", replaceColW)
187
222
 
188
223
  return
189
224
 
190
225
  # END Class GuiProjectSettings
191
226
 
192
227
 
193
- class GuiProjectEditMain(QWidget):
228
+ class _SettingsPage(NScrollableForm):
194
229
 
195
230
  def __init__(self, parent: QWidget) -> None:
196
231
  super().__init__(parent=parent)
197
232
 
198
- # The Form
199
- self.mainForm = NConfigLayout()
200
- self.mainForm.setHelpTextStyle(SHARED.theme.helpText)
201
- self.setLayout(self.mainForm)
202
-
203
- self.mainForm.addGroupLabel(self.tr("Project Settings"))
204
-
205
- xW = CONFIG.pxInt(250)
206
- pData = SHARED.project.data
207
-
208
- self.editName = QLineEdit()
209
- self.editName.setMaxLength(200)
210
- self.editName.setMaximumWidth(xW)
211
- self.editName.setText(pData.name)
212
- self.mainForm.addRow(
213
- self.tr("Project name"),
214
- self.editName,
215
- self.tr("Should be set only once.")
233
+ xW = CONFIG.pxInt(200)
234
+ data = SHARED.project.data
235
+ self.setHelpTextStyle(SHARED.theme.helpText)
236
+ self.setRowIndent(0)
237
+
238
+ # Project Name
239
+ self.projName = QLineEdit(self)
240
+ self.projName.setMaxLength(200)
241
+ self.projName.setMinimumWidth(xW)
242
+ self.projName.setText(data.name)
243
+ self.addRow(
244
+ self.tr("Project name"), self.projName,
245
+ self.tr("Changing this will affect the backup path."),
246
+ stretch=(3, 2)
216
247
  )
217
248
 
218
- self.editTitle = QLineEdit()
219
- self.editTitle.setMaxLength(200)
220
- self.editTitle.setMaximumWidth(xW)
221
- self.editTitle.setText(pData.title)
222
- self.mainForm.addRow(
223
- self.tr("Novel title"),
224
- self.editTitle,
225
- self.tr("Change whenever you want!")
249
+ # Project Author
250
+ self.projAuthor = QLineEdit(self)
251
+ self.projAuthor.setMaxLength(200)
252
+ self.projAuthor.setMinimumWidth(xW)
253
+ self.projAuthor.setText(data.author)
254
+ self.addRow(
255
+ self.tr("Author(s)"), self.projAuthor,
256
+ self.tr("Only used when building the manuscript."),
257
+ stretch=(3, 2)
226
258
  )
227
259
 
228
- self.editAuthor = QLineEdit()
229
- self.editAuthor.setMaxLength(200)
230
- self.editAuthor.setMaximumWidth(xW)
231
- self.editAuthor.setText(pData.author)
232
- self.mainForm.addRow(
233
- self.tr("Author(s)"),
234
- self.editAuthor,
235
- self.tr("Change whenever you want!")
236
- )
237
-
238
- self.projLang = QComboBox(self)
239
- self.projLang.setMaximumWidth(xW)
260
+ # Project Language
261
+ self.projLang = NComboBox(self)
262
+ self.projLang.setMinimumWidth(xW)
240
263
  for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ):
241
264
  self.projLang.addItem(language, tag)
242
- self.mainForm.addRow(
243
- self.tr("Project language"),
244
- self.projLang,
245
- self.tr("Used when building the manuscript.")
265
+ self.addRow(
266
+ self.tr("Project language"), self.projLang,
267
+ self.tr("Only used when building the manuscript."),
268
+ stretch=(3, 2)
246
269
  )
270
+ if (idx := self.projLang.findData(data.language)) != -1:
271
+ self.projLang.setCurrentIndex(idx)
247
272
 
248
- langIdx = 0
249
- if pData.language is not None:
250
- langIdx = self.projLang.findData(pData.language)
251
- if langIdx == -1:
252
- langIdx = self.projLang.findData("en_GB")
253
- if langIdx != -1:
254
- self.projLang.setCurrentIndex(langIdx)
255
-
256
- self.spellLang = QComboBox(self)
257
- self.spellLang.setMaximumWidth(xW)
273
+ # Spell Check Language
274
+ self.spellLang = NComboBox(self)
275
+ self.spellLang.setMinimumWidth(xW)
258
276
  self.spellLang.addItem(self.tr("Default"), "None")
259
277
  if CONFIG.hasEnchant:
260
278
  for tag, language in SHARED.spelling.listDictionaries():
261
279
  self.spellLang.addItem(language, tag)
262
- self.mainForm.addRow(
263
- self.tr("Spell check language"),
264
- self.spellLang,
265
- self.tr("Overrides main preferences.")
280
+ self.addRow(
281
+ self.tr("Spell check language"), self.spellLang,
282
+ self.tr("Overrides main preferences."),
283
+ stretch=(3, 2)
266
284
  )
285
+ if (idx := self.spellLang.findData(data.spellLang)) != -1:
286
+ self.spellLang.setCurrentIndex(idx)
267
287
 
268
- langIdx = 0
269
- if pData.spellLang is not None:
270
- langIdx = self.spellLang.findData(pData.spellLang)
271
- if langIdx != -1:
272
- self.spellLang.setCurrentIndex(langIdx)
273
-
288
+ # Backup on Close
274
289
  self.doBackup = NSwitch(self)
275
- self.doBackup.setChecked(not pData.doBackup)
276
- self.mainForm.addRow(
277
- self.tr("No backup on close"),
278
- self.doBackup,
290
+ self.doBackup.setChecked(not data.doBackup)
291
+ self.addRow(
292
+ self.tr("Disable backup on close"), self.doBackup,
279
293
  self.tr("Overrides main preferences.")
280
294
  )
281
295
 
296
+ self.finalise()
297
+
282
298
  return
283
299
 
284
- # END Class GuiProjectEditMain
300
+ # END Class _SettingsPage
285
301
 
286
302
 
287
- class GuiProjectEditStatus(QWidget):
303
+ class _StatusPage(NFixedPage):
288
304
 
289
305
  COL_LABEL = 0
290
306
  COL_USAGE = 1
@@ -297,57 +313,55 @@ class GuiProjectEditStatus(QWidget):
297
313
  super().__init__(parent=parent)
298
314
 
299
315
  if isStatus:
300
- self.theStatus = SHARED.project.data.itemStatus
301
- pageLabel = self.tr("Novel File Status Levels")
316
+ status = SHARED.project.data.itemStatus
317
+ pageLabel = self.tr("Novel Document Status Levels")
302
318
  colSetting = "statusColW"
303
319
  else:
304
- self.theStatus = SHARED.project.data.itemImport
305
- pageLabel = self.tr("Note File Importance Levels")
320
+ status = SHARED.project.data.itemImport
321
+ pageLabel = self.tr("Project Note Importance Levels")
306
322
  colSetting = "importColW"
307
323
 
308
324
  wCol0 = CONFIG.pxInt(
309
325
  SHARED.project.options.getInt("GuiProjectSettings", colSetting, 130)
310
326
  )
311
327
 
312
- self.colDeleted = []
313
- self.colChanged = False
314
- self.selColour = QColor(100, 100, 100)
328
+ self._changed = False
329
+ self._colDeleted = []
330
+ self._selColour = QColor(100, 100, 100)
315
331
 
316
332
  self.iPx = SHARED.theme.baseIconSize
317
333
 
318
- # The List
319
- # ========
334
+ # Title
335
+ self.pageTitle = NColourLabel(
336
+ pageLabel, SHARED.theme.helpText, parent=self,
337
+ scale=NColourLabel.HEADER_SCALE
338
+ )
320
339
 
321
- self.listBox = QTreeWidget()
322
- self.listBox.setHeaderLabels([
323
- self.tr("Label"), self.tr("Usage"),
324
- ])
340
+ # List Box
341
+ self.listBox = QTreeWidget(self)
342
+ self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")])
325
343
  self.listBox.itemSelectionChanged.connect(self._selectedItem)
326
344
  self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
327
345
  self.listBox.setIndentation(0)
328
346
 
329
- for key, entry in self.theStatus.items():
347
+ for key, entry in status.items():
330
348
  self._addItem(key, entry["name"], entry["cols"], entry["count"])
331
349
 
332
350
  # List Controls
333
- # =============
334
-
335
- self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
351
+ self.addButton = QPushButton(SHARED.theme.getIcon("add"), "", self)
336
352
  self.addButton.clicked.connect(self._newItem)
337
353
 
338
- self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "")
354
+ self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "", self)
339
355
  self.delButton.clicked.connect(self._delItem)
340
356
 
341
- self.upButton = QPushButton(SHARED.theme.getIcon("up"), "")
357
+ self.upButton = QPushButton(SHARED.theme.getIcon("up"), "", self)
342
358
  self.upButton.clicked.connect(lambda: self._moveItem(-1))
343
359
 
344
- self.dnButton = QPushButton(SHARED.theme.getIcon("down"), "")
360
+ self.dnButton = QPushButton(SHARED.theme.getIcon("down"), "", self)
345
361
  self.dnButton.clicked.connect(lambda: self._moveItem(1))
346
362
 
347
363
  # Edit Form
348
- # =========
349
-
350
- self.editName = QLineEdit()
364
+ self.editName = QLineEdit(self)
351
365
  self.editName.setMaxLength(40)
352
366
  self.editName.setPlaceholderText(self.tr("Select item to edit"))
353
367
  self.editName.setEnabled(False)
@@ -364,8 +378,6 @@ class GuiProjectEditStatus(QWidget):
364
378
  self.saveButton.clicked.connect(self._saveItem)
365
379
 
366
380
  # Assemble
367
- # ========
368
-
369
381
  self.listControls = QVBoxLayout()
370
382
  self.listControls.addWidget(self.addButton)
371
383
  self.listControls.addWidget(self.delButton)
@@ -387,16 +399,25 @@ class GuiProjectEditStatus(QWidget):
387
399
  self.innerBox.addLayout(self.listControls)
388
400
 
389
401
  self.outerBox = QVBoxLayout()
390
- self.outerBox.addWidget(QLabel("<b>%s</b>" % pageLabel))
402
+ self.outerBox.addWidget(self.pageTitle)
391
403
  self.outerBox.addLayout(self.innerBox)
392
404
 
393
- self.setLayout(self.outerBox)
405
+ self.setCentralLayout(self.outerBox)
394
406
 
395
407
  return
396
408
 
409
+ @property
410
+ def wasChanged(self) -> bool:
411
+ """The user changed these settings."""
412
+ return self._changed
413
+
414
+ ##
415
+ # Methods
416
+ ##
417
+
397
418
  def getNewList(self) -> tuple[list, list]:
398
419
  """Return list of entries."""
399
- if self.colChanged:
420
+ if self._changed:
400
421
  newList = []
401
422
  for n in range(self.listBox.topLevelItemCount()):
402
423
  item = self.listBox.topLevelItem(n)
@@ -406,10 +427,13 @@ class GuiProjectEditStatus(QWidget):
406
427
  "name": item.text(self.COL_LABEL),
407
428
  "cols": item.data(self.COL_LABEL, self.COL_ROLE),
408
429
  })
409
- return newList, self.colDeleted
410
-
430
+ return newList, self._colDeleted
411
431
  return [], []
412
432
 
433
+ def columnWidth(self) -> int:
434
+ """Return the size of the header column."""
435
+ return self.listBox.columnWidth(0)
436
+
413
437
  ##
414
438
  # Private Slots
415
439
  ##
@@ -417,12 +441,12 @@ class GuiProjectEditStatus(QWidget):
417
441
  @pyqtSlot()
418
442
  def _selectColour(self) -> None:
419
443
  """Open a dialog to select the status icon colour."""
420
- if self.selColour is not None:
444
+ if self._selColour is not None:
421
445
  newCol = QColorDialog.getColor(
422
- self.selColour, self, self.tr("Select Colour")
446
+ self._selColour, self, self.tr("Select Colour")
423
447
  )
424
448
  if newCol.isValid():
425
- self.selColour = newCol
449
+ self._selColour = newCol
426
450
  pixmap = QPixmap(self.iPx, self.iPx)
427
451
  pixmap.fill(newCol)
428
452
  self.colButton.setIcon(QIcon(pixmap))
@@ -433,7 +457,7 @@ class GuiProjectEditStatus(QWidget):
433
457
  def _newItem(self) -> None:
434
458
  """Create a new status item."""
435
459
  self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
436
- self.colChanged = True
460
+ self._changed = True
437
461
  return
438
462
 
439
463
  @pyqtSlot()
@@ -446,8 +470,8 @@ class GuiProjectEditStatus(QWidget):
446
470
  SHARED.error(self.tr("Cannot delete a status item that is in use."))
447
471
  else:
448
472
  self.listBox.takeTopLevelItem(iRow)
449
- self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
450
- self.colChanged = True
473
+ self._colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
474
+ self._changed = True
451
475
  return
452
476
 
453
477
  @pyqtSlot()
@@ -458,9 +482,9 @@ class GuiProjectEditStatus(QWidget):
458
482
  selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
459
483
  selItem.setIcon(self.COL_LABEL, self.colButton.icon())
460
484
  selItem.setData(self.COL_LABEL, self.COL_ROLE, (
461
- self.selColour.red(), self.selColour.green(), self.selColour.blue()
485
+ self._selColour.red(), self._selColour.green(), self._selColour.blue()
462
486
  ))
463
- self.colChanged = True
487
+ self._changed = True
464
488
  return
465
489
 
466
490
  @pyqtSlot()
@@ -474,7 +498,7 @@ class GuiProjectEditStatus(QWidget):
474
498
  name = selItem.text(self.COL_LABEL)
475
499
  pixmap = QPixmap(self.iPx, self.iPx)
476
500
  pixmap.fill(QColor(*cols))
477
- self.selColour = QColor(*cols)
501
+ self._selColour = QColor(*cols)
478
502
  self.editName.setText(name)
479
503
  self.colButton.setIcon(QIcon(pixmap))
480
504
  self.editName.selectAll()
@@ -485,7 +509,7 @@ class GuiProjectEditStatus(QWidget):
485
509
  else:
486
510
  pixmap = QPixmap(self.iPx, self.iPx)
487
511
  pixmap.fill(QColor(100, 100, 100))
488
- self.selColour = QColor(100, 100, 100)
512
+ self._selColour = QColor(100, 100, 100)
489
513
  self.editName.setText("")
490
514
  self.colButton.setIcon(QIcon(pixmap))
491
515
  self.editName.setEnabled(False)
@@ -498,16 +522,16 @@ class GuiProjectEditStatus(QWidget):
498
522
  ##
499
523
 
500
524
  def _addItem(self, key: str | None, name: str,
501
- cols: tuple[int, int, int], count: int) -> None:
525
+ colour: tuple[int, int, int], count: int) -> None:
502
526
  """Add a status item to the list."""
503
527
  pixmap = QPixmap(self.iPx, self.iPx)
504
- pixmap.fill(QColor(*cols))
528
+ pixmap.fill(QColor(*colour))
505
529
 
506
530
  item = QTreeWidgetItem()
507
531
  item.setText(self.COL_LABEL, name)
508
532
  item.setIcon(self.COL_LABEL, QIcon(pixmap))
509
533
  item.setData(self.COL_LABEL, self.KEY_ROLE, key)
510
- item.setData(self.COL_LABEL, self.COL_ROLE, cols)
534
+ item.setData(self.COL_LABEL, self.COL_ROLE, colour)
511
535
  item.setData(self.COL_LABEL, self.NUM_ROLE, count)
512
536
  item.setText(self.COL_USAGE, self._usageString(count))
513
537
 
@@ -533,30 +557,29 @@ class GuiProjectEditStatus(QWidget):
533
557
 
534
558
  if cItem is not None:
535
559
  cItem.setSelected(True)
536
- self.colChanged = True
560
+ self._changed = True
537
561
 
538
562
  return
539
563
 
540
564
  def _getSelectedItem(self) -> QTreeWidgetItem | None:
541
565
  """Get the currently selected item."""
542
- selItem = self.listBox.selectedItems()
543
- if len(selItem) > 0:
544
- return selItem[0]
566
+ if items := self.listBox.selectedItems():
567
+ return items[0]
545
568
  return None
546
569
 
547
- def _usageString(self, nUse: int) -> str:
570
+ def _usageString(self, count: int) -> str:
548
571
  """Generate usage string."""
549
- if nUse == 0:
572
+ if count == 0:
550
573
  return self.tr("Not in use")
551
- elif nUse == 1:
574
+ elif count == 1:
552
575
  return self.tr("Used once")
553
576
  else:
554
- return self.tr("Used by {0} items").format(nUse)
577
+ return self.tr("Used by {0} items").format(count)
555
578
 
556
- # END Class GuiProjectEditStatus
579
+ # END Class _StatusPage
557
580
 
558
581
 
559
- class GuiProjectEditReplace(QWidget):
582
+ class _ReplacePage(NFixedPage):
560
583
 
561
584
  COL_KEY = 0
562
585
  COL_REPL = 1
@@ -564,24 +587,24 @@ class GuiProjectEditReplace(QWidget):
564
587
  def __init__(self, parent: QWidget) -> None:
565
588
  super().__init__(parent=parent)
566
589
 
567
- self.arChanged = False
590
+ self._changed = False
568
591
 
569
592
  wCol0 = CONFIG.pxInt(
570
593
  SHARED.project.options.getInt("GuiProjectSettings", "replaceColW", 130)
571
594
  )
572
- pageLabel = self.tr("Text Replace List for Preview and Export")
573
595
 
574
- # List Box
575
- # ========
596
+ # Title
597
+ self.pageTitle = NColourLabel(
598
+ self.tr("Text Auto-Replace for Preview and Build"),
599
+ SHARED.theme.helpText, parent=self, scale=NColourLabel.HEADER_SCALE
600
+ )
576
601
 
602
+ # List Box
577
603
  self.listBox = QTreeWidget()
578
- self.listBox.setHeaderLabels([
579
- self.tr("Keyword"),
580
- self.tr("Replace With"),
581
- ])
582
- self.listBox.itemSelectionChanged.connect(self._selectedItem)
604
+ self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")])
583
605
  self.listBox.setColumnWidth(self.COL_KEY, wCol0)
584
606
  self.listBox.setIndentation(0)
607
+ self.listBox.itemSelectionChanged.connect(self._selectedItem)
585
608
 
586
609
  for aKey, aVal in SHARED.project.data.autoReplace.items():
587
610
  newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
@@ -591,8 +614,6 @@ class GuiProjectEditReplace(QWidget):
591
614
  self.listBox.setSortingEnabled(True)
592
615
 
593
616
  # List Controls
594
- # =============
595
-
596
617
  self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
597
618
  self.addButton.clicked.connect(self._addEntry)
598
619
 
@@ -600,14 +621,12 @@ class GuiProjectEditReplace(QWidget):
600
621
  self.delButton.clicked.connect(self._delEntry)
601
622
 
602
623
  # Edit Form
603
- # =========
604
-
605
- self.editKey = QLineEdit()
624
+ self.editKey = QLineEdit(self)
606
625
  self.editKey.setPlaceholderText(self.tr("Select item to edit"))
607
626
  self.editKey.setEnabled(False)
608
627
  self.editKey.setMaxLength(40)
609
628
 
610
- self.editValue = QLineEdit()
629
+ self.editValue = QLineEdit(self)
611
630
  self.editValue.setEnabled(False)
612
631
  self.editValue.setMaxLength(80)
613
632
 
@@ -615,8 +634,6 @@ class GuiProjectEditReplace(QWidget):
615
634
  self.saveButton.clicked.connect(self._saveEntry)
616
635
 
617
636
  # Assemble
618
- # ========
619
-
620
637
  self.listControls = QVBoxLayout()
621
638
  self.listControls.addWidget(self.addButton)
622
639
  self.listControls.addWidget(self.delButton)
@@ -636,50 +653,61 @@ class GuiProjectEditReplace(QWidget):
636
653
  self.innerBox.addLayout(self.listControls)
637
654
 
638
655
  self.outerBox = QVBoxLayout()
639
- self.outerBox.addWidget(QLabel("<b>%s</b>" % pageLabel))
656
+ self.outerBox.addWidget(self.pageTitle)
640
657
  self.outerBox.addLayout(self.innerBox)
641
658
 
642
- self.setLayout(self.outerBox)
659
+ self.setCentralLayout(self.outerBox)
643
660
 
644
661
  return
645
662
 
663
+ @property
664
+ def wasChanged(self) -> bool:
665
+ """The user changed these settings."""
666
+ return self._changed
667
+
668
+ ##
669
+ # Methods
670
+ ##
671
+
646
672
  def getNewList(self) -> dict:
647
673
  """Extract the list from the widget."""
648
674
  new = {}
649
675
  for n in range(self.listBox.topLevelItemCount()):
650
- tItem = self.listBox.topLevelItem(n)
651
- if tItem is not None:
676
+ if tItem := self.listBox.topLevelItem(n):
652
677
  aKey = self._stripNotAllowed(tItem.text(0))
653
678
  aVal = tItem.text(1)
654
679
  if len(aKey) > 0:
655
680
  new[aKey] = aVal
656
681
  return new
657
682
 
683
+ def columnWidth(self) -> int:
684
+ """Return the size of the header column."""
685
+ return self.listBox.columnWidth(0)
686
+
658
687
  ##
659
- # Internal Functions
688
+ # Private Slots
660
689
  ##
661
690
 
662
- def _selectedItem(self) -> bool:
691
+ @pyqtSlot()
692
+ def _selectedItem(self) -> None:
663
693
  """Extract the details from the selected item and populate the
664
694
  edit form.
665
695
  """
666
- selItem = self._getSelectedItem()
667
- if selItem is None:
668
- return False
669
- editKey = self._stripNotAllowed(selItem.text(0))
670
- editVal = selItem.text(1)
671
- self.editKey.setText(editKey)
672
- self.editValue.setText(editVal)
673
- self.editKey.setEnabled(True)
674
- self.editValue.setEnabled(True)
675
- self.editKey.selectAll()
676
- self.editKey.setFocus()
677
- return True
696
+ if selItem := self._getSelectedItem():
697
+ editKey = self._stripNotAllowed(selItem.text(0))
698
+ editVal = selItem.text(1)
699
+ self.editKey.setText(editKey)
700
+ self.editValue.setText(editVal)
701
+ self.editKey.setEnabled(True)
702
+ self.editValue.setEnabled(True)
703
+ self.editKey.selectAll()
704
+ self.editKey.setFocus()
705
+ return
678
706
 
707
+ @pyqtSlot()
679
708
  def _saveEntry(self) -> None:
680
709
  """Save the form data into the list widget."""
681
- selItem = self._getSelectedItem()
682
- if selItem:
710
+ if selItem := self._getSelectedItem():
683
711
  newKey = self.editKey.text()
684
712
  newVal = self.editValue.text()
685
713
  saveKey = self._stripNotAllowed(newKey)
@@ -691,38 +719,36 @@ class GuiProjectEditReplace(QWidget):
691
719
  self.editKey.setEnabled(False)
692
720
  self.editValue.setEnabled(False)
693
721
  self.listBox.clearSelection()
694
- self.arChanged = True
722
+ self._changed = True
695
723
  return
696
724
 
725
+ @pyqtSlot()
697
726
  def _addEntry(self) -> None:
698
727
  """Add a new list entry."""
699
728
  saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
700
- newVal = ""
701
- newItem = QTreeWidgetItem([saveKey, newVal])
702
- self.listBox.addTopLevelItem(newItem)
729
+ self.listBox.addTopLevelItem(QTreeWidgetItem([saveKey, ""]))
703
730
  return
704
731
 
732
+ @pyqtSlot()
705
733
  def _delEntry(self) -> None:
706
734
  """Delete the selected entry."""
707
- selItem = self._getSelectedItem()
708
- if selItem:
735
+ if selItem := self._getSelectedItem():
709
736
  self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
710
- self.arChanged = True
737
+ self._changed = True
711
738
  return
712
739
 
740
+ ##
741
+ # Internal Functions
742
+ ##
743
+
713
744
  def _getSelectedItem(self) -> QTreeWidgetItem | None:
714
745
  """Extract the currently selected item."""
715
- selItem = self.listBox.selectedItems()
716
- if len(selItem) == 0:
717
- return None
718
- return selItem[0]
746
+ if items := self.listBox.selectedItems():
747
+ return items[0]
748
+ return None
719
749
 
720
750
  def _stripNotAllowed(self, key: str) -> str:
721
751
  """Clean up the replace key string."""
722
- result = ""
723
- for c in key:
724
- if c.isalnum():
725
- result += c
726
- return result
752
+ return "".join(c for c in key if c.isalnum())
727
753
 
728
- # END Class GuiProjectEditReplace
754
+ # END Class _ReplacePage