novelWriter 2.3.1__py3-none-any.whl → 2.4__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 (119) hide show
  1. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/METADATA +5 -6
  2. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/RECORD +114 -107
  3. novelwriter/__init__.py +17 -10
  4. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  5. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  6. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  7. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  8. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  9. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  10. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  11. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  12. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  13. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  14. novelwriter/assets/icons/none.svg +4 -0
  15. novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
  16. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
  17. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
  18. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
  19. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
  20. novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
  21. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
  22. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
  23. novelwriter/assets/icons/typicons_light/icons.conf +4 -0
  24. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
  25. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
  26. novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
  27. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
  28. novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
  29. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
  30. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
  31. novelwriter/assets/manual.pdf +0 -0
  32. novelwriter/assets/sample.zip +0 -0
  33. novelwriter/assets/syntax/default_dark.conf +1 -0
  34. novelwriter/assets/syntax/default_light.conf +1 -0
  35. novelwriter/assets/syntax/grey_dark.conf +1 -0
  36. novelwriter/assets/syntax/grey_light.conf +1 -0
  37. novelwriter/assets/syntax/light_owl.conf +1 -0
  38. novelwriter/assets/syntax/night_owl.conf +1 -0
  39. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  40. novelwriter/assets/syntax/solarized_light.conf +1 -0
  41. novelwriter/assets/syntax/tomorrow.conf +1 -0
  42. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  43. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  44. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  45. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  46. novelwriter/assets/text/credits_en.htm +25 -23
  47. novelwriter/common.py +12 -4
  48. novelwriter/config.py +47 -16
  49. novelwriter/constants.py +5 -6
  50. novelwriter/core/buildsettings.py +64 -44
  51. novelwriter/core/coretools.py +97 -13
  52. novelwriter/core/docbuild.py +74 -7
  53. novelwriter/core/document.py +24 -3
  54. novelwriter/core/index.py +31 -112
  55. novelwriter/core/project.py +10 -15
  56. novelwriter/core/projectxml.py +1 -1
  57. novelwriter/core/sessions.py +2 -2
  58. novelwriter/core/spellcheck.py +3 -3
  59. novelwriter/core/status.py +6 -5
  60. novelwriter/core/storage.py +8 -2
  61. novelwriter/core/tohtml.py +22 -25
  62. novelwriter/core/tokenizer.py +417 -233
  63. novelwriter/core/tomd.py +17 -8
  64. novelwriter/core/toodt.py +386 -351
  65. novelwriter/core/tree.py +8 -8
  66. novelwriter/dialogs/about.py +9 -11
  67. novelwriter/dialogs/docmerge.py +17 -14
  68. novelwriter/dialogs/docsplit.py +20 -19
  69. novelwriter/dialogs/editlabel.py +5 -4
  70. novelwriter/dialogs/preferences.py +32 -40
  71. novelwriter/dialogs/projectsettings.py +31 -28
  72. novelwriter/dialogs/quotes.py +10 -9
  73. novelwriter/dialogs/wordlist.py +17 -14
  74. novelwriter/enum.py +17 -14
  75. novelwriter/error.py +14 -12
  76. novelwriter/extensions/circularprogress.py +12 -8
  77. novelwriter/extensions/configlayout.py +1 -3
  78. novelwriter/extensions/modified.py +51 -2
  79. novelwriter/extensions/pagedsidebar.py +16 -14
  80. novelwriter/extensions/simpleprogress.py +3 -1
  81. novelwriter/extensions/statusled.py +3 -1
  82. novelwriter/extensions/switch.py +10 -9
  83. novelwriter/extensions/switchbox.py +14 -13
  84. novelwriter/extensions/versioninfo.py +1 -1
  85. novelwriter/gui/doceditor.py +433 -496
  86. novelwriter/gui/dochighlight.py +54 -33
  87. novelwriter/gui/docviewer.py +162 -175
  88. novelwriter/gui/docviewerpanel.py +20 -37
  89. novelwriter/gui/editordocument.py +15 -4
  90. novelwriter/gui/itemdetails.py +51 -54
  91. novelwriter/gui/mainmenu.py +37 -16
  92. novelwriter/gui/noveltree.py +31 -37
  93. novelwriter/gui/outline.py +120 -98
  94. novelwriter/gui/projtree.py +61 -67
  95. novelwriter/gui/search.py +362 -0
  96. novelwriter/gui/sidebar.py +36 -45
  97. novelwriter/gui/statusbar.py +14 -14
  98. novelwriter/gui/theme.py +107 -32
  99. novelwriter/guimain.py +209 -202
  100. novelwriter/shared.py +31 -6
  101. novelwriter/text/counting.py +138 -0
  102. novelwriter/tools/dictionaries.py +15 -14
  103. novelwriter/tools/lipsum.py +20 -17
  104. novelwriter/tools/manusbuild.py +43 -35
  105. novelwriter/tools/manuscript.py +381 -104
  106. novelwriter/tools/manussettings.py +262 -125
  107. novelwriter/tools/noveldetails.py +20 -18
  108. novelwriter/tools/welcome.py +52 -49
  109. novelwriter/tools/writingstats.py +61 -55
  110. novelwriter/types.py +90 -0
  111. novelwriter/core/__init__.py +0 -3
  112. novelwriter/dialogs/__init__.py +0 -3
  113. novelwriter/extensions/__init__.py +0 -3
  114. novelwriter/gui/__init__.py +0 -3
  115. novelwriter/tools/__init__.py +0 -3
  116. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/LICENSE.md +0 -0
  117. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/WHEEL +0 -0
  118. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/entry_points.txt +0 -0
  119. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/top_level.txt +0 -0
@@ -26,20 +26,21 @@ from __future__ import annotations
26
26
 
27
27
  import logging
28
28
 
29
- from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
30
29
  from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
30
+ from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
31
31
  from PyQt5.QtWidgets import (
32
- QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout, QLineEdit,
33
- QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
34
- QWidget, qApp
32
+ QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
33
+ QLineEdit, QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem,
34
+ QVBoxLayout, QWidget
35
35
  )
36
36
 
37
37
  from novelwriter import CONFIG, SHARED
38
38
  from novelwriter.common import simplified
39
- from novelwriter.extensions.switch import NSwitch
40
- from novelwriter.extensions.modified import NComboBox
41
39
  from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
40
+ from novelwriter.extensions.modified import NComboBox, NIconToolButton
42
41
  from novelwriter.extensions.pagedsidebar import NPagedSideBar
42
+ from novelwriter.extensions.switch import NSwitch
43
+ from novelwriter.types import QtDialogCancel, QtDialogSave, QtUserRole
43
44
 
44
45
  logger = logging.getLogger(__name__)
45
46
 
@@ -83,9 +84,7 @@ class GuiProjectSettings(QDialog):
83
84
  self.sidebar.buttonClicked.connect(self._sidebarClicked)
84
85
 
85
86
  # Buttons
86
- self.buttonBox = QDialogButtonBox(
87
- QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel
88
- )
87
+ self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogCancel, self)
89
88
  self.buttonBox.accepted.connect(self._doSave)
90
89
  self.buttonBox.rejected.connect(self.close)
91
90
 
@@ -195,7 +194,7 @@ class GuiProjectSettings(QDialog):
195
194
  project.data.setAutoReplace(newList)
196
195
 
197
196
  self.newProjectSettingsReady.emit(rebuildTrees)
198
- qApp.processEvents()
197
+ QApplication.processEvents()
199
198
  self.close()
200
199
 
201
200
  return
@@ -258,17 +257,17 @@ class _SettingsPage(NScrollableForm):
258
257
  )
259
258
 
260
259
  # Project Language
260
+ projLang = data.language or CONFIG.guiLocale
261
261
  self.projLang = NComboBox(self)
262
262
  self.projLang.setMinimumWidth(xW)
263
263
  for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ):
264
264
  self.projLang.addItem(language, tag)
265
+ self.projLang.setCurrentData(projLang, projLang)
265
266
  self.addRow(
266
267
  self.tr("Project language"), self.projLang,
267
268
  self.tr("Only used when building the manuscript."),
268
269
  stretch=(3, 2)
269
270
  )
270
- if (idx := self.projLang.findData(data.language)) != -1:
271
- self.projLang.setCurrentIndex(idx)
272
271
 
273
272
  # Spell Check Language
274
273
  self.spellLang = NComboBox(self)
@@ -305,9 +304,9 @@ class _StatusPage(NFixedPage):
305
304
  COL_LABEL = 0
306
305
  COL_USAGE = 1
307
306
 
308
- KEY_ROLE = Qt.ItemDataRole.UserRole
309
- COL_ROLE = Qt.ItemDataRole.UserRole + 1
310
- NUM_ROLE = Qt.ItemDataRole.UserRole + 2
307
+ KEY_ROLE = QtUserRole
308
+ COL_ROLE = QtUserRole + 1
309
+ NUM_ROLE = QtUserRole + 2
311
310
 
312
311
  def __init__(self, parent: QWidget, isStatus: bool) -> None:
313
312
  super().__init__(parent=parent)
@@ -329,7 +328,9 @@ class _StatusPage(NFixedPage):
329
328
  self._colDeleted = []
330
329
  self._selColour = QColor(100, 100, 100)
331
330
 
332
- self.iPx = SHARED.theme.baseIconSize
331
+ self.iPx = SHARED.theme.baseIconHeight
332
+ iSz = SHARED.theme.baseIconSize
333
+ bSz = SHARED.theme.buttonIconSize
333
334
 
334
335
  # Title
335
336
  self.pageTitle = NColourLabel(
@@ -348,16 +349,16 @@ class _StatusPage(NFixedPage):
348
349
  self._addItem(key, entry["name"], entry["cols"], entry["count"])
349
350
 
350
351
  # List Controls
351
- self.addButton = QPushButton(SHARED.theme.getIcon("add"), "", self)
352
+ self.addButton = NIconToolButton(self, iSz, "add")
352
353
  self.addButton.clicked.connect(self._newItem)
353
354
 
354
- self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "", self)
355
+ self.delButton = NIconToolButton(self, iSz, "remove")
355
356
  self.delButton.clicked.connect(self._delItem)
356
357
 
357
- self.upButton = QPushButton(SHARED.theme.getIcon("up"), "", self)
358
+ self.upButton = NIconToolButton(self, iSz, "up")
358
359
  self.upButton.clicked.connect(lambda: self._moveItem(-1))
359
360
 
360
- self.dnButton = QPushButton(SHARED.theme.getIcon("down"), "", self)
361
+ self.dnButton = NIconToolButton(self, iSz, "down")
361
362
  self.dnButton.clicked.connect(lambda: self._moveItem(1))
362
363
 
363
364
  # Edit Form
@@ -368,12 +369,12 @@ class _StatusPage(NFixedPage):
368
369
 
369
370
  self.colPixmap = QPixmap(self.iPx, self.iPx)
370
371
  self.colPixmap.fill(QColor(100, 100, 100))
371
- self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"))
372
- self.colButton.setIconSize(self.colPixmap.rect().size())
372
+ self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"), self)
373
+ self.colButton.setIconSize(bSz)
373
374
  self.colButton.setEnabled(False)
374
375
  self.colButton.clicked.connect(self._selectColour)
375
376
 
376
- self.saveButton = QPushButton(self.tr("Save"))
377
+ self.saveButton = QPushButton(self.tr("Save"), self)
377
378
  self.saveButton.setEnabled(False)
378
379
  self.saveButton.clicked.connect(self._saveItem)
379
380
 
@@ -589,6 +590,8 @@ class _ReplacePage(NFixedPage):
589
590
 
590
591
  self._changed = False
591
592
 
593
+ iSz = SHARED.theme.baseIconSize
594
+
592
595
  wCol0 = CONFIG.pxInt(
593
596
  SHARED.project.options.getInt("GuiProjectSettings", "replaceColW", 130)
594
597
  )
@@ -600,7 +603,7 @@ class _ReplacePage(NFixedPage):
600
603
  )
601
604
 
602
605
  # List Box
603
- self.listBox = QTreeWidget()
606
+ self.listBox = QTreeWidget(self)
604
607
  self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")])
605
608
  self.listBox.setColumnWidth(self.COL_KEY, wCol0)
606
609
  self.listBox.setIndentation(0)
@@ -610,14 +613,14 @@ class _ReplacePage(NFixedPage):
610
613
  newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
611
614
  self.listBox.addTopLevelItem(newItem)
612
615
 
613
- self.listBox.sortByColumn(self.COL_KEY, Qt.AscendingOrder)
616
+ self.listBox.sortByColumn(self.COL_KEY, Qt.SortOrder.AscendingOrder)
614
617
  self.listBox.setSortingEnabled(True)
615
618
 
616
619
  # List Controls
617
- self.addButton = QPushButton(SHARED.theme.getIcon("add"), "")
620
+ self.addButton = NIconToolButton(self, iSz, "add")
618
621
  self.addButton.clicked.connect(self._addEntry)
619
622
 
620
- self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "")
623
+ self.delButton = NIconToolButton(self, iSz, "remove")
621
624
  self.delButton.clicked.connect(self._delEntry)
622
625
 
623
626
  # Edit Form
@@ -630,7 +633,7 @@ class _ReplacePage(NFixedPage):
630
633
  self.editValue.setEnabled(False)
631
634
  self.editValue.setMaxLength(80)
632
635
 
633
- self.saveButton = QPushButton(self.tr("Save"))
636
+ self.saveButton = QPushButton(self.tr("Save"), self)
634
637
  self.saveButton.clicked.connect(self._saveEntry)
635
638
 
636
639
  # Assemble
@@ -26,7 +26,7 @@ from __future__ import annotations
26
26
  import logging
27
27
 
28
28
  from PyQt5.QtGui import QFontMetrics
29
- from PyQt5.QtCore import QSize, Qt, pyqtSlot
29
+ from PyQt5.QtCore import QSize, pyqtSlot
30
30
  from PyQt5.QtWidgets import (
31
31
  QDialog, QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
32
32
  QListWidgetItem, QVBoxLayout, QWidget
@@ -34,6 +34,7 @@ from PyQt5.QtWidgets import (
34
34
 
35
35
  from novelwriter import CONFIG
36
36
  from novelwriter.constants import trConst, nwQuotes
37
+ from novelwriter.types import QtAlignCenter, QtAlignTop, QtDialogCancel, QtDialogOk, QtUserRole
37
38
 
38
39
  logger = logging.getLogger(__name__)
39
40
 
@@ -42,7 +43,7 @@ class GuiQuoteSelect(QDialog):
42
43
 
43
44
  _selected = ""
44
45
 
45
- D_KEY = Qt.ItemDataRole.UserRole
46
+ D_KEY = QtUserRole
46
47
 
47
48
  def __init__(self, parent: QWidget, current: str = '"') -> None:
48
49
  super().__init__(parent=parent)
@@ -65,14 +66,14 @@ class GuiQuoteSelect(QDialog):
65
66
  lblFont.setPointSizeF(4*lblFont.pointSizeF())
66
67
 
67
68
  # Preview Label
68
- self.previewLabel = QLabel(current)
69
+ self.previewLabel = QLabel(current, self)
69
70
  self.previewLabel.setFont(lblFont)
70
71
  self.previewLabel.setFixedSize(QSize(pxW, pxH))
71
- self.previewLabel.setAlignment(Qt.AlignCenter)
72
- self.previewLabel.setFrameStyle(QFrame.Box | QFrame.Plain)
72
+ self.previewLabel.setAlignment(QtAlignCenter)
73
+ self.previewLabel.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Plain)
73
74
 
74
75
  # Quote Symbols
75
- self.listBox = QListWidget()
76
+ self.listBox = QListWidget(self)
76
77
  self.listBox.itemSelectionChanged.connect(self._selectedSymbol)
77
78
 
78
79
  minSize = 100
@@ -89,12 +90,12 @@ class GuiQuoteSelect(QDialog):
89
90
  self.listBox.setMinimumHeight(CONFIG.pxInt(150))
90
91
 
91
92
  # Buttons
92
- self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
93
+ self.buttonBox = QDialogButtonBox(QtDialogOk | QtDialogCancel, self)
93
94
  self.buttonBox.accepted.connect(self.accept)
94
95
  self.buttonBox.rejected.connect(self.reject)
95
96
 
96
97
  # Assemble
97
- self.labelBox.addWidget(self.previewLabel, 0, Qt.AlignTop)
98
+ self.labelBox.addWidget(self.previewLabel, 0, QtAlignTop)
98
99
  self.labelBox.addStretch(1)
99
100
 
100
101
  self.innerBox.addLayout(self.labelBox)
@@ -122,7 +123,7 @@ class GuiQuoteSelect(QDialog):
122
123
  def getQuote(cls, parent: QWidget, current: str = "") -> tuple[str, bool]:
123
124
  """Pop the dialog and return the result."""
124
125
  cls = GuiQuoteSelect(parent, current=current)
125
- cls.exec_()
126
+ cls.exec()
126
127
  quote = cls._selected
127
128
  accepted = cls.result() == QDialog.DialogCode.Accepted
128
129
  cls.deleteLater()
@@ -28,17 +28,19 @@ import logging
28
28
  from typing import TYPE_CHECKING
29
29
  from pathlib import Path
30
30
 
31
- from PyQt5.QtGui import QCloseEvent
32
31
  from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
32
+ from PyQt5.QtGui import QCloseEvent
33
33
  from PyQt5.QtWidgets import (
34
- QAbstractItemView, QDialog, QDialogButtonBox, QFileDialog, QHBoxLayout,
35
- QLineEdit, QListWidget, QPushButton, QVBoxLayout, qApp
34
+ QAbstractItemView, QApplication, QDialog, QDialogButtonBox, QFileDialog,
35
+ QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout
36
36
  )
37
37
 
38
38
  from novelwriter import CONFIG, SHARED
39
39
  from novelwriter.common import formatFileFilter
40
40
  from novelwriter.core.spellcheck import UserDictionary
41
41
  from novelwriter.extensions.configlayout import NColourLabel
42
+ from novelwriter.extensions.modified import NIconToolButton
43
+ from novelwriter.types import QtDialogClose, QtDialogSave
42
44
 
43
45
  if TYPE_CHECKING: # pragma: no cover
44
46
  from novelwriter.guimain import GuiMain
@@ -57,6 +59,7 @@ class GuiWordList(QDialog):
57
59
  self.setObjectName("GuiWordList")
58
60
  self.setWindowTitle(self.tr("Project Word List"))
59
61
 
62
+ iSz = SHARED.theme.baseIconSize
60
63
  mS = CONFIG.pxInt(250)
61
64
  wW = CONFIG.pxInt(320)
62
65
  wH = CONFIG.pxInt(340)
@@ -74,11 +77,11 @@ class GuiWordList(QDialog):
74
77
  scale=NColourLabel.HEADER_SCALE
75
78
  )
76
79
 
77
- self.importButton = QPushButton(SHARED.theme.getIcon("import"), "", self)
80
+ self.importButton = NIconToolButton(self, iSz, "import")
78
81
  self.importButton.setToolTip(self.tr("Import words from text file"))
79
82
  self.importButton.clicked.connect(self._importWords)
80
83
 
81
- self.exportButton = QPushButton(SHARED.theme.getIcon("export"), "", self)
84
+ self.exportButton = NIconToolButton(self, iSz, "export")
82
85
  self.exportButton.setToolTip(self.tr("Export words to text file"))
83
86
  self.exportButton.clicked.connect(self._exportWords)
84
87
 
@@ -89,17 +92,17 @@ class GuiWordList(QDialog):
89
92
 
90
93
  # List Box
91
94
  self.listBox = QListWidget(self)
92
- self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
95
+ self.listBox.setDragDropMode(QAbstractItemView.DragDropMode.NoDragDrop)
93
96
  self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
94
97
  self.listBox.setSortingEnabled(True)
95
98
 
96
99
  # Add/Remove Form
97
100
  self.newEntry = QLineEdit(self)
98
101
 
99
- self.addButton = QPushButton(SHARED.theme.getIcon("add"), "", self)
102
+ self.addButton = NIconToolButton(self, iSz, "add")
100
103
  self.addButton.clicked.connect(self._doAdd)
101
104
 
102
- self.delButton = QPushButton(SHARED.theme.getIcon("remove"), "", self)
105
+ self.delButton = NIconToolButton(self, iSz, "remove")
103
106
  self.delButton.clicked.connect(self._doDelete)
104
107
 
105
108
  self.editBox = QHBoxLayout()
@@ -108,7 +111,7 @@ class GuiWordList(QDialog):
108
111
  self.editBox.addWidget(self.delButton, 0)
109
112
 
110
113
  # Buttons
111
- self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
114
+ self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogClose, self)
112
115
  self.buttonBox.accepted.connect(self._doSave)
113
116
  self.buttonBox.rejected.connect(self.close)
114
117
 
@@ -155,7 +158,7 @@ class GuiWordList(QDialog):
155
158
  self.newEntry.setText("")
156
159
  self.listBox.clearSelection()
157
160
  self._addWord(word)
158
- if items := self.listBox.findItems(word, Qt.MatchExactly):
161
+ if items := self.listBox.findItems(word, Qt.MatchFlag.MatchExactly):
159
162
  self.listBox.setCurrentItem(items[0])
160
163
  self.listBox.scrollToItem(items[0], QAbstractItemView.ScrollHint.PositionAtCenter)
161
164
  return
@@ -175,7 +178,7 @@ class GuiWordList(QDialog):
175
178
  userDict.add(word)
176
179
  userDict.save()
177
180
  self.newWordListReady.emit()
178
- qApp.processEvents()
181
+ QApplication.processEvents()
179
182
  self.close()
180
183
  return
181
184
 
@@ -187,7 +190,7 @@ class GuiWordList(QDialog):
187
190
  ))
188
191
  ffilter = formatFileFilter(["*.txt", "*"])
189
192
  path, _ = QFileDialog.getOpenFileName(
190
- self, self.tr("Import File"), str(Path.home()), filter=ffilter
193
+ self, self.tr("Import File"), str(CONFIG.homePath()), filter=ffilter
191
194
  )
192
195
  if path:
193
196
  try:
@@ -204,7 +207,7 @@ class GuiWordList(QDialog):
204
207
  def _exportWords(self) -> None:
205
208
  """Export words to file."""
206
209
  path, _ = QFileDialog.getSaveFileName(
207
- self, self.tr("Export File"), str(Path.home())
210
+ self, self.tr("Export File"), str(CONFIG.homePath())
208
211
  )
209
212
  if path:
210
213
  try:
@@ -242,7 +245,7 @@ class GuiWordList(QDialog):
242
245
 
243
246
  def _addWord(self, word: str) -> None:
244
247
  """Add a single word to the list."""
245
- if word and not self.listBox.findItems(word, Qt.MatchExactly):
248
+ if word and not self.listBox.findItems(word, Qt.MatchFlag.MatchExactly):
246
249
  self.listBox.addItem(word)
247
250
  self._changed = True
248
251
  return
novelwriter/enum.py CHANGED
@@ -112,20 +112,22 @@ class nwDocAction(Enum):
112
112
  BLOCK_TXT = 19
113
113
  BLOCK_TTL = 20
114
114
  BLOCK_UNN = 21
115
- REPL_SNG = 22
116
- REPL_DBL = 23
117
- RM_BREAKS = 24
118
- ALIGN_L = 25
119
- ALIGN_C = 26
120
- ALIGN_R = 27
121
- INDENT_L = 28
122
- INDENT_R = 29
123
- SC_ITALIC = 30
124
- SC_BOLD = 31
125
- SC_STRIKE = 32
126
- SC_ULINE = 33
127
- SC_SUP = 34
128
- SC_SUB = 35
115
+ BLOCK_HSC = 22
116
+ REPL_SNG = 23
117
+ REPL_DBL = 24
118
+ RM_BREAKS = 25
119
+ ALIGN_L = 26
120
+ ALIGN_C = 27
121
+ ALIGN_R = 28
122
+ INDENT_L = 29
123
+ INDENT_R = 30
124
+ SC_ITALIC = 31
125
+ SC_BOLD = 32
126
+ SC_STRIKE = 33
127
+ SC_ULINE = 34
128
+ SC_MARK = 35
129
+ SC_SUP = 36
130
+ SC_SUB = 37
129
131
 
130
132
  # END Enum nwDocAction
131
133
 
@@ -153,6 +155,7 @@ class nwView(Enum):
153
155
  PROJECT = 1
154
156
  NOVEL = 2
155
157
  OUTLINE = 3
158
+ SEARCH = 4
156
159
 
157
160
  # END Enum nwView
158
161
 
novelwriter/error.py CHANGED
@@ -29,11 +29,11 @@ import logging
29
29
 
30
30
  from typing import TYPE_CHECKING
31
31
 
32
- from PyQt5.QtGui import QFont, QFontDatabase
33
32
  from PyQt5.QtCore import Qt, pyqtSlot
33
+ from PyQt5.QtGui import QFont, QFontDatabase
34
34
  from PyQt5.QtWidgets import (
35
- QWidget, qApp, QDialog, QGridLayout, QStyle, QPlainTextEdit, QLabel,
36
- QDialogButtonBox
35
+ QApplication, QWidget, QDialog, QGridLayout, QStyle, QPlainTextEdit,
36
+ QLabel, QDialogButtonBox
37
37
  )
38
38
 
39
39
  if TYPE_CHECKING: # pragma: no cover
@@ -54,7 +54,7 @@ def logException() -> None:
54
54
  return
55
55
 
56
56
 
57
- def formatException(exc) -> str:
57
+ def formatException(exc: BaseException) -> str:
58
58
  """Format an exception as a string the same way the default
59
59
  exception handler does.
60
60
  """
@@ -74,7 +74,9 @@ class NWErrorMessage(QDialog):
74
74
  # Widgets
75
75
  self.msgIcon = QLabel()
76
76
  self.msgIcon.setPixmap(
77
- qApp.style().standardIcon(QStyle.SP_MessageBoxCritical).pixmap(64, 64)
77
+ QApplication.style().standardIcon(
78
+ QStyle.StandardPixmap.SP_MessageBoxCritical
79
+ ).pixmap(64, 64)
78
80
  )
79
81
  self.msgHead = QLabel()
80
82
  self.msgHead.setOpenExternalLinks(True)
@@ -88,13 +90,13 @@ class NWErrorMessage(QDialog):
88
90
  self.msgBody.setFont(font)
89
91
  self.msgBody.setReadOnly(True)
90
92
 
91
- self.btnBox = QDialogButtonBox(QDialogButtonBox.Close)
93
+ self.btnBox = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
92
94
  self.btnBox.rejected.connect(self._doClose)
93
95
 
94
96
  # Assemble
95
97
  self.mainBox = QGridLayout()
96
- self.mainBox.addWidget(self.msgIcon, 0, 0, 2, 1, Qt.AlignTop)
97
- self.mainBox.addWidget(self.msgHead, 0, 1, 1, 1, Qt.AlignTop)
98
+ self.mainBox.addWidget(self.msgIcon, 0, 0, 2, 1, Qt.AlignmentFlag.AlignTop)
99
+ self.mainBox.addWidget(self.msgHead, 0, 1, 1, 1, Qt.AlignmentFlag.AlignTop)
98
100
  self.mainBox.addWidget(self.msgBody, 1, 1, 1, 1)
99
101
  self.mainBox.addWidget(self.btnBox, 2, 0, 1, 2)
100
102
  self.mainBox.setSpacing(16)
@@ -179,14 +181,14 @@ class NWErrorMessage(QDialog):
179
181
  def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackType) -> None:
180
182
  """Function to catch unhandled global exceptions."""
181
183
  from traceback import print_tb
182
- from PyQt5.QtWidgets import qApp
184
+ from PyQt5.QtWidgets import QApplication
183
185
 
184
186
  logger.critical("%s: %s", exType.__name__, str(exValue))
185
187
  print_tb(exTrace)
186
188
 
187
189
  try:
188
190
  nwGUI = None
189
- for qWin in qApp.topLevelWidgets():
191
+ for qWin in QApplication.topLevelWidgets():
190
192
  if qWin.objectName() == "GuiMain":
191
193
  nwGUI = qWin
192
194
  break
@@ -197,7 +199,7 @@ def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackTyp
197
199
 
198
200
  errMsg = NWErrorMessage(nwGUI)
199
201
  errMsg.setMessage(exType, exValue, exTrace)
200
- errMsg.exec_()
202
+ errMsg.exec()
201
203
 
202
204
  try:
203
205
  # Try a controlled shutdown
@@ -209,7 +211,7 @@ def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackTyp
209
211
  logger.critical("Could not close the project before exiting")
210
212
  logger.critical(formatException(exc))
211
213
 
212
- qApp.exit(1)
214
+ QApplication.exit(1)
213
215
 
214
216
  except Exception as exc:
215
217
  logger.critical(formatException(exc))
@@ -25,10 +25,14 @@ from __future__ import annotations
25
25
 
26
26
  from math import ceil
27
27
 
28
+ from PyQt5.QtCore import QRect
28
29
  from PyQt5.QtGui import QBrush, QColor, QPaintEvent, QPainter, QPen
29
- from PyQt5.QtCore import QRect, Qt
30
30
  from PyQt5.QtWidgets import QProgressBar, QSizePolicy, QWidget
31
31
 
32
+ from novelwriter.types import (
33
+ QtPaintAnitAlias, QtAlignCenter, QtRoundCap, QtSolidLine, QtTransparent
34
+ )
35
+
32
36
 
33
37
  class NProgressCircle(QProgressBar):
34
38
  """Extension: Circular Progress Widget
@@ -48,14 +52,14 @@ class NProgressCircle(QProgressBar):
48
52
  self._point = point
49
53
  self._dRect = QRect(0, 0, size, size)
50
54
  self._cRect = QRect(point, point, size - 2*point, size - 2*point)
51
- self._dPen = QPen(Qt.transparent)
52
- self._dBrush = QBrush(Qt.transparent)
55
+ self._dPen = QPen(QtTransparent)
56
+ self._dBrush = QBrush(QtTransparent)
53
57
  self.setColours(
54
58
  track=self.palette().alternateBase().color(),
55
59
  bar=self.palette().highlight().color(),
56
60
  text=self.palette().text().color()
57
61
  )
58
- self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
62
+ self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
59
63
  self.setFixedWidth(size)
60
64
  self.setFixedHeight(size)
61
65
  return
@@ -67,9 +71,9 @@ class NProgressCircle(QProgressBar):
67
71
  self._dPen = QPen(back)
68
72
  self._dBrush = QBrush(back)
69
73
  if isinstance(bar, QColor):
70
- self._cPen = QPen(QBrush(bar), self._point, Qt.SolidLine, Qt.RoundCap)
74
+ self._cPen = QPen(QBrush(bar), self._point, QtSolidLine, QtRoundCap)
71
75
  if isinstance(track, QColor):
72
- self._bPen = QPen(QBrush(track), self._point, Qt.SolidLine, Qt.RoundCap)
76
+ self._bPen = QPen(QBrush(track), self._point, QtSolidLine, QtRoundCap)
73
77
  if isinstance(text, QColor):
74
78
  self._tColor = text
75
79
  return
@@ -85,7 +89,7 @@ class NProgressCircle(QProgressBar):
85
89
  progress = 100.0*self.value()/self.maximum()
86
90
  angle = ceil(16*3.6*progress)
87
91
  painter = QPainter(self)
88
- painter.setRenderHint(QPainter.Antialiasing, True)
92
+ painter.setRenderHint(QtPaintAnitAlias, True)
89
93
  painter.setPen(self._dPen)
90
94
  painter.setBrush(self._dBrush)
91
95
  painter.drawEllipse(self._dRect)
@@ -94,7 +98,7 @@ class NProgressCircle(QProgressBar):
94
98
  painter.setPen(self._cPen)
95
99
  painter.drawArc(self._cRect, 90*16, -angle)
96
100
  painter.setPen(self._tColor)
97
- painter.drawText(self._cRect, Qt.AlignCenter, self._text or f"{progress:.1f} %")
101
+ painter.drawText(self._cRect, QtAlignCenter, self._text or f"{progress:.1f} %")
98
102
  return
99
103
 
100
104
  # END Class NProgressCircle
@@ -37,8 +37,6 @@ from PyQt5.QtWidgets import (
37
37
  from novelwriter import CONFIG
38
38
 
39
39
  DEFAULT_SCALE = 0.9
40
- RIGHT_TOP = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
41
- LEFT_TOP = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
42
40
 
43
41
 
44
42
  class NFixedPage(QFrame):
@@ -260,7 +258,7 @@ class NColourLabel(QLabel):
260
258
  font.setWeight(QFont.Weight.Bold if bold else QFont.Weight.Normal)
261
259
  if color:
262
260
  colour = self.palette()
263
- colour.setColor(QPalette.WindowText, color)
261
+ colour.setColor(QPalette.ColorRole.WindowText, color)
264
262
  self.setPalette(colour)
265
263
 
266
264
  self.setFont(font)
@@ -25,9 +25,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
25
25
  """
26
26
  from __future__ import annotations
27
27
 
28
- from PyQt5.QtCore import Qt
28
+ from PyQt5.QtCore import QSize, Qt
29
29
  from PyQt5.QtGui import QWheelEvent
30
- from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QWidget
30
+ from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QToolButton, QWidget
31
+
32
+ from novelwriter import SHARED
31
33
 
32
34
 
33
35
  class NComboBox(QComboBox):
@@ -44,6 +46,12 @@ class NComboBox(QComboBox):
44
46
  event.ignore()
45
47
  return
46
48
 
49
+ def setCurrentData(self, data: str, default: str) -> None:
50
+ """Set the current index from data, with a fallback."""
51
+ idx = self.findData(data)
52
+ self.setCurrentIndex(self.findData(default) if idx < 0 else idx)
53
+ return
54
+
47
55
  # END Class NComboBox
48
56
 
49
57
 
@@ -79,3 +87,44 @@ class NDoubleSpinBox(QDoubleSpinBox):
79
87
  return
80
88
 
81
89
  # END Class NDoubleSpinBox
90
+
91
+
92
+ class NIconToolButton(QToolButton):
93
+
94
+ def __init__(self, parent: QWidget, iconSize: QSize, icon: str | None = None) -> None:
95
+ super().__init__(parent=parent)
96
+ self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
97
+ self.setIconSize(iconSize)
98
+ self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
99
+ if icon:
100
+ self.setThemeIcon(icon)
101
+ return
102
+
103
+ def setThemeIcon(self, iconKey: str) -> None:
104
+ """Set an icon from the current theme."""
105
+ self.setIcon(SHARED.theme.getIcon(iconKey))
106
+ return
107
+
108
+ # END Class NIconToolButton
109
+
110
+
111
+ class NIconToggleButton(QToolButton):
112
+
113
+ def __init__(self, parent: QWidget, iconSize: QSize, icon: str | None = None) -> None:
114
+ super().__init__(parent=parent)
115
+ self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
116
+ self.setIconSize(iconSize)
117
+ self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
118
+ self.setCheckable(True)
119
+ self.setStyleSheet("border: none; background: transparent;")
120
+ if icon:
121
+ self.setThemeIcon(icon)
122
+ return
123
+
124
+ def setThemeIcon(self, iconKey: str) -> None:
125
+ """Set an icon from the current theme."""
126
+ iconSize = self.iconSize()
127
+ self.setIcon(SHARED.theme.getToggleIcon(iconKey, (iconSize.width(), iconSize.height())))
128
+ return
129
+
130
+ # END Class NUnfoldButton