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