novelWriter 2.4b1__py3-none-any.whl → 2.4.1__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 (89) hide show
  1. {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/METADATA +5 -6
  2. {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/RECORD +79 -83
  3. novelwriter/__init__.py +15 -8
  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 +2 -2
  16. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
  17. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
  18. novelwriter/assets/icons/typicons_light/icons.conf +2 -2
  19. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
  20. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
  21. novelwriter/assets/manual.pdf +0 -0
  22. novelwriter/assets/sample.zip +0 -0
  23. novelwriter/common.py +11 -3
  24. novelwriter/config.py +12 -4
  25. novelwriter/core/buildsettings.py +7 -7
  26. novelwriter/core/coretools.py +21 -22
  27. novelwriter/core/docbuild.py +2 -2
  28. novelwriter/core/projectxml.py +1 -1
  29. novelwriter/core/spellcheck.py +3 -3
  30. novelwriter/core/status.py +3 -2
  31. novelwriter/core/tokenizer.py +3 -3
  32. novelwriter/core/toodt.py +333 -356
  33. novelwriter/dialogs/about.py +9 -11
  34. novelwriter/dialogs/docmerge.py +17 -14
  35. novelwriter/dialogs/docsplit.py +14 -12
  36. novelwriter/dialogs/editlabel.py +5 -4
  37. novelwriter/dialogs/preferences.py +29 -34
  38. novelwriter/dialogs/projectsettings.py +31 -28
  39. novelwriter/dialogs/quotes.py +10 -9
  40. novelwriter/dialogs/wordlist.py +17 -14
  41. novelwriter/error.py +14 -12
  42. novelwriter/extensions/circularprogress.py +12 -8
  43. novelwriter/extensions/configlayout.py +1 -3
  44. novelwriter/extensions/modified.py +33 -2
  45. novelwriter/extensions/pagedsidebar.py +16 -14
  46. novelwriter/extensions/simpleprogress.py +3 -1
  47. novelwriter/extensions/statusled.py +3 -1
  48. novelwriter/extensions/switch.py +10 -9
  49. novelwriter/extensions/switchbox.py +14 -13
  50. novelwriter/gui/doceditor.py +205 -246
  51. novelwriter/gui/dochighlight.py +26 -9
  52. novelwriter/gui/docviewer.py +55 -59
  53. novelwriter/gui/docviewerpanel.py +16 -13
  54. novelwriter/gui/editordocument.py +4 -4
  55. novelwriter/gui/itemdetails.py +45 -48
  56. novelwriter/gui/mainmenu.py +2 -2
  57. novelwriter/gui/noveltree.py +23 -21
  58. novelwriter/gui/outline.py +93 -94
  59. novelwriter/gui/projtree.py +32 -30
  60. novelwriter/gui/search.py +75 -29
  61. novelwriter/gui/sidebar.py +24 -28
  62. novelwriter/gui/statusbar.py +14 -14
  63. novelwriter/gui/theme.py +61 -39
  64. novelwriter/guimain.py +37 -33
  65. novelwriter/shared.py +21 -9
  66. novelwriter/text/counting.py +1 -0
  67. novelwriter/tools/dictionaries.py +15 -14
  68. novelwriter/tools/lipsum.py +20 -17
  69. novelwriter/tools/manusbuild.py +44 -35
  70. novelwriter/tools/manuscript.py +112 -112
  71. novelwriter/tools/manussettings.py +91 -98
  72. novelwriter/tools/noveldetails.py +20 -18
  73. novelwriter/tools/welcome.py +51 -48
  74. novelwriter/tools/writingstats.py +61 -55
  75. novelwriter/types.py +90 -0
  76. novelwriter/assets/icons/typicons_dark/typ_arrow-down.svg +0 -4
  77. novelwriter/assets/icons/typicons_dark/typ_arrow-right.svg +0 -4
  78. novelwriter/assets/icons/typicons_light/typ_arrow-down.svg +0 -4
  79. novelwriter/assets/icons/typicons_light/typ_arrow-right.svg +0 -4
  80. novelwriter/core/__init__.py +0 -3
  81. novelwriter/dialogs/__init__.py +0 -3
  82. novelwriter/extensions/__init__.py +0 -3
  83. novelwriter/gui/__init__.py +0 -3
  84. novelwriter/text/__init__.py +0 -3
  85. novelwriter/tools/__init__.py +0 -3
  86. {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/LICENSE.md +0 -0
  87. {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/WHEEL +0 -0
  88. {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/entry_points.txt +0 -0
  89. {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/top_level.txt +0 -0
@@ -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/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)
@@ -29,6 +29,8 @@ from PyQt5.QtCore import QSize, Qt
29
29
  from PyQt5.QtGui import QWheelEvent
30
30
  from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QToolButton, QWidget
31
31
 
32
+ from novelwriter import SHARED
33
+
32
34
 
33
35
  class NComboBox(QComboBox):
34
36
 
@@ -89,11 +91,40 @@ class NDoubleSpinBox(QDoubleSpinBox):
89
91
 
90
92
  class NIconToolButton(QToolButton):
91
93
 
92
- def __init__(self, parent: QWidget, iconSize: int) -> None:
94
+ def __init__(self, parent: QWidget, iconSize: QSize, icon: str | None = None) -> None:
93
95
  super().__init__(parent=parent)
94
96
  self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
95
- self.setIconSize(QSize(iconSize, iconSize))
97
+ self.setIconSize(iconSize)
96
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))
97
106
  return
98
107
 
99
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
@@ -32,6 +32,8 @@ from PyQt5.QtWidgets import (
32
32
  QStyleOptionToolButton, QToolBar, QToolButton, QWidget
33
33
  )
34
34
 
35
+ from novelwriter.types import QtPaintAnitAlias, QtAlignLeft, QtMouseOver, QtNoBrush, QtNoPen
36
+
35
37
 
36
38
  class NPagedSideBar(QToolBar):
37
39
  """Extensions: Paged Side Bar
@@ -54,10 +56,10 @@ class NPagedSideBar(QToolBar):
54
56
  self._group.buttonClicked.connect(self._buttonClicked)
55
57
 
56
58
  self.setMovable(False)
57
- self.setOrientation(Qt.Vertical)
59
+ self.setOrientation(Qt.Orientation.Vertical)
58
60
 
59
61
  stretch = QWidget(self)
60
- stretch.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
62
+ stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
61
63
  self._stretchAction = self.addWidget(stretch)
62
64
 
63
65
  return
@@ -117,13 +119,13 @@ class _NPagedToolButton(QToolButton):
117
119
  def __init__(self, parent: QWidget) -> None:
118
120
  super().__init__(parent=parent)
119
121
 
120
- self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
122
+ self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
121
123
  self.setCheckable(True)
122
124
 
123
125
  fH = self.fontMetrics().height()
124
126
  self._bH = round(fH * 1.7)
125
127
  self._tM = (self._bH - fH)//2
126
- self._lM = 3*self.style().pixelMetric(QStyle.PM_ButtonMargin)//2
128
+ self._lM = 3*self.style().pixelMetric(QStyle.PixelMetric.PM_ButtonMargin)//2
127
129
  self._cR = self._lM//2
128
130
  self._aH = 2*fH//7
129
131
  self.setFixedHeight(self._bH)
@@ -143,15 +145,15 @@ class _NPagedToolButton(QToolButton):
143
145
  opt.initFrom(self)
144
146
 
145
147
  paint = QPainter(self)
146
- paint.setRenderHint(QPainter.Antialiasing, True)
147
- paint.setPen(Qt.NoPen)
148
- paint.setBrush(Qt.NoBrush)
148
+ paint.setRenderHint(QtPaintAnitAlias, True)
149
+ paint.setPen(QtNoPen)
150
+ paint.setBrush(QtNoBrush)
149
151
 
150
152
  width = self.width()
151
153
  height = self.height()
152
154
  palette = self.palette()
153
155
 
154
- if opt.state & QStyle.State_MouseOver == QStyle.State_MouseOver:
156
+ if opt.state & QtMouseOver == QtMouseOver:
155
157
  backCol = palette.base()
156
158
  paint.setBrush(backCol)
157
159
  paint.setOpacity(0.75)
@@ -171,7 +173,7 @@ class _NPagedToolButton(QToolButton):
171
173
 
172
174
  paint.setPen(textCol)
173
175
  paint.setOpacity(1.0)
174
- paint.drawText(QRectF(self._lM, self._tM, tW, tH), Qt.AlignLeft, self.text())
176
+ paint.drawText(QRectF(self._lM, self._tM, tW, tH), QtAlignLeft, self.text())
175
177
 
176
178
  tC = self.height()//2
177
179
  tW = self.width() - self._aH - self._lM
@@ -195,12 +197,12 @@ class _NPagedToolLabel(QLabel):
195
197
  def __init__(self, parent: QWidget, textColor: QColor | None = None) -> None:
196
198
  super().__init__(parent=parent)
197
199
 
198
- self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
200
+ self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
199
201
 
200
202
  fH = self.fontMetrics().height()
201
203
  self._bH = round(fH * 1.7)
202
204
  self._tM = (self._bH - fH)//2
203
- self._lM = self.style().pixelMetric(QStyle.PM_ButtonMargin)//2
205
+ self._lM = self.style().pixelMetric(QStyle.PixelMetric.PM_ButtonMargin)//2
204
206
  self.setFixedHeight(self._bH)
205
207
 
206
208
  self._textCol = textColor or self.palette().text().color()
@@ -212,8 +214,8 @@ class _NPagedToolLabel(QLabel):
212
214
  label that matches the button style.
213
215
  """
214
216
  paint = QPainter(self)
215
- paint.setRenderHint(QPainter.Antialiasing, True)
216
- paint.setPen(Qt.NoPen)
217
+ paint.setRenderHint(QtPaintAnitAlias, True)
218
+ paint.setPen(QtNoPen)
217
219
 
218
220
  width = self.width()
219
221
  height = self.height()
@@ -223,7 +225,7 @@ class _NPagedToolLabel(QLabel):
223
225
 
224
226
  paint.setPen(self._textCol)
225
227
  paint.setOpacity(1.0)
226
- paint.drawText(QRectF(self._lM, self._tM, tW, tH), Qt.AlignLeft, self.text())
228
+ paint.drawText(QRectF(self._lM, self._tM, tW, tH), QtAlignLeft, self.text())
227
229
 
228
230
  return
229
231
 
@@ -28,6 +28,8 @@ from math import ceil
28
28
  from PyQt5.QtGui import QPaintEvent, QPainter
29
29
  from PyQt5.QtWidgets import QProgressBar, QWidget
30
30
 
31
+ from novelwriter.types import QtPaintAnitAlias
32
+
31
33
 
32
34
  class NProgressSimple(QProgressBar):
33
35
  """Extension: Simple Progress Widget
@@ -44,7 +46,7 @@ class NProgressSimple(QProgressBar):
44
46
  if (value := self.value()) > 0:
45
47
  progress = ceil(self.width()*float(value)/self.maximum())
46
48
  painter = QPainter(self)
47
- painter.setRenderHint(QPainter.Antialiasing, True)
49
+ painter.setRenderHint(QtPaintAnitAlias, True)
48
50
  painter.setPen(self.palette().highlight().color())
49
51
  painter.setBrush(self.palette().highlight())
50
52
  painter.drawRect(0, 0, progress, self.height())
@@ -30,6 +30,8 @@ from typing import Literal
30
30
  from PyQt5.QtGui import QColor, QPaintEvent, QPainter
31
31
  from PyQt5.QtWidgets import QAbstractButton, QWidget
32
32
 
33
+ from novelwriter.types import QtPaintAnitAlias
34
+
33
35
  logger = logging.getLogger(__name__)
34
36
 
35
37
 
@@ -67,7 +69,7 @@ class StatusLED(QAbstractButton):
67
69
  def paintEvent(self, event: QPaintEvent) -> None:
68
70
  """Draw the LED."""
69
71
  painter = QPainter(self)
70
- painter.setRenderHint(QPainter.Antialiasing, True)
72
+ painter.setRenderHint(QtPaintAnitAlias, True)
71
73
  painter.setPen(self.palette().dark().color())
72
74
  painter.setBrush(self._theCol)
73
75
  painter.setOpacity(1.0)
@@ -27,25 +27,26 @@ from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent
27
27
  from PyQt5.QtCore import QEvent, QPropertyAnimation, Qt, pyqtProperty
28
28
  from PyQt5.QtWidgets import QAbstractButton, QSizePolicy, QWidget
29
29
 
30
- from novelwriter import CONFIG
30
+ from novelwriter import CONFIG, SHARED
31
+ from novelwriter.types import QtPaintAnitAlias, QtMouseLeft, QtNoPen
31
32
 
32
33
 
33
34
  class NSwitch(QAbstractButton):
34
35
 
35
36
  __slots__ = ("_xW", "_xH", "_xR", "_rB", "_rH", "_rR", "_offset")
36
37
 
37
- def __init__(self, parent: QWidget | None = None, height: int = 0) -> None:
38
+ def __init__(self, parent: QWidget, height: int = 0) -> None:
38
39
  super().__init__(parent=parent)
39
40
 
40
- self._xH = height or CONFIG.pxInt(20)
41
+ self._xH = height or SHARED.theme.baseButtonHeight
41
42
  self._xW = 2*self._xH
42
43
  self._xR = int(self._xH*0.5)
43
- self._rB = int(CONFIG.guiScale*2)
44
+ self._rB = CONFIG.pxInt(2)
44
45
  self._rH = self._xH - 2*self._rB
45
46
  self._rR = self._xR - self._rB
46
47
 
47
48
  self.setCheckable(True)
48
- self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
49
+ self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
49
50
  self.setFixedWidth(self._xW)
50
51
  self.setFixedHeight(self._xH)
51
52
  self._offset = self._xR
@@ -89,8 +90,8 @@ class NSwitch(QAbstractButton):
89
90
  def paintEvent(self, event: QPaintEvent) -> None:
90
91
  """Drawing the switch itself."""
91
92
  painter = QPainter(self)
92
- painter.setRenderHint(QPainter.Antialiasing, True)
93
- painter.setPen(Qt.NoPen)
93
+ painter.setRenderHint(QtPaintAnitAlias, True)
94
+ painter.setPen(QtNoPen)
94
95
 
95
96
  palette = self.palette()
96
97
  if self.isChecked():
@@ -119,7 +120,7 @@ class NSwitch(QAbstractButton):
119
120
  def mouseReleaseEvent(self, event: QMouseEvent) -> None:
120
121
  """Animate the switch on mouse release."""
121
122
  super().mouseReleaseEvent(event)
122
- if event.button() == Qt.LeftButton:
123
+ if event.button() == QtMouseLeft:
123
124
  anim = QPropertyAnimation(self, b"offset", self)
124
125
  anim.setDuration(120)
125
126
  anim.setStartValue(self._offset)
@@ -129,7 +130,7 @@ class NSwitch(QAbstractButton):
129
130
 
130
131
  def enterEvent(self, event: QEvent) -> None:
131
132
  """Change the cursor when hovering the button."""
132
- self.setCursor(Qt.PointingHandCursor)
133
+ self.setCursor(Qt.CursorShape.PointingHandCursor)
133
134
  super().enterEvent(event)
134
135
  return
135
136
 
@@ -24,10 +24,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
24
24
  from __future__ import annotations
25
25
 
26
26
  from PyQt5.QtGui import QIcon
27
- from PyQt5.QtCore import Qt, pyqtSignal
27
+ from PyQt5.QtCore import pyqtSignal
28
28
  from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QSizePolicy, QWidget
29
29
 
30
30
  from novelwriter.extensions.switch import NSwitch
31
+ from novelwriter.types import QtAlignLeft, QtAlignRight, QtAlignRightMiddle
31
32
 
32
33
 
33
34
  class NSwitchBox(QScrollArea):
@@ -57,8 +58,8 @@ class NSwitchBox(QScrollArea):
57
58
  self._content = QGridLayout()
58
59
  self._content.setColumnStretch(1, 1)
59
60
 
60
- self._widget = QWidget()
61
- self._widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
61
+ self._widget = QWidget(self)
62
+ self._widget.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Minimum)
62
63
  self._widget.setLayout(self._content)
63
64
 
64
65
  self.setWidgetResizable(True)
@@ -68,29 +69,29 @@ class NSwitchBox(QScrollArea):
68
69
 
69
70
  def addLabel(self, text: str) -> None:
70
71
  """Add a header label to the content box."""
71
- label = QLabel(text)
72
+ label = QLabel(text, self)
72
73
  font = label.font()
73
74
  font.setBold(True)
74
75
  label.setFont(font)
75
- self._content.addWidget(label, self._index, 0, 1, 3, Qt.AlignLeft)
76
+ self._content.addWidget(label, self._index, 0, 1, 3, QtAlignLeft)
76
77
  self._widgets.append(label)
77
78
  self._bumpIndex()
78
79
  return
79
80
 
80
81
  def addItem(self, qIcon: QIcon, text: str, identifier: str, default: bool = False) -> None:
81
82
  """Add an item to the content box."""
82
- icon = QLabel("")
83
- icon.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
83
+ icon = QLabel("", self)
84
+ icon.setAlignment(QtAlignRightMiddle)
84
85
  icon.setPixmap(qIcon.pixmap(self._sIcon, self._sIcon))
85
- self._content.addWidget(icon, self._index, 0, Qt.AlignLeft)
86
+ self._content.addWidget(icon, self._index, 0, QtAlignLeft)
86
87
 
87
- label = QLabel(text)
88
- self._content.addWidget(label, self._index, 1, Qt.AlignLeft)
88
+ label = QLabel(text, self)
89
+ self._content.addWidget(label, self._index, 1, QtAlignLeft)
89
90
 
90
91
  switch = NSwitch(self, height=self._hSwitch)
91
92
  switch.setChecked(default)
92
93
  switch.toggled.connect(lambda state: self._emitSwitchSignal(identifier, state))
93
- self._content.addWidget(switch, self._index, 2, Qt.AlignRight)
94
+ self._content.addWidget(switch, self._index, 2, QtAlignRight)
94
95
 
95
96
  self._widgets.append(switch)
96
97
  self._bumpIndex()
@@ -99,9 +100,9 @@ class NSwitchBox(QScrollArea):
99
100
 
100
101
  def addSeparator(self) -> None:
101
102
  """Add a blank entry in the content box."""
102
- spacer = QWidget()
103
+ spacer = QWidget(self)
103
104
  spacer.setFixedHeight(int(0.5*self._sIcon))
104
- self._content.addWidget(spacer, self._index, 0, 1, 3, Qt.AlignLeft)
105
+ self._content.addWidget(spacer, self._index, 0, 1, 3, QtAlignLeft)
105
106
  self._widgets.append(spacer)
106
107
  self._bumpIndex()
107
108
  return