novelWriter 2.5.3__py3-none-any.whl → 2.6b2__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 (83) hide show
  1. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/METADATA +1 -1
  2. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/RECORD +80 -60
  3. novelwriter/__init__.py +49 -10
  4. novelwriter/assets/i18n/project_en_GB.json +1 -0
  5. novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
  6. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
  7. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
  8. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
  9. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
  10. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
  11. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
  12. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
  13. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
  14. novelwriter/assets/icons/typicons_light/icons.conf +8 -0
  15. novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
  16. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
  17. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
  18. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
  19. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
  20. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
  21. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
  22. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
  23. novelwriter/assets/manual.pdf +0 -0
  24. novelwriter/assets/sample.zip +0 -0
  25. novelwriter/common.py +100 -2
  26. novelwriter/config.py +25 -15
  27. novelwriter/constants.py +168 -60
  28. novelwriter/core/buildsettings.py +66 -39
  29. novelwriter/core/coretools.py +145 -147
  30. novelwriter/core/docbuild.py +132 -170
  31. novelwriter/core/index.py +38 -37
  32. novelwriter/core/item.py +41 -8
  33. novelwriter/core/itemmodel.py +518 -0
  34. novelwriter/core/options.py +4 -1
  35. novelwriter/core/project.py +67 -89
  36. novelwriter/core/spellcheck.py +9 -14
  37. novelwriter/core/status.py +7 -5
  38. novelwriter/core/tree.py +268 -287
  39. novelwriter/dialogs/docmerge.py +7 -17
  40. novelwriter/dialogs/preferences.py +46 -33
  41. novelwriter/dialogs/projectsettings.py +5 -5
  42. novelwriter/enum.py +36 -23
  43. novelwriter/extensions/configlayout.py +27 -12
  44. novelwriter/extensions/modified.py +13 -1
  45. novelwriter/extensions/pagedsidebar.py +5 -5
  46. novelwriter/formats/shared.py +155 -0
  47. novelwriter/formats/todocx.py +1191 -0
  48. novelwriter/formats/tohtml.py +451 -0
  49. novelwriter/{core → formats}/tokenizer.py +487 -491
  50. novelwriter/formats/tomarkdown.py +217 -0
  51. novelwriter/{core → formats}/toodt.py +311 -432
  52. novelwriter/formats/toqdoc.py +484 -0
  53. novelwriter/formats/toraw.py +91 -0
  54. novelwriter/gui/doceditor.py +342 -284
  55. novelwriter/gui/dochighlight.py +96 -84
  56. novelwriter/gui/docviewer.py +88 -31
  57. novelwriter/gui/docviewerpanel.py +17 -25
  58. novelwriter/gui/editordocument.py +17 -2
  59. novelwriter/gui/itemdetails.py +25 -28
  60. novelwriter/gui/mainmenu.py +129 -63
  61. novelwriter/gui/noveltree.py +45 -47
  62. novelwriter/gui/outline.py +196 -249
  63. novelwriter/gui/projtree.py +594 -1241
  64. novelwriter/gui/search.py +9 -10
  65. novelwriter/gui/sidebar.py +7 -6
  66. novelwriter/gui/theme.py +10 -5
  67. novelwriter/guimain.py +100 -196
  68. novelwriter/shared.py +66 -27
  69. novelwriter/text/counting.py +2 -0
  70. novelwriter/text/patterns.py +168 -60
  71. novelwriter/tools/manusbuild.py +14 -12
  72. novelwriter/tools/manuscript.py +120 -78
  73. novelwriter/tools/manussettings.py +424 -291
  74. novelwriter/tools/welcome.py +4 -4
  75. novelwriter/tools/writingstats.py +3 -3
  76. novelwriter/types.py +23 -7
  77. novelwriter/core/tohtml.py +0 -530
  78. novelwriter/core/tomarkdown.py +0 -252
  79. novelwriter/core/toqdoc.py +0 -419
  80. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/LICENSE.md +0 -0
  81. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/WHEEL +0 -0
  82. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/entry_points.txt +0 -0
  83. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/top_level.txt +0 -0
@@ -162,23 +162,13 @@ class GuiDocMerge(NDialog):
162
162
  self._data = {}
163
163
  self._data["sHandle"] = sHandle
164
164
  self._data["origItems"] = itemList
165
-
166
165
  self.listBox.clear()
167
166
  for tHandle in itemList:
168
- nwItem = SHARED.project.tree[tHandle]
169
- if nwItem is None or not nwItem.isFileType():
170
- continue
171
-
172
- itemIcon = SHARED.theme.getItemIcon(
173
- nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, nwItem.mainHeading
174
- )
175
-
176
- newItem = QListWidgetItem()
177
- newItem.setIcon(itemIcon)
178
- newItem.setText(nwItem.itemName)
179
- newItem.setData(self.D_HANDLE, tHandle)
180
- newItem.setCheckState(Qt.CheckState.Checked)
181
-
182
- self.listBox.addItem(newItem)
183
-
167
+ if (nwItem := SHARED.project.tree[tHandle]) and nwItem.isFileType():
168
+ item = QListWidgetItem()
169
+ item.setIcon(nwItem.getMainIcon())
170
+ item.setText(nwItem.itemName)
171
+ item.setData(self.D_HANDLE, tHandle)
172
+ item.setCheckState(Qt.CheckState.Checked)
173
+ self.listBox.addItem(item)
184
174
  return
@@ -34,7 +34,7 @@ from PyQt5.QtWidgets import (
34
34
  )
35
35
 
36
36
  from novelwriter import CONFIG, SHARED
37
- from novelwriter.common import describeFont, uniqueCompact
37
+ from novelwriter.common import compact, describeFont, uniqueCompact
38
38
  from novelwriter.constants import nwUnicode
39
39
  from novelwriter.dialogs.quotes import GuiQuoteSelect
40
40
  from novelwriter.extensions.configlayout import NColourLabel, NScrollableForm
@@ -133,7 +133,7 @@ class GuiPreferences(NDialog):
133
133
  """Build the settings form."""
134
134
  section = 0
135
135
  iSz = SHARED.theme.baseIconSize
136
- boxFixed = 5*SHARED.theme.textNWidth
136
+ boxFixed = 6*SHARED.theme.textNWidth
137
137
  minWidth = CONFIG.pxInt(200)
138
138
  fontWidth = CONFIG.pxInt(162)
139
139
 
@@ -543,6 +543,23 @@ class GuiPreferences(NDialog):
543
543
  self.tr("Applies to the selected quote styles.")
544
544
  )
545
545
 
546
+ self.altDialogOpen = QLineEdit(self)
547
+ self.altDialogOpen.setMaxLength(4)
548
+ self.altDialogOpen.setFixedWidth(boxFixed)
549
+ self.altDialogOpen.setAlignment(QtAlignCenter)
550
+ self.altDialogOpen.setText(CONFIG.altDialogOpen)
551
+
552
+ self.altDialogClose = QLineEdit(self)
553
+ self.altDialogClose.setMaxLength(4)
554
+ self.altDialogClose.setFixedWidth(boxFixed)
555
+ self.altDialogClose.setAlignment(QtAlignCenter)
556
+ self.altDialogClose.setText(CONFIG.altDialogClose)
557
+
558
+ self.mainForm.addRow(
559
+ self.tr("Alternative dialogue symbols"), [self.altDialogOpen, self.altDialogClose],
560
+ self.tr("Custom highlighting of dialogue text.")
561
+ )
562
+
546
563
  self.allowOpenDial = NSwitch(self)
547
564
  self.allowOpenDial.setChecked(CONFIG.allowOpenDial)
548
565
  self.mainForm.addRow(
@@ -550,41 +567,34 @@ class GuiPreferences(NDialog):
550
567
  self.tr("Highlight dialogue line with no closing quote.")
551
568
  )
552
569
 
553
- self.narratorBreak = QLineEdit(self)
554
- self.narratorBreak.setMaxLength(1)
555
- self.narratorBreak.setFixedWidth(boxFixed)
556
- self.narratorBreak.setAlignment(QtAlignCenter)
557
- self.narratorBreak.setText(CONFIG.narratorBreak)
558
- self.mainForm.addRow(
559
- self.tr("Dialogue narrator break symbol"), self.narratorBreak,
560
- self.tr("Symbol to indicate injected narrator break.")
561
- )
562
-
563
570
  self.dialogLine = QLineEdit(self)
564
- self.dialogLine.setMaxLength(1)
571
+ self.dialogLine.setMaxLength(4)
565
572
  self.dialogLine.setFixedWidth(boxFixed)
566
573
  self.dialogLine.setAlignment(QtAlignCenter)
567
574
  self.dialogLine.setText(CONFIG.dialogLine)
568
575
  self.mainForm.addRow(
569
- self.tr("Dialogue line symbol"), self.dialogLine,
570
- self.tr("Lines starting with this symbol are dialogue.")
576
+ self.tr("Dialogue line symbols"), self.dialogLine,
577
+ self.tr("Lines starting with any of these symbols are dialogue.")
571
578
  )
572
579
 
573
- self.altDialogOpen = QLineEdit(self)
574
- self.altDialogOpen.setMaxLength(4)
575
- self.altDialogOpen.setFixedWidth(boxFixed)
576
- self.altDialogOpen.setAlignment(QtAlignCenter)
577
- self.altDialogOpen.setText(CONFIG.altDialogOpen)
578
-
579
- self.altDialogClose = QLineEdit(self)
580
- self.altDialogClose.setMaxLength(4)
581
- self.altDialogClose.setFixedWidth(boxFixed)
582
- self.altDialogClose.setAlignment(QtAlignCenter)
583
- self.altDialogClose.setText(CONFIG.altDialogClose)
580
+ self.narratorBreak = QLineEdit(self)
581
+ self.narratorBreak.setMaxLength(1)
582
+ self.narratorBreak.setFixedWidth(boxFixed)
583
+ self.narratorBreak.setAlignment(QtAlignCenter)
584
+ self.narratorBreak.setText(CONFIG.narratorBreak)
585
+ self.mainForm.addRow(
586
+ self.tr("Narrator break symbol"), self.narratorBreak,
587
+ self.tr("Symbol to indicate a narrator break in dialogue")
588
+ )
584
589
 
590
+ self.narratorDialog = QLineEdit(self)
591
+ self.narratorDialog.setMaxLength(1)
592
+ self.narratorDialog.setFixedWidth(boxFixed)
593
+ self.narratorDialog.setAlignment(QtAlignCenter)
594
+ self.narratorDialog.setText(CONFIG.narratorDialog)
585
595
  self.mainForm.addRow(
586
- self.tr("Alternative dialogue symbols"), [self.altDialogOpen, self.altDialogClose],
587
- self.tr("Custom highlighting of dialogue text.")
596
+ self.tr("Alternating dialogue/narration symbol"), self.narratorDialog,
597
+ self.tr("Alternates dialogue highlighting within any paragraph.")
588
598
  )
589
599
 
590
600
  self.highlightEmph = NSwitch(self)
@@ -952,17 +962,19 @@ class GuiPreferences(NDialog):
952
962
  # Text Highlighting
953
963
  dialogueStyle = self.dialogStyle.currentData()
954
964
  allowOpenDial = self.allowOpenDial.isChecked()
965
+ dialogueLine = uniqueCompact(self.dialogLine.text())
955
966
  narratorBreak = self.narratorBreak.text().strip()
956
- dialogueLine = self.dialogLine.text().strip()
957
- altDialogOpen = self.altDialogOpen.text()
958
- altDialogClose = self.altDialogClose.text()
967
+ narratorDialog = self.narratorDialog.text().strip()
968
+ altDialogOpen = compact(self.altDialogOpen.text())
969
+ altDialogClose = compact(self.altDialogClose.text())
959
970
  highlightEmph = self.highlightEmph.isChecked()
960
971
  showMultiSpaces = self.showMultiSpaces.isChecked()
961
972
 
962
973
  updateSyntax |= CONFIG.dialogStyle != dialogueStyle
963
974
  updateSyntax |= CONFIG.allowOpenDial != allowOpenDial
964
- updateSyntax |= CONFIG.narratorBreak != narratorBreak
965
975
  updateSyntax |= CONFIG.dialogLine != dialogueLine
976
+ updateSyntax |= CONFIG.narratorBreak != narratorBreak
977
+ updateSyntax |= CONFIG.narratorDialog != narratorDialog
966
978
  updateSyntax |= CONFIG.altDialogOpen != altDialogOpen
967
979
  updateSyntax |= CONFIG.altDialogClose != altDialogClose
968
980
  updateSyntax |= CONFIG.highlightEmph != highlightEmph
@@ -970,8 +982,9 @@ class GuiPreferences(NDialog):
970
982
 
971
983
  CONFIG.dialogStyle = dialogueStyle
972
984
  CONFIG.allowOpenDial = allowOpenDial
973
- CONFIG.narratorBreak = narratorBreak
974
985
  CONFIG.dialogLine = dialogueLine
986
+ CONFIG.narratorBreak = narratorBreak
987
+ CONFIG.narratorDialog = narratorDialog
975
988
  CONFIG.altDialogOpen = altDialogOpen
976
989
  CONFIG.altDialogClose = altDialogClose
977
990
  CONFIG.highlightEmph = highlightEmph
@@ -35,7 +35,7 @@ from PyQt5.QtWidgets import (
35
35
  )
36
36
 
37
37
  from novelwriter import CONFIG, SHARED
38
- from novelwriter.common import simplified
38
+ from novelwriter.common import qtLambda, simplified
39
39
  from novelwriter.constants import nwLabels, trConst
40
40
  from novelwriter.core.status import NWStatus, StatusEntry
41
41
  from novelwriter.enum import nwStatusShape
@@ -185,11 +185,11 @@ class GuiProjectSettings(NDialog):
185
185
 
186
186
  if self.statusPage.changed:
187
187
  logger.debug("Updating status labels")
188
- project.data.itemStatus.update(self.statusPage.getNewList())
188
+ project.updateStatus("s", self.statusPage.getNewList())
189
189
 
190
190
  if self.importPage.changed:
191
191
  logger.debug("Updating importance labels")
192
- project.data.itemImport.update(self.importPage.getNewList())
192
+ project.updateStatus("i", self.importPage.getNewList())
193
193
 
194
194
  if self.replacePage.changed:
195
195
  logger.debug("Updating auto-replace settings")
@@ -365,10 +365,10 @@ class _StatusPage(NFixedPage):
365
365
  self.delButton.clicked.connect(self._delItem)
366
366
 
367
367
  self.upButton = NIconToolButton(self, iSz, "up")
368
- self.upButton.clicked.connect(lambda: self._moveItem(-1))
368
+ self.upButton.clicked.connect(qtLambda(self._moveItem, -1))
369
369
 
370
370
  self.dnButton = NIconToolButton(self, iSz, "down")
371
- self.dnButton.clicked.connect(lambda: self._moveItem(1))
371
+ self.dnButton.clicked.connect(qtLambda(self._moveItem, 1))
372
372
 
373
373
  # Edit Form
374
374
  self.editName = QLineEdit(self)
novelwriter/enum.py CHANGED
@@ -75,6 +75,13 @@ class nwTrinary(Enum):
75
75
  POSITIVE = 1
76
76
 
77
77
 
78
+ class nwChange(Enum):
79
+
80
+ CREATE = 0
81
+ UPDATE = 1
82
+ DELETE = 2
83
+
84
+
78
85
  class nwDocMode(Enum):
79
86
 
80
87
  VIEW = 0
@@ -137,6 +144,7 @@ class nwDocInsert(Enum):
137
144
  VSPACE_M = 9
138
145
  LIPSUM = 10
139
146
  FOOTNOTE = 11
147
+ LINE_BRK = 12
140
148
 
141
149
 
142
150
  class nwView(Enum):
@@ -157,35 +165,40 @@ class nwFocus(Enum):
157
165
 
158
166
  class nwOutline(Enum):
159
167
 
160
- TITLE = 0
161
- LEVEL = 1
162
- LABEL = 2
163
- LINE = 3
164
- CCOUNT = 4
165
- WCOUNT = 5
166
- PCOUNT = 6
167
- POV = 7
168
- FOCUS = 8
169
- CHAR = 9
170
- PLOT = 10
171
- TIME = 11
172
- WORLD = 12
173
- OBJECT = 13
174
- ENTITY = 14
175
- CUSTOM = 15
176
- SYNOP = 16
168
+ TITLE = 0
169
+ LEVEL = 1
170
+ LABEL = 2
171
+ LINE = 3
172
+ STATUS = 4
173
+ CCOUNT = 5
174
+ WCOUNT = 6
175
+ PCOUNT = 7
176
+ POV = 8
177
+ FOCUS = 9
178
+ CHAR = 10
179
+ PLOT = 11
180
+ TIME = 12
181
+ WORLD = 13
182
+ OBJECT = 14
183
+ ENTITY = 15
184
+ CUSTOM = 16
185
+ STORY = 17
186
+ MENTION = 18
187
+ SYNOP = 19
177
188
 
178
189
 
179
190
  class nwBuildFmt(Enum):
180
191
 
181
192
  ODT = 0
182
193
  FODT = 1
183
- HTML = 2
184
- NWD = 3
185
- STD_MD = 4
186
- EXT_MD = 5
187
- J_HTML = 6
188
- J_NWD = 7
194
+ DOCX = 2
195
+ PDF = 3
196
+ HTML = 4
197
+ STD_MD = 5
198
+ EXT_MD = 6
199
+ NWD = 7
200
+ J_HTML = 8
201
+ J_NWD = 9
189
202
 
190
203
 
191
204
  class nwStatusShape(Enum):
@@ -27,14 +27,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
27
27
  """
28
28
  from __future__ import annotations
29
29
 
30
- from PyQt5.QtCore import Qt
31
- from PyQt5.QtGui import QColor, QFont, QPalette
30
+ from PyQt5.QtGui import QColor, QFont, QPalette, QPixmap
32
31
  from PyQt5.QtWidgets import (
33
32
  QAbstractButton, QFrame, QHBoxLayout, QLabel, QLayout, QScrollArea,
34
33
  QVBoxLayout, QWidget
35
34
  )
36
35
 
37
36
  from novelwriter import CONFIG
37
+ from novelwriter.types import QtScrollAsNeeded
38
38
 
39
39
  DEFAULT_SCALE = 0.9
40
40
 
@@ -76,8 +76,8 @@ class NScrollablePage(QScrollArea):
76
76
  self._widget = QWidget(self)
77
77
  self.setWidget(self._widget)
78
78
  self.setWidgetResizable(True)
79
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
80
- self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
79
+ self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
80
+ self.setVerticalScrollBarPolicy(QtScrollAsNeeded)
81
81
  self.setFrameShadow(QFrame.Shadow.Sunken)
82
82
  self.setFrameShape(QFrame.Shape.StyledPanel)
83
83
  return
@@ -113,8 +113,8 @@ class NScrollableForm(QScrollArea):
113
113
 
114
114
  self.setWidget(self._widget)
115
115
  self.setWidgetResizable(True)
116
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
117
- self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
116
+ self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
117
+ self.setVerticalScrollBarPolicy(QtScrollAsNeeded)
118
118
  self.setFrameShadow(QFrame.Shadow.Sunken)
119
119
  self.setFrameShape(QFrame.Shape.StyledPanel)
120
120
 
@@ -180,9 +180,16 @@ class NScrollableForm(QScrollArea):
180
180
  self._sections[identifier] = qLabel
181
181
  return
182
182
 
183
- def addRow(self, label: str, widget: QWidget | list[QWidget], helpText: str = "",
184
- unit: str | None = None, button: QWidget | None = None, editable: str | None = None,
185
- stretch: tuple[int, int] = (1, 0)) -> None:
183
+ def addRow(
184
+ self,
185
+ label: str | None,
186
+ widget: QWidget | list[QWidget | QPixmap | int],
187
+ helpText: str = "",
188
+ unit: str | None = None,
189
+ button: QWidget | None = None,
190
+ editable: str | None = None,
191
+ stretch: tuple[int, int] = (1, 0),
192
+ ) -> None:
186
193
  """Add a label and a widget as a new row of the form."""
187
194
  row = QHBoxLayout()
188
195
  row.setSpacing(CONFIG.pxInt(12))
@@ -191,13 +198,20 @@ class NScrollableForm(QScrollArea):
191
198
  wBox = QHBoxLayout()
192
199
  wBox.setContentsMargins(0, 0, 0, 0)
193
200
  for item in widget:
194
- wBox.addWidget(item)
201
+ if isinstance(item, QWidget):
202
+ wBox.addWidget(item)
203
+ elif isinstance(item, QPixmap):
204
+ icon = QLabel(self)
205
+ icon.setPixmap(item)
206
+ wBox.addWidget(icon)
207
+ elif isinstance(item, int):
208
+ wBox.addSpacing(CONFIG.pxInt(item))
195
209
  qWidget = QWidget(self)
196
210
  qWidget.setLayout(wBox)
197
211
  else:
198
212
  qWidget = widget
199
213
 
200
- qLabel = QLabel(label, self)
214
+ qLabel = QLabel(label or "", self)
201
215
  qLabel.setIndent(self._indent)
202
216
  qLabel.setBuddy(qWidget)
203
217
 
@@ -230,7 +244,8 @@ class NScrollableForm(QScrollArea):
230
244
  row.addWidget(qWidget, stretch[1])
231
245
 
232
246
  self._layout.addLayout(row)
233
- self._index[label.strip()] = qWidget
247
+ if label:
248
+ self._index[label.strip()] = qWidget
234
249
  self._first = False
235
250
 
236
251
  return
@@ -136,9 +136,21 @@ class NSpinBox(QSpinBox):
136
136
 
137
137
  class NDoubleSpinBox(QDoubleSpinBox):
138
138
 
139
- def __init__(self, parent: QWidget | None = None) -> None:
139
+ def __init__(
140
+ self,
141
+ parent: QWidget | None = None,
142
+ *,
143
+ min: float = 0.0,
144
+ max: float = 15.0,
145
+ step: float = 0.1,
146
+ prec: int = 2,
147
+ ) -> None:
140
148
  super().__init__(parent=parent)
141
149
  self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
150
+ self.setMinimum(min)
151
+ self.setMaximum(max)
152
+ self.setSingleStep(step)
153
+ self.setDecimals(prec)
142
154
  return
143
155
 
144
156
  def wheelEvent(self, event: QWheelEvent) -> None:
@@ -52,7 +52,7 @@ class NPagedSideBar(QToolBar):
52
52
 
53
53
  self._labelCol = None
54
54
  self._spacerHeight = self.fontMetrics().height() // 2
55
- self._buttons: dict[int, _NPagedToolButton] = {}
55
+ self._buttons: dict[int, _PagedToolButton] = {}
56
56
 
57
57
  self._group = QButtonGroup(self)
58
58
  self._group.setExclusive(True)
@@ -67,7 +67,7 @@ class NPagedSideBar(QToolBar):
67
67
 
68
68
  return
69
69
 
70
- def button(self, buttonId: int) -> _NPagedToolButton:
70
+ def button(self, buttonId: int) -> _PagedToolButton:
71
71
  """Return a specific button."""
72
72
  return self._buttons[buttonId]
73
73
 
@@ -85,7 +85,7 @@ class NPagedSideBar(QToolBar):
85
85
 
86
86
  def addButton(self, text: str, buttonId: int = -1) -> QAction:
87
87
  """Add a new button to the toolbar."""
88
- button = _NPagedToolButton(self)
88
+ button = _PagedToolButton(self)
89
89
  button.setText(text)
90
90
 
91
91
  action = self.insertWidget(self._stretchAction, button)
@@ -113,7 +113,7 @@ class NPagedSideBar(QToolBar):
113
113
  return
114
114
 
115
115
 
116
- class _NPagedToolButton(QToolButton):
116
+ class _PagedToolButton(QToolButton):
117
117
 
118
118
  __slots__ = ("_bH", "_tM", "_lM", "_cR", "_aH")
119
119
 
@@ -154,7 +154,7 @@ class _NPagedToolButton(QToolButton):
154
154
  height = self.height()
155
155
  palette = self.palette()
156
156
 
157
- if opt.state & QtMouseOver == QtMouseOver:
157
+ if opt.state & QtMouseOver == QtMouseOver: # pragma: no cover
158
158
  backCol = palette.base()
159
159
  paint.setBrush(backCol)
160
160
  paint.setOpacity(0.75)
@@ -0,0 +1,155 @@
1
+ """
2
+ novelWriter – Formats Shared
3
+ ============================
4
+
5
+ File History:
6
+ Created: 2024-10-21 [2.6b1]
7
+
8
+ This file is a part of novelWriter
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
+
11
+ This program is free software: you can redistribute it and/or modify
12
+ it under the terms of the GNU General Public License as published by
13
+ the Free Software Foundation, either version 3 of the License, or
14
+ (at your option) any later version.
15
+
16
+ This program is distributed in the hope that it will be useful, but
17
+ WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19
+ General Public License for more details.
20
+
21
+ You should have received a copy of the GNU General Public License
22
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import re
27
+
28
+ from enum import Flag, IntEnum
29
+
30
+ from PyQt5.QtGui import QColor
31
+
32
+ ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""}
33
+ RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL)
34
+
35
+
36
+ def stripEscape(text: str) -> str:
37
+ """Strip escaped Markdown characters from paragraph text."""
38
+ if "\\" in text:
39
+ return RX_ESC.sub(lambda x: ESCAPES[x.group(0)], text)
40
+ return text
41
+
42
+
43
+ class TextDocumentTheme:
44
+ """Default document theme."""
45
+
46
+ text: QColor = QColor(0, 0, 0)
47
+ highlight: QColor = QColor(255, 255, 166)
48
+ head: QColor = QColor(66, 113, 174)
49
+ link: QColor = QColor(66, 113, 174)
50
+ comment: QColor = QColor(100, 100, 100)
51
+ note: QColor = QColor(129, 55, 9)
52
+ code: QColor = QColor(66, 113, 174)
53
+ modifier: QColor = QColor(129, 55, 9)
54
+ keyword: QColor = QColor(245, 135, 31)
55
+ tag: QColor = QColor(66, 113, 174)
56
+ optional: QColor = QColor(66, 113, 174)
57
+ dialog: QColor = QColor(66, 113, 174)
58
+ altdialog: QColor = QColor(129, 55, 9)
59
+
60
+
61
+ # Enums
62
+ # =====
63
+
64
+ class TextFmt(IntEnum):
65
+ """Text Format.
66
+
67
+ An enum indicating the beginning or end of a text format region.
68
+ They must be paired with a position, and apply to locations in a
69
+ text block.
70
+ """
71
+
72
+ B_B = 1 # Begin bold
73
+ B_E = 2 # End bold
74
+ I_B = 3 # Begin italics
75
+ I_E = 4 # End italics
76
+ D_B = 5 # Begin strikeout
77
+ D_E = 6 # End strikeout
78
+ U_B = 7 # Begin underline
79
+ U_E = 8 # End underline
80
+ M_B = 9 # Begin mark
81
+ M_E = 10 # End mark
82
+ SUP_B = 11 # Begin superscript
83
+ SUP_E = 12 # End superscript
84
+ SUB_B = 13 # Begin subscript
85
+ SUB_E = 14 # End subscript
86
+ COL_B = 15 # Begin colour
87
+ COL_E = 16 # End colour
88
+ ANM_B = 17 # Begin anchor name
89
+ ANM_E = 18 # End anchor name
90
+ ARF_B = 19 # Begin anchor link
91
+ ARF_E = 20 # End anchor link
92
+ HRF_B = 21 # Begin href link
93
+ HRF_E = 22 # End href link
94
+ FNOTE = 23 # Footnote marker
95
+ FIELD = 24 # Data field
96
+ STRIP = 25 # Strip the format code
97
+
98
+
99
+ class BlockTyp(IntEnum):
100
+ """Text Block Type.
101
+
102
+ An enum indicating the type of a text block.
103
+ """
104
+
105
+ EMPTY = 1 # Empty line (new paragraph)
106
+ TITLE = 2 # Title
107
+ HEAD1 = 3 # Heading 1
108
+ HEAD2 = 4 # Heading 2
109
+ HEAD3 = 5 # Heading 3
110
+ HEAD4 = 6 # Heading 4
111
+ TEXT = 7 # Text line
112
+ SEP = 8 # Scene separator
113
+ SKIP = 9 # Paragraph break
114
+ SUMMARY = 10 # Synopsis/short comment
115
+ NOTE = 11 # Note
116
+ COMMENT = 12 # Comment
117
+ KEYWORD = 13 # Tag/reference keywords
118
+
119
+
120
+ class BlockFmt(Flag):
121
+ """Text Block Format.
122
+
123
+ An enum of flags that can be combined to format a text block.
124
+ """
125
+
126
+ NONE = 0x0000 # No special style
127
+ LEFT = 0x0001 # Left aligned
128
+ RIGHT = 0x0002 # Right aligned
129
+ CENTRE = 0x0004 # Centred
130
+ JUSTIFY = 0x0008 # Justified
131
+ PBB = 0x0010 # Page break before
132
+ PBA = 0x0020 # Page break after
133
+ Z_TOP = 0x0040 # Zero top margin
134
+ Z_BTM = 0x0080 # Zero bottom margin
135
+ IND_L = 0x0100 # Left indentation
136
+ IND_R = 0x0200 # Right indentation
137
+ IND_T = 0x0400 # Text indentation
138
+
139
+ # Masks
140
+ ALIGNED = LEFT | RIGHT | CENTRE | JUSTIFY
141
+
142
+
143
+ # Types
144
+ # =====
145
+
146
+ # A list of formats for a single text string, consisting of:
147
+ # text position, text format, and meta data
148
+ T_Formats = list[tuple[int, TextFmt, str]]
149
+
150
+ # A note or comment with text and associated text formats
151
+ T_Note = tuple[str, T_Formats]
152
+
153
+ # A tokenized text block, consisting of:
154
+ # type, header number, text, text formats, and block format
155
+ T_Block = tuple[BlockTyp, str, str, T_Formats, BlockFmt]