novelWriter 2.4.3__py3-none-any.whl → 2.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/METADATA +4 -5
  2. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/RECORD +122 -112
  3. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +33 -39
  5. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  6. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  7. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  8. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  9. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  10. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  11. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  12. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  13. novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
  14. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  15. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  16. novelwriter/assets/i18n/project_en_GB.json +1 -0
  17. novelwriter/assets/i18n/project_pl_PL.json +116 -0
  18. novelwriter/assets/i18n/project_pt_BR.json +74 -74
  19. novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
  20. novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
  21. novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
  22. novelwriter/assets/icons/typicons_light/icons.conf +2 -0
  23. novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
  24. novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
  25. novelwriter/assets/manual.pdf +0 -0
  26. novelwriter/assets/sample.zip +0 -0
  27. novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
  28. novelwriter/assets/syntax/default_dark.conf +32 -18
  29. novelwriter/assets/syntax/default_light.conf +24 -10
  30. novelwriter/assets/syntax/dracula.conf +44 -0
  31. novelwriter/assets/syntax/grey_dark.conf +5 -4
  32. novelwriter/assets/syntax/grey_light.conf +5 -4
  33. novelwriter/assets/syntax/light_owl.conf +7 -6
  34. novelwriter/assets/syntax/night_owl.conf +7 -6
  35. novelwriter/assets/syntax/snazzy.conf +42 -0
  36. novelwriter/assets/syntax/solarized_dark.conf +4 -3
  37. novelwriter/assets/syntax/solarized_light.conf +4 -3
  38. novelwriter/assets/syntax/tango.conf +27 -11
  39. novelwriter/assets/syntax/tomorrow.conf +6 -5
  40. novelwriter/assets/syntax/tomorrow_night.conf +7 -6
  41. novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
  42. novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
  43. novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
  44. novelwriter/assets/text/credits_en.htm +52 -41
  45. novelwriter/assets/themes/cyberpunk_night.conf +3 -0
  46. novelwriter/assets/themes/default_dark.conf +2 -0
  47. novelwriter/assets/themes/default_light.conf +2 -0
  48. novelwriter/assets/themes/dracula.conf +48 -0
  49. novelwriter/assets/themes/solarized_dark.conf +2 -0
  50. novelwriter/assets/themes/solarized_light.conf +2 -0
  51. novelwriter/common.py +33 -12
  52. novelwriter/config.py +184 -98
  53. novelwriter/constants.py +47 -35
  54. novelwriter/core/buildsettings.py +68 -69
  55. novelwriter/core/coretools.py +5 -23
  56. novelwriter/core/docbuild.py +52 -40
  57. novelwriter/core/document.py +3 -5
  58. novelwriter/core/index.py +115 -45
  59. novelwriter/core/item.py +8 -19
  60. novelwriter/core/options.py +2 -4
  61. novelwriter/core/project.py +37 -61
  62. novelwriter/core/projectdata.py +1 -3
  63. novelwriter/core/projectxml.py +12 -15
  64. novelwriter/core/sessions.py +3 -5
  65. novelwriter/core/spellcheck.py +4 -9
  66. novelwriter/core/status.py +211 -164
  67. novelwriter/core/storage.py +0 -8
  68. novelwriter/core/tohtml.py +139 -105
  69. novelwriter/core/tokenizer.py +278 -122
  70. novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
  71. novelwriter/core/toodt.py +257 -166
  72. novelwriter/core/toqdoc.py +419 -0
  73. novelwriter/core/tree.py +5 -7
  74. novelwriter/dialogs/about.py +11 -18
  75. novelwriter/dialogs/docmerge.py +17 -19
  76. novelwriter/dialogs/docsplit.py +17 -19
  77. novelwriter/dialogs/editlabel.py +6 -10
  78. novelwriter/dialogs/preferences.py +200 -164
  79. novelwriter/dialogs/projectsettings.py +225 -189
  80. novelwriter/dialogs/quotes.py +12 -9
  81. novelwriter/dialogs/wordlist.py +9 -15
  82. novelwriter/enum.py +35 -30
  83. novelwriter/error.py +8 -15
  84. novelwriter/extensions/configlayout.py +55 -21
  85. novelwriter/extensions/eventfilters.py +1 -5
  86. novelwriter/extensions/modified.py +70 -14
  87. novelwriter/extensions/novelselector.py +1 -3
  88. novelwriter/extensions/pagedsidebar.py +9 -12
  89. novelwriter/extensions/{circularprogress.py → progressbars.py} +30 -8
  90. novelwriter/extensions/statusled.py +40 -26
  91. novelwriter/extensions/switch.py +4 -6
  92. novelwriter/extensions/switchbox.py +7 -6
  93. novelwriter/extensions/versioninfo.py +3 -9
  94. novelwriter/gui/doceditor.py +120 -139
  95. novelwriter/gui/dochighlight.py +231 -186
  96. novelwriter/gui/docviewer.py +69 -108
  97. novelwriter/gui/docviewerpanel.py +3 -10
  98. novelwriter/gui/editordocument.py +1 -3
  99. novelwriter/gui/itemdetails.py +7 -11
  100. novelwriter/gui/mainmenu.py +22 -18
  101. novelwriter/gui/noveltree.py +11 -24
  102. novelwriter/gui/outline.py +15 -26
  103. novelwriter/gui/projtree.py +39 -65
  104. novelwriter/gui/search.py +10 -3
  105. novelwriter/gui/sidebar.py +2 -6
  106. novelwriter/gui/statusbar.py +29 -37
  107. novelwriter/gui/theme.py +26 -48
  108. novelwriter/guimain.py +162 -160
  109. novelwriter/shared.py +36 -19
  110. novelwriter/text/patterns.py +113 -0
  111. novelwriter/tools/dictionaries.py +10 -20
  112. novelwriter/tools/lipsum.py +10 -16
  113. novelwriter/tools/manusbuild.py +9 -11
  114. novelwriter/tools/manuscript.py +75 -149
  115. novelwriter/tools/manussettings.py +74 -76
  116. novelwriter/tools/noveldetails.py +16 -21
  117. novelwriter/tools/welcome.py +21 -26
  118. novelwriter/tools/writingstats.py +9 -12
  119. novelwriter/types.py +49 -4
  120. novelwriter/extensions/simpleprogress.py +0 -55
  121. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/LICENSE.md +0 -0
  122. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/entry_points.txt +0 -0
  123. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/top_level.txt +0 -0
@@ -27,25 +27,31 @@ from __future__ import annotations
27
27
  import logging
28
28
 
29
29
  from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
30
- from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
30
+ from PyQt5.QtGui import QCloseEvent, QColor
31
31
  from PyQt5.QtWidgets import (
32
- QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
33
- QLineEdit, QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem,
34
- QVBoxLayout, QWidget
32
+ QAbstractItemView, QApplication, QColorDialog, QDialogButtonBox,
33
+ QHBoxLayout, QLineEdit, QMenu, QStackedWidget, QToolButton, QTreeWidget,
34
+ QTreeWidgetItem, QVBoxLayout, QWidget
35
35
  )
36
36
 
37
37
  from novelwriter import CONFIG, SHARED
38
38
  from novelwriter.common import simplified
39
+ from novelwriter.constants import nwLabels, trConst
40
+ from novelwriter.core.status import NWStatus, StatusEntry
41
+ from novelwriter.enum import nwStatusShape
39
42
  from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
40
- from novelwriter.extensions.modified import NComboBox, NIconToolButton
43
+ from novelwriter.extensions.modified import NComboBox, NDialog, NIconToolButton
41
44
  from novelwriter.extensions.pagedsidebar import NPagedSideBar
42
45
  from novelwriter.extensions.switch import NSwitch
43
- from novelwriter.types import QtDialogCancel, QtDialogSave, QtUserRole
46
+ from novelwriter.types import (
47
+ QtDialogCancel, QtDialogSave, QtSizeMinimum, QtSizeMinimumExpanding,
48
+ QtUserRole
49
+ )
44
50
 
45
51
  logger = logging.getLogger(__name__)
46
52
 
47
53
 
48
- class GuiProjectSettings(QDialog):
54
+ class GuiProjectSettings(NDialog):
49
55
 
50
56
  PAGE_SETTINGS = 0
51
57
  PAGE_STATUS = 1
@@ -70,8 +76,8 @@ class GuiProjectSettings(QDialog):
70
76
 
71
77
  # Title
72
78
  self.titleLabel = NColourLabel(
73
- self.tr("Project Settings"), SHARED.theme.helpText,
74
- parent=self, scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4)
79
+ self.tr("Project Settings"), self, color=SHARED.theme.helpText,
80
+ scale=NColourLabel.HEADER_SCALE, indent=CONFIG.pxInt(4)
75
81
  )
76
82
 
77
83
  # SideBar
@@ -86,7 +92,7 @@ class GuiProjectSettings(QDialog):
86
92
  # Buttons
87
93
  self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogCancel, self)
88
94
  self.buttonBox.accepted.connect(self._doSave)
89
- self.buttonBox.rejected.connect(self.close)
95
+ self.buttonBox.rejected.connect(self.reject)
90
96
 
91
97
  # Content
92
98
  SHARED.project.countStatus()
@@ -141,7 +147,7 @@ class GuiProjectSettings(QDialog):
141
147
  """Capture the user closing the window and save settings."""
142
148
  self._saveSettings()
143
149
  event.accept()
144
- self.deleteLater()
150
+ self.softDelete()
145
151
  return
146
152
 
147
153
  ##
@@ -179,19 +185,19 @@ class GuiProjectSettings(QDialog):
179
185
 
180
186
  rebuildTrees = False
181
187
 
182
- if self.statusPage.wasChanged:
183
- newList, delList = self.statusPage.getNewList()
184
- project.setStatusColours(newList, delList)
188
+ if self.statusPage.changed:
189
+ logger.debug("Updating status labels")
190
+ project.data.itemStatus.update(self.statusPage.getNewList())
185
191
  rebuildTrees = True
186
192
 
187
- if self.importPage.wasChanged:
188
- newList, delList = self.importPage.getNewList()
189
- project.setImportColours(newList, delList)
193
+ if self.importPage.changed:
194
+ logger.debug("Updating importance labels")
195
+ project.data.itemImport.update(self.importPage.getNewList())
190
196
  rebuildTrees = True
191
197
 
192
- if self.replacePage.wasChanged:
193
- newList = self.replacePage.getNewList()
194
- project.data.setAutoReplace(newList)
198
+ if self.replacePage.changed:
199
+ logger.debug("Updating auto-replace settings")
200
+ project.data.setAutoReplace(self.replacePage.getNewList())
195
201
 
196
202
  self.newProjectSettingsReady.emit(rebuildTrees)
197
203
  QApplication.processEvents()
@@ -221,8 +227,6 @@ class GuiProjectSettings(QDialog):
221
227
 
222
228
  return
223
229
 
224
- # END Class GuiProjectSettings
225
-
226
230
 
227
231
  class _SettingsPage(NScrollableForm):
228
232
 
@@ -296,17 +300,15 @@ class _SettingsPage(NScrollableForm):
296
300
 
297
301
  return
298
302
 
299
- # END Class _SettingsPage
300
-
301
303
 
302
304
  class _StatusPage(NFixedPage):
303
305
 
304
- COL_LABEL = 0
305
- COL_USAGE = 1
306
+ C_DATA = 0
307
+ C_LABEL = 0
308
+ C_USAGE = 1
306
309
 
307
- KEY_ROLE = QtUserRole
308
- COL_ROLE = QtUserRole + 1
309
- NUM_ROLE = QtUserRole + 2
310
+ D_KEY = QtUserRole
311
+ D_ENTRY = QtUserRole + 1
310
312
 
311
313
  def __init__(self, parent: QWidget, isStatus: bool) -> None:
312
314
  super().__init__(parent=parent)
@@ -325,28 +327,39 @@ class _StatusPage(NFixedPage):
325
327
  )
326
328
 
327
329
  self._changed = False
328
- self._colDeleted = []
329
- self._selColour = QColor(100, 100, 100)
330
+ self._color = QColor(100, 100, 100)
331
+ self._shape = nwStatusShape.SQUARE
332
+ self._icons = {}
330
333
 
331
- self.iPx = SHARED.theme.baseIconHeight
334
+ self._iPx = SHARED.theme.baseIconHeight
332
335
  iSz = SHARED.theme.baseIconSize
333
- bSz = SHARED.theme.buttonIconSize
336
+ bPd = CONFIG.pxInt(4)
337
+
338
+ iColor = self.palette().text().color()
339
+
340
+ # Labels
341
+ self.trCountNone = self.tr("Not in use")
342
+ self.trCountOne = self.tr("Used once")
343
+ self.trCountMore = self.tr("Used by {0} items")
344
+ self.trSelColor = self.tr("Select Colour")
334
345
 
335
346
  # Title
336
347
  self.pageTitle = NColourLabel(
337
- pageLabel, SHARED.theme.helpText, parent=self,
348
+ pageLabel, self, color=SHARED.theme.helpText,
338
349
  scale=NColourLabel.HEADER_SCALE
339
350
  )
340
351
 
341
352
  # List Box
342
353
  self.listBox = QTreeWidget(self)
343
354
  self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")])
344
- self.listBox.itemSelectionChanged.connect(self._selectedItem)
345
- self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
355
+ self.listBox.setColumnWidth(self.C_LABEL, wCol0)
346
356
  self.listBox.setIndentation(0)
357
+ self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
358
+ self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
359
+ self.listBox.itemSelectionChanged.connect(self._selectionChanged)
347
360
 
348
- for key, entry in status.items():
349
- self._addItem(key, entry["name"], entry["cols"], entry["count"])
361
+ for key, entry in status.iterItems():
362
+ self._addItem(key, StatusEntry.duplicate(entry))
350
363
 
351
364
  # List Controls
352
365
  self.addButton = NIconToolButton(self, iSz, "add")
@@ -367,16 +380,43 @@ class _StatusPage(NFixedPage):
367
380
  self.editName.setPlaceholderText(self.tr("Select item to edit"))
368
381
  self.editName.setEnabled(False)
369
382
 
370
- self.colPixmap = QPixmap(self.iPx, self.iPx)
371
- self.colPixmap.fill(QColor(100, 100, 100))
372
- self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"), self)
373
- self.colButton.setIconSize(bSz)
374
- self.colButton.setEnabled(False)
375
- self.colButton.clicked.connect(self._selectColour)
383
+ buttonStyle = (
384
+ f"QToolButton {{padding: 0 {bPd}px;}} "
385
+ "QToolButton::menu-indicator {image: none;}"
386
+ )
376
387
 
377
- self.saveButton = QPushButton(self.tr("Save"), self)
378
- self.saveButton.setEnabled(False)
379
- self.saveButton.clicked.connect(self._saveItem)
388
+ self.colorButton = NIconToolButton(self, iSz)
389
+ self.colorButton.setToolTip(self.tr("Colour"))
390
+ self.colorButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
391
+ self.colorButton.setStyleSheet(buttonStyle)
392
+ self.colorButton.setEnabled(False)
393
+ self.colorButton.clicked.connect(self._selectColour)
394
+
395
+ def buildMenu(menu: QMenu, items: dict[nwStatusShape, str]) -> None:
396
+ for shape, label in items.items():
397
+ icon = NWStatus.createIcon(self._iPx, iColor, shape)
398
+ action = menu.addAction(icon, trConst(label))
399
+ action.triggered.connect(lambda _, shape=shape: self._selectShape(shape))
400
+ self._icons[shape] = icon
401
+
402
+ self.shapeMenu = QMenu(self)
403
+ buildMenu(self.shapeMenu, nwLabels.SHAPES_PLAIN)
404
+ buildMenu(self.shapeMenu.addMenu(self.tr("Circles ...")), nwLabels.SHAPES_CIRCLE)
405
+ buildMenu(self.shapeMenu.addMenu(self.tr("Bars ...")), nwLabels.SHAPES_BARS)
406
+ buildMenu(self.shapeMenu.addMenu(self.tr("Blocks ...")), nwLabels.SHAPES_BLOCKS)
407
+
408
+ self.shapeButton = NIconToolButton(self, iSz)
409
+ self.shapeButton.setMenu(self.shapeMenu)
410
+ self.shapeButton.setToolTip(self.tr("Shape"))
411
+ self.shapeButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
412
+ self.shapeButton.setStyleSheet(buttonStyle)
413
+ self.shapeButton.setEnabled(False)
414
+
415
+ self.applyButton = QToolButton(self)
416
+ self.applyButton.setText(self.tr("Apply"))
417
+ self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
418
+ self.applyButton.setEnabled(False)
419
+ self.applyButton.clicked.connect(self._applyChanges)
380
420
 
381
421
  # Assemble
382
422
  self.listControls = QVBoxLayout()
@@ -387,28 +427,30 @@ class _StatusPage(NFixedPage):
387
427
  self.listControls.addStretch(1)
388
428
 
389
429
  self.editBox = QHBoxLayout()
390
- self.editBox.addWidget(self.editName)
391
- self.editBox.addWidget(self.colButton)
392
- self.editBox.addWidget(self.saveButton)
430
+ self.editBox.addWidget(self.editName, 1)
431
+ self.editBox.addWidget(self.colorButton, 0)
432
+ self.editBox.addWidget(self.shapeButton, 0)
433
+ self.editBox.addWidget(self.applyButton, 0)
393
434
 
394
435
  self.mainBox = QVBoxLayout()
395
- self.mainBox.addWidget(self.listBox)
396
- self.mainBox.addLayout(self.editBox)
436
+ self.mainBox.addWidget(self.listBox, 1)
437
+ self.mainBox.addLayout(self.editBox, 0)
397
438
 
398
439
  self.innerBox = QHBoxLayout()
399
- self.innerBox.addLayout(self.mainBox)
400
- self.innerBox.addLayout(self.listControls)
440
+ self.innerBox.addLayout(self.mainBox, 1)
441
+ self.innerBox.addLayout(self.listControls, 0)
401
442
 
402
443
  self.outerBox = QVBoxLayout()
403
- self.outerBox.addWidget(self.pageTitle)
404
- self.outerBox.addLayout(self.innerBox)
444
+ self.outerBox.addWidget(self.pageTitle, 0)
445
+ self.outerBox.addLayout(self.innerBox, 1)
405
446
 
406
447
  self.setCentralLayout(self.outerBox)
448
+ self._setButtonIcons()
407
449
 
408
450
  return
409
451
 
410
452
  @property
411
- def wasChanged(self) -> bool:
453
+ def changed(self) -> bool:
412
454
  """The user changed these settings."""
413
455
  return self._changed
414
456
 
@@ -416,20 +458,17 @@ class _StatusPage(NFixedPage):
416
458
  # Methods
417
459
  ##
418
460
 
419
- def getNewList(self) -> tuple[list, list]:
461
+ def getNewList(self) -> list[tuple[str | None, StatusEntry]]:
420
462
  """Return list of entries."""
421
463
  if self._changed:
422
- newList = []
464
+ update = []
423
465
  for n in range(self.listBox.topLevelItemCount()):
424
- item = self.listBox.topLevelItem(n)
425
- if item is not None:
426
- newList.append({
427
- "key": item.data(self.COL_LABEL, self.KEY_ROLE),
428
- "name": item.text(self.COL_LABEL),
429
- "cols": item.data(self.COL_LABEL, self.COL_ROLE),
430
- })
431
- return newList, self._colDeleted
432
- return [], []
466
+ if item := self.listBox.topLevelItem(n):
467
+ key = item.data(self.C_DATA, self.D_KEY)
468
+ entry = item.data(self.C_DATA, self.D_ENTRY)
469
+ update.append((key, entry))
470
+ return update
471
+ return []
433
472
 
434
473
  def columnWidth(self) -> int:
435
474
  """Return the size of the header column."""
@@ -442,124 +481,119 @@ class _StatusPage(NFixedPage):
442
481
  @pyqtSlot()
443
482
  def _selectColour(self) -> None:
444
483
  """Open a dialog to select the status icon colour."""
445
- if self._selColour is not None:
446
- newCol = QColorDialog.getColor(
447
- self._selColour, self, self.tr("Select Colour")
448
- )
449
- if newCol.isValid():
450
- self._selColour = newCol
451
- pixmap = QPixmap(self.iPx, self.iPx)
452
- pixmap.fill(newCol)
453
- self.colButton.setIcon(QIcon(pixmap))
454
- self.colButton.setIconSize(pixmap.rect().size())
484
+ if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid():
485
+ self._color = color
486
+ self._setButtonIcons()
455
487
  return
456
488
 
457
489
  @pyqtSlot()
458
490
  def _newItem(self) -> None:
459
491
  """Create a new status item."""
460
- self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
492
+ color = QColor(100, 100, 100)
493
+ shape = nwStatusShape.SQUARE
494
+ icon = NWStatus.createIcon(self._iPx, color, shape)
495
+ self._addItem(None, StatusEntry(self.tr("New Item"), color, shape, icon, 0))
461
496
  self._changed = True
462
497
  return
463
498
 
464
499
  @pyqtSlot()
465
500
  def _delItem(self) -> None:
466
501
  """Delete a status item."""
467
- selItem = self._getSelectedItem()
468
- if isinstance(selItem, QTreeWidgetItem):
469
- iRow = self.listBox.indexOfTopLevelItem(selItem)
470
- if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
502
+ if item := self._getSelectedItem():
503
+ iRow = self.listBox.indexOfTopLevelItem(item)
504
+ entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
505
+ if entry.count > 0:
471
506
  SHARED.error(self.tr("Cannot delete a status item that is in use."))
472
507
  else:
473
508
  self.listBox.takeTopLevelItem(iRow)
474
- self._colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
475
509
  self._changed = True
476
510
  return
477
511
 
478
512
  @pyqtSlot()
479
- def _saveItem(self) -> None:
513
+ def _applyChanges(self) -> None:
480
514
  """Save changes made to a status item."""
481
- selItem = self._getSelectedItem()
482
- if isinstance(selItem, QTreeWidgetItem):
483
- selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
484
- selItem.setIcon(self.COL_LABEL, self.colButton.icon())
485
- selItem.setData(self.COL_LABEL, self.COL_ROLE, (
486
- self._selColour.red(), self._selColour.green(), self._selColour.blue()
487
- ))
515
+ if item := self._getSelectedItem():
516
+ entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
517
+
518
+ name = simplified(self.editName.text())
519
+ icon = NWStatus.createIcon(self._iPx, self._color, self._shape)
520
+
521
+ entry.name = name
522
+ entry.color = self._color
523
+ entry.shape = self._shape
524
+ entry.icon = icon
525
+
526
+ item.setText(self.C_LABEL, name)
527
+ item.setIcon(self.C_LABEL, icon)
528
+
488
529
  self._changed = True
530
+
489
531
  return
490
532
 
491
533
  @pyqtSlot()
492
- def _selectedItem(self) -> None:
534
+ def _selectionChanged(self) -> None:
493
535
  """Extract the info of a selected item and populate the settings
494
536
  boxes and button. If no item is selected, clear the form.
495
537
  """
496
- selItem = self._getSelectedItem()
497
- if isinstance(selItem, QTreeWidgetItem):
498
- cols = selItem.data(self.COL_LABEL, self.COL_ROLE)
499
- name = selItem.text(self.COL_LABEL)
500
- pixmap = QPixmap(self.iPx, self.iPx)
501
- pixmap.fill(QColor(*cols))
502
- self._selColour = QColor(*cols)
503
- self.editName.setText(name)
504
- self.colButton.setIcon(QIcon(pixmap))
538
+ if item := self._getSelectedItem():
539
+ entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
540
+ self._color = entry.color
541
+ self._shape = entry.shape
542
+ self._setButtonIcons()
543
+
544
+ self.editName.setText(entry.name)
505
545
  self.editName.selectAll()
506
546
  self.editName.setFocus()
547
+
507
548
  self.editName.setEnabled(True)
508
- self.colButton.setEnabled(True)
509
- self.saveButton.setEnabled(True)
549
+ self.colorButton.setEnabled(True)
550
+ self.shapeButton.setEnabled(True)
551
+ self.applyButton.setEnabled(True)
552
+
510
553
  else:
511
- pixmap = QPixmap(self.iPx, self.iPx)
512
- pixmap.fill(QColor(100, 100, 100))
513
- self._selColour = QColor(100, 100, 100)
554
+ self._color = QColor(100, 100, 100)
555
+ self._shape = nwStatusShape.SQUARE
556
+ self._setButtonIcons()
514
557
  self.editName.setText("")
515
- self.colButton.setIcon(QIcon(pixmap))
558
+
516
559
  self.editName.setEnabled(False)
517
- self.colButton.setEnabled(False)
518
- self.saveButton.setEnabled(False)
560
+ self.colorButton.setEnabled(False)
561
+ self.shapeButton.setEnabled(False)
562
+ self.applyButton.setEnabled(False)
519
563
  return
520
564
 
521
565
  ##
522
566
  # Internal Functions
523
567
  ##
524
568
 
525
- def _addItem(self, key: str | None, name: str,
526
- colour: tuple[int, int, int], count: int) -> None:
527
- """Add a status item to the list."""
528
- pixmap = QPixmap(self.iPx, self.iPx)
529
- pixmap.fill(QColor(*colour))
569
+ def _selectShape(self, shape: nwStatusShape) -> None:
570
+ """Set the current shape."""
571
+ self._shape = shape
572
+ self._setButtonIcons()
573
+ return
530
574
 
575
+ def _addItem(self, key: str | None, entry: StatusEntry) -> None:
576
+ """Add a status item to the list."""
531
577
  item = QTreeWidgetItem()
532
- item.setText(self.COL_LABEL, name)
533
- item.setIcon(self.COL_LABEL, QIcon(pixmap))
534
- item.setData(self.COL_LABEL, self.KEY_ROLE, key)
535
- item.setData(self.COL_LABEL, self.COL_ROLE, colour)
536
- item.setData(self.COL_LABEL, self.NUM_ROLE, count)
537
- item.setText(self.COL_USAGE, self._usageString(count))
538
-
578
+ item.setText(self.C_LABEL, entry.name)
579
+ item.setIcon(self.C_LABEL, entry.icon)
580
+ item.setText(self.C_USAGE, self._usageString(entry.count))
581
+ item.setData(self.C_DATA, self.D_KEY, key)
582
+ item.setData(self.C_DATA, self.D_ENTRY, entry)
539
583
  self.listBox.addTopLevelItem(item)
540
-
541
584
  return
542
585
 
543
586
  def _moveItem(self, step: int) -> None:
544
587
  """Move and item up or down step."""
545
- selItem = self._getSelectedItem()
546
- if selItem is None:
547
- return
548
-
549
- tIndex = self.listBox.indexOfTopLevelItem(selItem)
550
- nChild = self.listBox.topLevelItemCount()
551
- nIndex = tIndex + step
552
- if nIndex < 0 or nIndex >= nChild:
553
- return
554
-
555
- cItem = self.listBox.takeTopLevelItem(tIndex)
556
- self.listBox.insertTopLevelItem(nIndex, cItem)
557
- self.listBox.clearSelection()
558
-
559
- if cItem is not None:
560
- cItem.setSelected(True)
561
- self._changed = True
562
-
588
+ if item := self._getSelectedItem():
589
+ tIdx = self.listBox.indexOfTopLevelItem(item)
590
+ nItm = self.listBox.topLevelItemCount()
591
+ nIdx = tIdx + step
592
+ if (0 <= nIdx < nItm) and (cItem := self.listBox.takeTopLevelItem(tIdx)):
593
+ self.listBox.insertTopLevelItem(nIdx, cItem)
594
+ self.listBox.clearSelection()
595
+ cItem.setSelected(True)
596
+ self._changed = True
563
597
  return
564
598
 
565
599
  def _getSelectedItem(self) -> QTreeWidgetItem | None:
@@ -571,19 +605,24 @@ class _StatusPage(NFixedPage):
571
605
  def _usageString(self, count: int) -> str:
572
606
  """Generate usage string."""
573
607
  if count == 0:
574
- return self.tr("Not in use")
608
+ return self.trCountNone
575
609
  elif count == 1:
576
- return self.tr("Used once")
610
+ return self.trCountOne
577
611
  else:
578
- return self.tr("Used by {0} items").format(count)
612
+ return self.trCountMore.format(count)
579
613
 
580
- # END Class _StatusPage
614
+ def _setButtonIcons(self) -> None:
615
+ """Set the colour of the colour button."""
616
+ icon = NWStatus.createIcon(self._iPx, self._color, nwStatusShape.SQUARE)
617
+ self.colorButton.setIcon(icon)
618
+ self.shapeButton.setIcon(self._icons[self._shape])
619
+ return
581
620
 
582
621
 
583
622
  class _ReplacePage(NFixedPage):
584
623
 
585
- COL_KEY = 0
586
- COL_REPL = 1
624
+ C_KEY = 0
625
+ C_REPL = 1
587
626
 
588
627
  def __init__(self, parent: QWidget) -> None:
589
628
  super().__init__(parent=parent)
@@ -598,22 +637,24 @@ class _ReplacePage(NFixedPage):
598
637
 
599
638
  # Title
600
639
  self.pageTitle = NColourLabel(
601
- self.tr("Text Auto-Replace for Preview and Build"),
602
- SHARED.theme.helpText, parent=self, scale=NColourLabel.HEADER_SCALE
640
+ self.tr("Text Auto-Replace for Preview and Build"), self,
641
+ color=SHARED.theme.helpText, scale=NColourLabel.HEADER_SCALE
603
642
  )
604
643
 
605
644
  # List Box
606
645
  self.listBox = QTreeWidget(self)
607
646
  self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")])
608
- self.listBox.setColumnWidth(self.COL_KEY, wCol0)
647
+ self.listBox.setColumnWidth(self.C_KEY, wCol0)
609
648
  self.listBox.setIndentation(0)
610
- self.listBox.itemSelectionChanged.connect(self._selectedItem)
649
+ self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
650
+ self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
651
+ self.listBox.itemSelectionChanged.connect(self._selectionChanged)
611
652
 
612
653
  for aKey, aVal in SHARED.project.data.autoReplace.items():
613
654
  newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
614
655
  self.listBox.addTopLevelItem(newItem)
615
656
 
616
- self.listBox.sortByColumn(self.COL_KEY, Qt.SortOrder.AscendingOrder)
657
+ self.listBox.sortByColumn(self.C_KEY, Qt.SortOrder.AscendingOrder)
617
658
  self.listBox.setSortingEnabled(True)
618
659
 
619
660
  # List Controls
@@ -633,8 +674,10 @@ class _ReplacePage(NFixedPage):
633
674
  self.editValue.setEnabled(False)
634
675
  self.editValue.setMaxLength(80)
635
676
 
636
- self.saveButton = QPushButton(self.tr("Save"), self)
637
- self.saveButton.clicked.connect(self._saveEntry)
677
+ self.applyButton = QToolButton(self)
678
+ self.applyButton.setText(self.tr("Apply"))
679
+ self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
680
+ self.applyButton.clicked.connect(self._applyChanges)
638
681
 
639
682
  # Assemble
640
683
  self.listControls = QVBoxLayout()
@@ -645,11 +688,11 @@ class _ReplacePage(NFixedPage):
645
688
  self.editBox = QHBoxLayout()
646
689
  self.editBox.addWidget(self.editKey, 4)
647
690
  self.editBox.addWidget(self.editValue, 5)
648
- self.editBox.addWidget(self.saveButton, 0)
691
+ self.editBox.addWidget(self.applyButton, 0)
649
692
 
650
693
  self.mainBox = QVBoxLayout()
651
- self.mainBox.addWidget(self.listBox)
652
- self.mainBox.addLayout(self.editBox)
694
+ self.mainBox.addWidget(self.listBox, 1)
695
+ self.mainBox.addLayout(self.editBox, 0)
653
696
 
654
697
  self.innerBox = QHBoxLayout()
655
698
  self.innerBox.addLayout(self.mainBox)
@@ -664,7 +707,7 @@ class _ReplacePage(NFixedPage):
664
707
  return
665
708
 
666
709
  @property
667
- def wasChanged(self) -> bool:
710
+ def changed(self) -> bool:
668
711
  """The user changed these settings."""
669
712
  return self._changed
670
713
 
@@ -672,15 +715,13 @@ class _ReplacePage(NFixedPage):
672
715
  # Methods
673
716
  ##
674
717
 
675
- def getNewList(self) -> dict:
718
+ def getNewList(self) -> dict[str, str]:
676
719
  """Extract the list from the widget."""
677
720
  new = {}
678
721
  for n in range(self.listBox.topLevelItemCount()):
679
- if tItem := self.listBox.topLevelItem(n):
680
- aKey = self._stripNotAllowed(tItem.text(0))
681
- aVal = tItem.text(1)
682
- if len(aKey) > 0:
683
- new[aKey] = aVal
722
+ if item := self.listBox.topLevelItem(n):
723
+ if key := self._stripNotAllowed(item.text(self.C_KEY)):
724
+ new[key] = item.text(self.C_REPL)
684
725
  return new
685
726
 
686
727
  def columnWidth(self) -> int:
@@ -692,51 +733,48 @@ class _ReplacePage(NFixedPage):
692
733
  ##
693
734
 
694
735
  @pyqtSlot()
695
- def _selectedItem(self) -> None:
736
+ def _selectionChanged(self) -> None:
696
737
  """Extract the details from the selected item and populate the
697
738
  edit form.
698
739
  """
699
- if selItem := self._getSelectedItem():
700
- editKey = self._stripNotAllowed(selItem.text(0))
701
- editVal = selItem.text(1)
702
- self.editKey.setText(editKey)
703
- self.editValue.setText(editVal)
740
+ if item := self._getSelectedItem():
741
+ self.editKey.setText(self._stripNotAllowed(item.text(self.C_KEY)))
742
+ self.editValue.setText(item.text(self.C_REPL))
704
743
  self.editKey.setEnabled(True)
705
744
  self.editValue.setEnabled(True)
706
745
  self.editKey.selectAll()
707
746
  self.editKey.setFocus()
747
+ else:
748
+ self.editKey.setText("")
749
+ self.editValue.setText("")
750
+ self.editKey.setEnabled(False)
751
+ self.editValue.setEnabled(False)
708
752
  return
709
753
 
710
754
  @pyqtSlot()
711
- def _saveEntry(self) -> None:
755
+ def _applyChanges(self) -> None:
712
756
  """Save the form data into the list widget."""
713
- if selItem := self._getSelectedItem():
714
- newKey = self.editKey.text()
715
- newVal = self.editValue.text()
716
- saveKey = self._stripNotAllowed(newKey)
717
- if len(saveKey) > 0 and len(newVal) > 0:
718
- selItem.setText(self.COL_KEY, "<%s>" % saveKey)
719
- selItem.setText(self.COL_REPL, newVal)
720
- self.editKey.clear()
721
- self.editValue.clear()
722
- self.editKey.setEnabled(False)
723
- self.editValue.setEnabled(False)
724
- self.listBox.clearSelection()
757
+ if item := self._getSelectedItem():
758
+ key = self._stripNotAllowed(self.editKey.text())
759
+ value = self.editValue.text()
760
+ if key and value:
761
+ item.setText(self.C_KEY, f"<{key}>")
762
+ item.setText(self.C_REPL, value)
725
763
  self._changed = True
726
764
  return
727
765
 
728
766
  @pyqtSlot()
729
767
  def _addEntry(self) -> None:
730
768
  """Add a new list entry."""
731
- saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
732
- self.listBox.addTopLevelItem(QTreeWidgetItem([saveKey, ""]))
769
+ key = f"<keyword{self.listBox.topLevelItemCount() + 1:d}>"
770
+ self.listBox.addTopLevelItem(QTreeWidgetItem([key, ""]))
733
771
  return
734
772
 
735
773
  @pyqtSlot()
736
774
  def _delEntry(self) -> None:
737
775
  """Delete the selected entry."""
738
- if selItem := self._getSelectedItem():
739
- self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
776
+ if item := self._getSelectedItem():
777
+ self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(item))
740
778
  self._changed = True
741
779
  return
742
780
 
@@ -753,5 +791,3 @@ class _ReplacePage(NFixedPage):
753
791
  def _stripNotAllowed(self, key: str) -> str:
754
792
  """Clean up the replace key string."""
755
793
  return "".join(c for c in key if c.isalnum())
756
-
757
- # END Class _ReplacePage