novelWriter 2.5b1__py3-none-any.whl → 2.5rc1__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 (62) hide show
  1. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/RECORD +61 -61
  3. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +3 -3
  5. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  6. novelwriter/assets/i18n/project_pt_BR.json +74 -74
  7. novelwriter/assets/manual.pdf +0 -0
  8. novelwriter/assets/sample.zip +0 -0
  9. novelwriter/assets/themes/cyberpunk_night.conf +1 -0
  10. novelwriter/assets/themes/default_dark.conf +1 -0
  11. novelwriter/assets/themes/default_light.conf +1 -0
  12. novelwriter/assets/themes/dracula.conf +1 -0
  13. novelwriter/assets/themes/solarized_dark.conf +1 -0
  14. novelwriter/assets/themes/solarized_light.conf +1 -0
  15. novelwriter/common.py +2 -3
  16. novelwriter/config.py +67 -15
  17. novelwriter/constants.py +8 -10
  18. novelwriter/core/buildsettings.py +5 -3
  19. novelwriter/core/coretools.py +3 -1
  20. novelwriter/core/docbuild.py +1 -0
  21. novelwriter/core/tohtml.py +69 -29
  22. novelwriter/core/tokenizer.py +83 -14
  23. novelwriter/core/toodt.py +48 -21
  24. novelwriter/core/toqdoc.py +25 -9
  25. novelwriter/dialogs/about.py +10 -15
  26. novelwriter/dialogs/docmerge.py +16 -16
  27. novelwriter/dialogs/docsplit.py +16 -16
  28. novelwriter/dialogs/editlabel.py +6 -8
  29. novelwriter/dialogs/preferences.py +94 -68
  30. novelwriter/dialogs/projectsettings.py +10 -10
  31. novelwriter/dialogs/quotes.py +9 -5
  32. novelwriter/dialogs/wordlist.py +6 -6
  33. novelwriter/enum.py +4 -5
  34. novelwriter/extensions/configlayout.py +23 -4
  35. novelwriter/extensions/modified.py +22 -3
  36. novelwriter/extensions/{circularprogress.py → progressbars.py} +26 -3
  37. novelwriter/extensions/statusled.py +28 -22
  38. novelwriter/gui/doceditor.py +20 -11
  39. novelwriter/gui/dochighlight.py +30 -39
  40. novelwriter/gui/docviewer.py +21 -14
  41. novelwriter/gui/mainmenu.py +11 -11
  42. novelwriter/gui/outline.py +3 -3
  43. novelwriter/gui/projtree.py +19 -28
  44. novelwriter/gui/search.py +10 -1
  45. novelwriter/gui/statusbar.py +25 -29
  46. novelwriter/gui/theme.py +3 -0
  47. novelwriter/guimain.py +91 -84
  48. novelwriter/shared.py +10 -8
  49. novelwriter/text/patterns.py +113 -0
  50. novelwriter/tools/dictionaries.py +2 -8
  51. novelwriter/tools/lipsum.py +8 -12
  52. novelwriter/tools/manusbuild.py +9 -9
  53. novelwriter/tools/manuscript.py +10 -5
  54. novelwriter/tools/manussettings.py +7 -3
  55. novelwriter/tools/noveldetails.py +10 -10
  56. novelwriter/tools/welcome.py +10 -10
  57. novelwriter/tools/writingstats.py +3 -3
  58. novelwriter/types.py +5 -2
  59. novelwriter/extensions/simpleprogress.py +0 -53
  60. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/LICENSE.md +0 -0
  61. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/entry_points.txt +0 -0
  62. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/top_level.txt +0 -0
@@ -28,18 +28,22 @@ import logging
28
28
  from PyQt5.QtCore import QSize, pyqtSlot
29
29
  from PyQt5.QtGui import QFontMetrics
30
30
  from PyQt5.QtWidgets import (
31
- QDialog, QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
31
+ QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
32
32
  QListWidgetItem, QVBoxLayout, QWidget
33
33
  )
34
34
 
35
35
  from novelwriter import CONFIG
36
36
  from novelwriter.constants import nwQuotes, trConst
37
- from novelwriter.types import QtAlignCenter, QtAlignTop, QtDialogCancel, QtDialogOk, QtUserRole
37
+ from novelwriter.extensions.modified import NDialog
38
+ from novelwriter.types import (
39
+ QtAccepted, QtAlignCenter, QtAlignTop, QtDialogCancel, QtDialogOk,
40
+ QtUserRole
41
+ )
38
42
 
39
43
  logger = logging.getLogger(__name__)
40
44
 
41
45
 
42
- class GuiQuoteSelect(QDialog):
46
+ class GuiQuoteSelect(NDialog):
43
47
 
44
48
  _selected = ""
45
49
 
@@ -126,8 +130,8 @@ class GuiQuoteSelect(QDialog):
126
130
  cls = GuiQuoteSelect(parent, current=current)
127
131
  cls.exec()
128
132
  quote = cls._selected
129
- accepted = cls.result() == QDialog.DialogCode.Accepted
130
- cls.deleteLater()
133
+ accepted = cls.result() == QtAccepted
134
+ cls.softDelete()
131
135
  return quote, accepted
132
136
 
133
137
  ##
@@ -30,7 +30,7 @@ from pathlib import Path
30
30
  from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
31
31
  from PyQt5.QtGui import QCloseEvent
32
32
  from PyQt5.QtWidgets import (
33
- QAbstractItemView, QApplication, QDialog, QDialogButtonBox, QFileDialog,
33
+ QAbstractItemView, QApplication, QDialogButtonBox, QFileDialog,
34
34
  QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout, QWidget
35
35
  )
36
36
 
@@ -38,13 +38,13 @@ from novelwriter import CONFIG, SHARED
38
38
  from novelwriter.common import formatFileFilter
39
39
  from novelwriter.core.spellcheck import UserDictionary
40
40
  from novelwriter.extensions.configlayout import NColourLabel
41
- from novelwriter.extensions.modified import NIconToolButton
41
+ from novelwriter.extensions.modified import NDialog, NIconToolButton
42
42
  from novelwriter.types import QtDialogClose, QtDialogSave
43
43
 
44
44
  logger = logging.getLogger(__name__)
45
45
 
46
46
 
47
- class GuiWordList(QDialog):
47
+ class GuiWordList(NDialog):
48
48
 
49
49
  newWordListReady = pyqtSignal()
50
50
 
@@ -69,7 +69,7 @@ class GuiWordList(QDialog):
69
69
 
70
70
  # Header
71
71
  self.headLabel = NColourLabel(
72
- self.tr("Project Word List"), SHARED.theme.helpText, parent=self,
72
+ self.tr("Project Word List"), self, color=SHARED.theme.helpText,
73
73
  scale=NColourLabel.HEADER_SCALE
74
74
  )
75
75
 
@@ -109,7 +109,7 @@ class GuiWordList(QDialog):
109
109
  # Buttons
110
110
  self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogClose, self)
111
111
  self.buttonBox.accepted.connect(self._doSave)
112
- self.buttonBox.rejected.connect(self.close)
112
+ self.buttonBox.rejected.connect(self.reject)
113
113
 
114
114
  # Assemble
115
115
  self.outerBox = QVBoxLayout()
@@ -140,7 +140,7 @@ class GuiWordList(QDialog):
140
140
  """Capture the close event and perform cleanup."""
141
141
  self._saveGuiSettings()
142
142
  event.accept()
143
- self.deleteLater()
143
+ self.softDelete()
144
144
  return
145
145
 
146
146
  ##
novelwriter/enum.py CHANGED
@@ -148,12 +148,11 @@ class nwView(Enum):
148
148
  SEARCH = 4
149
149
 
150
150
 
151
- class nwWidget(Enum):
151
+ class nwFocus(Enum):
152
152
 
153
- TREE = 1
154
- EDITOR = 2
155
- VIEWER = 3
156
- OUTLINE = 4
153
+ TREE = 1
154
+ DOCUMENT = 2
155
+ OUTLINE = 3
157
156
 
158
157
 
159
158
  class nwOutline(Enum):
@@ -203,7 +203,7 @@ class NScrollableForm(QScrollArea):
203
203
 
204
204
  if helpText:
205
205
  qHelp = NColourLabel(
206
- str(helpText), color=self._helpCol, parent=self,
206
+ str(helpText), self, color=self._helpCol,
207
207
  scale=self._fontScale, wrap=True, indent=self._indent
208
208
  )
209
209
  labelBox = QVBoxLayout()
@@ -252,11 +252,20 @@ class NColourLabel(QLabel):
252
252
  HELP_SCALE = DEFAULT_SCALE
253
253
  HEADER_SCALE = 1.25
254
254
 
255
- def __init__(self, text: str, color: QColor | None = None, parent: QWidget | None = None,
256
- scale: float = HELP_SCALE, wrap: bool = False, indent: int = 0,
257
- bold: bool = False) -> None:
255
+ _state = None
256
+
257
+ def __init__(
258
+ self, text: str, parent: QWidget, *,
259
+ color: QColor | None = None, faded: QColor | None = None,
260
+ scale: float = HELP_SCALE, wrap: bool = False, indent: int = 0,
261
+ bold: bool = False
262
+ ) -> None:
258
263
  super().__init__(text, parent=parent)
259
264
 
265
+ default = self.palette().windowText().color()
266
+ self._color = color or default
267
+ self._faded = faded or default
268
+
260
269
  font = self.font()
261
270
  font.setPointSizeF(scale*font.pointSizeF())
262
271
  font.setWeight(QFont.Weight.Bold if bold else QFont.Weight.Normal)
@@ -268,7 +277,17 @@ class NColourLabel(QLabel):
268
277
  self.setFont(font)
269
278
  self.setIndent(indent)
270
279
  self.setWordWrap(wrap)
280
+ self.setColorState(True)
281
+
282
+ return
271
283
 
284
+ def setColorState(self, state: bool) -> None:
285
+ """Change the colour state."""
286
+ if self._state is not state:
287
+ self._state = state
288
+ colour = self.palette()
289
+ colour.setColor(QPalette.ColorRole.WindowText, self._color if state else self._faded)
290
+ self.setPalette(colour)
272
291
  return
273
292
 
274
293
 
@@ -30,7 +30,7 @@ from __future__ import annotations
30
30
  from enum import Enum
31
31
  from typing import TYPE_CHECKING
32
32
 
33
- from PyQt5.QtCore import QSize, Qt
33
+ from PyQt5.QtCore import QSize, Qt, pyqtSlot
34
34
  from PyQt5.QtGui import QWheelEvent
35
35
  from PyQt5.QtWidgets import (
36
36
  QApplication, QComboBox, QDialog, QDoubleSpinBox, QSpinBox, QToolButton,
@@ -43,7 +43,26 @@ if TYPE_CHECKING: # pragma: no cover
43
43
  from novelwriter.guimain import GuiMain
44
44
 
45
45
 
46
- class NToolDialog(QDialog):
46
+ class NDialog(QDialog):
47
+
48
+ def softDelete(self) -> None:
49
+ """Since calling deleteLater is sometimes not safe from Python,
50
+ as the C++ object can be deleted before the Python process is
51
+ done with the object, we instead set the dialog's parent to None
52
+ so that it gets garbage collected when it runs out of scope.
53
+ """
54
+ self.setParent(None) # type: ignore
55
+ return
56
+
57
+ @pyqtSlot()
58
+ def reject(self) -> None:
59
+ """Overload the reject slot and also call close."""
60
+ super().reject()
61
+ self.close()
62
+ return
63
+
64
+
65
+ class NToolDialog(NDialog):
47
66
 
48
67
  def __init__(self, parent: GuiMain) -> None:
49
68
  super().__init__(parent=parent)
@@ -62,7 +81,7 @@ class NToolDialog(QDialog):
62
81
  return
63
82
 
64
83
 
65
- class NNonBlockingDialog(QDialog):
84
+ class NNonBlockingDialog(NDialog):
66
85
 
67
86
  def __init__(self, parent: QWidget | None = None) -> None:
68
87
  super().__init__(parent=parent)
@@ -1,9 +1,10 @@
1
1
  """
2
- novelWriter – Custom Widget: Progress Circle
3
- ============================================
2
+ novelWriter – Custom Widget: Progress Bars
3
+ ==========================================
4
4
 
5
5
  File History:
6
- Created: 2023-06-07 [2.1b1]
6
+ Created: 2023-06-07 [2.1b1] NProgressCircle
7
+ Created: 2023-06-09 [2.1b1] NProgressSimple
7
8
 
8
9
  This file is a part of novelWriter
9
10
  Copyright 2018–2024, Veronica Berglyd Olsen
@@ -101,3 +102,25 @@ class NProgressCircle(QProgressBar):
101
102
  painter.setPen(self._tColor)
102
103
  painter.drawText(self._cRect, QtAlignCenter, self._text or f"{progress:.1f} %")
103
104
  return
105
+
106
+
107
+ class NProgressSimple(QProgressBar):
108
+ """Extension: Simple Progress Widget
109
+
110
+ A custom widget that paints a plain bar with no other styling.
111
+ """
112
+
113
+ def __init__(self, parent: QWidget) -> None:
114
+ super().__init__(parent=parent)
115
+ return
116
+
117
+ def paintEvent(self, event: QPaintEvent) -> None:
118
+ """Custom painter for the progress bar."""
119
+ if (value := self.value()) > 0:
120
+ progress = ceil(self.width()*float(value)/self.maximum())
121
+ painter = QPainter(self)
122
+ painter.setRenderHint(QtPaintAnitAlias, True)
123
+ painter.setPen(self.palette().highlight().color())
124
+ painter.setBrush(self.palette().highlight())
125
+ painter.drawRect(0, 0, progress, self.height())
126
+ return
@@ -25,44 +25,50 @@ from __future__ import annotations
25
25
 
26
26
  import logging
27
27
 
28
- from typing import Literal
29
-
30
28
  from PyQt5.QtGui import QColor, QPainter, QPaintEvent
31
29
  from PyQt5.QtWidgets import QAbstractButton, QWidget
32
30
 
33
- from novelwriter.types import QtPaintAnitAlias
31
+ from novelwriter.enum import nwTrinary
32
+ from novelwriter.types import QtBlack, QtPaintAnitAlias
34
33
 
35
34
  logger = logging.getLogger(__name__)
36
35
 
37
36
 
38
37
  class StatusLED(QAbstractButton):
39
38
 
40
- S_NONE = 0
41
- S_BAD = 1
42
- S_GOOD = 2
43
-
44
- def __init__(self, colNone: QColor, colGood: QColor, colBad: QColor,
45
- sW: int, sH: int, parent: QWidget | None = None) -> None:
39
+ def __init__(self, sW: int, sH: int, parent: QWidget | None = None) -> None:
46
40
  super().__init__(parent=parent)
47
-
48
- self._colNone = colNone
49
- self._colGood = colGood
50
- self._colBad = colBad
51
- self._theCol = colNone
52
-
41
+ self._neutral = QtBlack
42
+ self._postitve = QtBlack
43
+ self._negative = QtBlack
44
+ self._color = QtBlack
45
+ self._state = nwTrinary.NEUTRAL
53
46
  self.setFixedWidth(sW)
54
47
  self.setFixedHeight(sH)
48
+ return
55
49
 
50
+ @property
51
+ def state(self) -> nwTrinary:
52
+ """The current state of the LED."""
53
+ return self._state
54
+
55
+ def setColors(self, neutral: QColor, positive: QColor, negative: QColor) -> None:
56
+ """Set the three colours for the status values."""
57
+ self._neutral = neutral
58
+ self._postitve = positive
59
+ self._negative = negative
60
+ self.setState(self._state)
56
61
  return
57
62
 
58
- def setState(self, state: Literal[0, 1, 2]) -> None:
63
+ def setState(self, state: nwTrinary) -> None:
59
64
  """Set the colour state."""
60
- if state == self.S_GOOD:
61
- self._theCol = self._colGood
62
- elif state == self.S_BAD:
63
- self._theCol = self._colBad
65
+ if state == nwTrinary.POSITIVE:
66
+ self._color = self._postitve
67
+ elif state == nwTrinary.NEGATIVE:
68
+ self._color = self._negative
64
69
  else:
65
- self._theCol = self._colNone
70
+ self._color = self._neutral
71
+ self._state = state
66
72
  self.update()
67
73
  return
68
74
 
@@ -71,7 +77,7 @@ class StatusLED(QAbstractButton):
71
77
  painter = QPainter(self)
72
78
  painter.setRenderHint(QtPaintAnitAlias, True)
73
79
  painter.setPen(self.palette().dark().color())
74
- painter.setBrush(self._theCol)
80
+ painter.setBrush(self._color)
75
81
  painter.setOpacity(1.0)
76
82
  painter.drawEllipse(1, 1, self.width() - 2, self.height() - 2)
77
83
  return
@@ -55,6 +55,7 @@ from novelwriter.common import minmax, transferCase
55
55
  from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
56
56
  from novelwriter.core.document import NWDocument
57
57
  from novelwriter.enum import nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary
58
+ from novelwriter.extensions.configlayout import NColourLabel
58
59
  from novelwriter.extensions.eventfilters import WheelEventFilter
59
60
  from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton
60
61
  from novelwriter.gui.dochighlight import BLOCK_META, BLOCK_TITLE
@@ -64,7 +65,7 @@ from novelwriter.text.counting import standardCounter
64
65
  from novelwriter.tools.lipsum import GuiLipsum
65
66
  from novelwriter.types import (
66
67
  QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop,
67
- QtAlignRight, QtKeepAnchor, QtModCtrl, QtModeNone, QtModShift, QtMouseLeft,
68
+ QtAlignRight, QtKeepAnchor, QtModCtrl, QtModNone, QtModShift, QtMouseLeft,
68
69
  QtMoveAnchor, QtMoveLeft, QtMoveRight
69
70
  )
70
71
 
@@ -210,6 +211,7 @@ class GuiDocEditor(QPlainTextEdit):
210
211
  # Function Mapping
211
212
  self.closeSearch = self.docSearch.closeSearch
212
213
  self.searchVisible = self.docSearch.isVisible
214
+ self.changeFocusState = self.docHeader.changeFocusState
213
215
 
214
216
  # Finalise
215
217
  self.updateSyntaxColours()
@@ -436,11 +438,6 @@ class GuiDocEditor(QPlainTextEdit):
436
438
 
437
439
  return True
438
440
 
439
- def updateTagHighLighting(self) -> None:
440
- """Rerun the syntax highlighter on all meta data lines."""
441
- self._qDocument.syntaxHighlighter.rehighlightByType(BLOCK_META)
442
- return
443
-
444
441
  def replaceText(self, text: str) -> None:
445
442
  """Replace the text of the current document with the provided
446
443
  text. This also clears undo history.
@@ -956,7 +953,7 @@ class GuiDocEditor(QPlainTextEdit):
956
953
  super().keyPressEvent(event)
957
954
  nPos = self.cursorRect().topLeft().y()
958
955
  kMod = event.modifiers()
959
- okMod = kMod in (QtModeNone, QtModShift)
956
+ okMod = kMod in (QtModNone, QtModShift)
960
957
  okKey = event.key() not in self.MOVE_KEYS
961
958
  if nPos != cPos and okMod and okKey:
962
959
  mPos = CONFIG.autoScrollPos*0.01 * self.viewport().height()
@@ -1034,6 +1031,13 @@ class GuiDocEditor(QPlainTextEdit):
1034
1031
  self.beginSearch()
1035
1032
  return
1036
1033
 
1034
+ @pyqtSlot(list, list)
1035
+ def updateChangedTags(self, updated: list[str], deleted: list[str]) -> None:
1036
+ """Tags have changed, so just in case we rehighlight them."""
1037
+ if updated or deleted:
1038
+ self._qDocument.syntaxHighlighter.rehighlightByType(BLOCK_META)
1039
+ return
1040
+
1037
1041
  ##
1038
1042
  # Private Slots
1039
1043
  ##
@@ -1922,8 +1926,6 @@ class GuiDocEditor(QPlainTextEdit):
1922
1926
  ).format(tag)):
1923
1927
  itemClass = nwKeyWords.KEY_CLASS.get(tBits[0], nwItemClass.NO_CLASS)
1924
1928
  self.requestNewNoteCreation.emit(tag, itemClass)
1925
- QApplication.processEvents()
1926
- self._qDocument.syntaxHighlighter.rehighlightBlock(block)
1927
1929
 
1928
1930
  return nwTrinary.POSITIVE if exist else nwTrinary.NEGATIVE
1929
1931
 
@@ -2030,6 +2032,9 @@ class GuiDocEditor(QPlainTextEdit):
2030
2032
  cursor.movePosition(QtMoveLeft, QtKeepAnchor, nDelete)
2031
2033
  cursor.insertText(tInsert)
2032
2034
 
2035
+ # Re-highlight, since the auto-replace sometimes interferes with it
2036
+ self._qDocument.syntaxHighlighter.rehighlightBlock(cursor.block())
2037
+
2033
2038
  return
2034
2039
 
2035
2040
  @staticmethod
@@ -2782,8 +2787,7 @@ class GuiDocEditHeader(QWidget):
2782
2787
  self.setAutoFillBackground(True)
2783
2788
 
2784
2789
  # Title Label
2785
- self.itemTitle = QLabel("", self)
2786
- self.itemTitle.setIndent(0)
2790
+ self.itemTitle = NColourLabel("", self, faded=SHARED.theme.fadedText)
2787
2791
  self.itemTitle.setMargin(0)
2788
2792
  self.itemTitle.setContentsMargins(0, 0, 0, 0)
2789
2793
  self.itemTitle.setAutoFillBackground(True)
@@ -2921,6 +2925,11 @@ class GuiDocEditHeader(QWidget):
2921
2925
 
2922
2926
  return
2923
2927
 
2928
+ def changeFocusState(self, state: bool) -> None:
2929
+ """Toggle focus state."""
2930
+ self.itemTitle.setColorState(state)
2931
+ return
2932
+
2924
2933
  def setHandle(self, tHandle: str) -> None:
2925
2934
  """Set the document title from the handle, or alternatively, set
2926
2935
  the whole document path within the project.
@@ -39,6 +39,7 @@ from novelwriter.common import checkInt
39
39
  from novelwriter.constants import nwHeaders, nwRegEx, nwUnicode
40
40
  from novelwriter.core.index import processComment
41
41
  from novelwriter.enum import nwComment
42
+ from novelwriter.text.patterns import REGEX_PATTERNS
42
43
  from novelwriter.types import QRegExUnicode
43
44
 
44
45
  logger = logging.getLogger(__name__)
@@ -59,8 +60,8 @@ BLOCK_TITLE = 4
59
60
  class GuiDocHighlighter(QSyntaxHighlighter):
60
61
 
61
62
  __slots__ = (
62
- "_tHandle", "_isInactive", "_spellCheck", "_spellErr", "_hStyles",
63
- "_txtRules", "_cmnRules",
63
+ "_tHandle", "_isNovel", "_isInactive", "_spellCheck", "_spellErr",
64
+ "_hStyles", "_minRules", "_txtRules", "_cmnRules",
64
65
  )
65
66
 
66
67
  def __init__(self, document: QTextDocument) -> None:
@@ -69,11 +70,13 @@ class GuiDocHighlighter(QSyntaxHighlighter):
69
70
  logger.debug("Create: GuiDocHighlighter")
70
71
 
71
72
  self._tHandle = None
73
+ self._isNovel = False
72
74
  self._isInactive = False
73
75
  self._spellCheck = False
74
76
  self._spellErr = QTextCharFormat()
75
77
 
76
78
  self._hStyles: dict[str, QTextCharFormat] = {}
79
+ self._minRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = []
77
80
  self._txtRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = []
78
81
  self._cmnRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = []
79
82
 
@@ -137,6 +140,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
137
140
  hlRule = {
138
141
  0: self._hStyles["mspaces"],
139
142
  }
143
+ self._minRules.append((rxRule, hlRule))
140
144
  self._txtRules.append((rxRule, hlRule))
141
145
  self._cmnRules.append((rxRule, hlRule))
142
146
 
@@ -146,106 +150,89 @@ class GuiDocHighlighter(QSyntaxHighlighter):
146
150
  hlRule = {
147
151
  0: self._hStyles["nobreak"],
148
152
  }
153
+ self._minRules.append((rxRule, hlRule))
149
154
  self._txtRules.append((rxRule, hlRule))
150
155
  self._cmnRules.append((rxRule, hlRule))
151
156
 
152
157
  # Dialogue
153
158
  if CONFIG.dialogStyle > 0:
154
- symO = ""
155
- symC = ""
156
- if CONFIG.dialogStyle in (1, 3):
157
- symO += CONFIG.fmtSQuoteOpen
158
- symC += CONFIG.fmtSQuoteClose
159
- if CONFIG.dialogStyle in (2, 3):
160
- symO += CONFIG.fmtDQuoteOpen
161
- symC += CONFIG.fmtDQuoteClose
162
-
163
- rxEnd = "|$" if CONFIG.allowOpenDial else ""
164
- rxRule = QRegularExpression(f"\\B[{symO}].*?[{symC}]\\B{rxEnd}")
165
- rxRule.setPatternOptions(QRegExUnicode)
159
+ rxRule = REGEX_PATTERNS.dialogStyle
166
160
  hlRule = {
167
161
  0: self._hStyles["dialog"],
168
162
  }
169
163
  self._txtRules.append((rxRule, hlRule))
170
164
 
171
165
  if CONFIG.dialogLine:
172
- sym = QRegularExpression.escape(CONFIG.dialogLine)
173
- rxRule = QRegularExpression(f"^{sym}.*?$")
174
- rxRule.setPatternOptions(QRegExUnicode)
166
+ rxRule = REGEX_PATTERNS.dialogLine
175
167
  hlRule = {
176
168
  0: self._hStyles["dialog"],
177
169
  }
178
170
  self._txtRules.append((rxRule, hlRule))
179
171
 
180
172
  if CONFIG.narratorBreak:
181
- sym = QRegularExpression.escape(CONFIG.narratorBreak)
182
- rxRule = QRegularExpression(f"({sym}\\b)(.*?)(\\b{sym})")
183
- rxRule.setPatternOptions(QRegExUnicode)
173
+ rxRule = REGEX_PATTERNS.narratorBreak
184
174
  hlRule = {
185
175
  0: self._hStyles["text"],
186
176
  }
187
177
  self._txtRules.append((rxRule, hlRule))
188
178
 
189
179
  if CONFIG.altDialogOpen and CONFIG.altDialogClose:
190
- symO = QRegularExpression.escape(CONFIG.altDialogOpen)
191
- symC = QRegularExpression.escape(CONFIG.altDialogClose)
192
- rxRule = QRegularExpression(f"\\B{symO}.*?{symC}\\B")
193
- rxRule.setPatternOptions(QRegExUnicode)
180
+ rxRule = REGEX_PATTERNS.altDialogStyle
194
181
  hlRule = {
195
182
  0: self._hStyles["altdialog"],
196
183
  }
197
184
  self._txtRules.append((rxRule, hlRule))
198
185
 
199
186
  # Markdown Italic
200
- rxRule = QRegularExpression(nwRegEx.FMT_EI)
201
- rxRule.setPatternOptions(QRegExUnicode)
187
+ rxRule = REGEX_PATTERNS.markdownItalic
202
188
  hlRule = {
203
189
  1: self._hStyles["markup"],
204
190
  2: self._hStyles["italic"],
205
191
  3: self._hStyles["markup"],
206
192
  }
193
+ self._minRules.append((rxRule, hlRule))
207
194
  self._txtRules.append((rxRule, hlRule))
208
195
  self._cmnRules.append((rxRule, hlRule))
209
196
 
210
197
  # Markdown Bold
211
- rxRule = QRegularExpression(nwRegEx.FMT_EB)
212
- rxRule.setPatternOptions(QRegExUnicode)
198
+ rxRule = REGEX_PATTERNS.markdownBold
213
199
  hlRule = {
214
200
  1: self._hStyles["markup"],
215
201
  2: self._hStyles["bold"],
216
202
  3: self._hStyles["markup"],
217
203
  }
204
+ self._minRules.append((rxRule, hlRule))
218
205
  self._txtRules.append((rxRule, hlRule))
219
206
  self._cmnRules.append((rxRule, hlRule))
220
207
 
221
208
  # Markdown Strikethrough
222
- rxRule = QRegularExpression(nwRegEx.FMT_ST)
223
- rxRule.setPatternOptions(QRegExUnicode)
209
+ rxRule = REGEX_PATTERNS.markdownStrike
224
210
  hlRule = {
225
211
  1: self._hStyles["markup"],
226
212
  2: self._hStyles["strike"],
227
213
  3: self._hStyles["markup"],
228
214
  }
215
+ self._minRules.append((rxRule, hlRule))
229
216
  self._txtRules.append((rxRule, hlRule))
230
217
  self._cmnRules.append((rxRule, hlRule))
231
218
 
232
219
  # Shortcodes
233
- rxRule = QRegularExpression(nwRegEx.FMT_SC)
234
- rxRule.setPatternOptions(QRegExUnicode)
220
+ rxRule = REGEX_PATTERNS.shortcodePlain
235
221
  hlRule = {
236
222
  1: self._hStyles["code"],
237
223
  }
224
+ self._minRules.append((rxRule, hlRule))
238
225
  self._txtRules.append((rxRule, hlRule))
239
226
  self._cmnRules.append((rxRule, hlRule))
240
227
 
241
228
  # Shortcodes w/Value
242
- rxRule = QRegularExpression(nwRegEx.FMT_SV)
243
- rxRule.setPatternOptions(QRegExUnicode)
229
+ rxRule = REGEX_PATTERNS.shortcodeValue
244
230
  hlRule = {
245
231
  1: self._hStyles["code"],
246
232
  2: self._hStyles["value"],
247
233
  3: self._hStyles["code"],
248
234
  }
235
+ self._minRules.append((rxRule, hlRule))
249
236
  self._txtRules.append((rxRule, hlRule))
250
237
  self._cmnRules.append((rxRule, hlRule))
251
238
 
@@ -255,6 +242,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
255
242
  hlRule = {
256
243
  1: self._hStyles["markup"],
257
244
  }
245
+ self._minRules.append((rxRule, hlRule))
258
246
  self._txtRules.append((rxRule, hlRule))
259
247
 
260
248
  # Auto-Replace Tags
@@ -263,6 +251,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
263
251
  hlRule = {
264
252
  0: self._hStyles["replace"],
265
253
  }
254
+ self._minRules.append((rxRule, hlRule))
266
255
  self._txtRules.append((rxRule, hlRule))
267
256
  self._cmnRules.append((rxRule, hlRule))
268
257
 
@@ -280,9 +269,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
280
269
  def setHandle(self, tHandle: str) -> None:
281
270
  """Set the handle of the currently highlighted document."""
282
271
  self._tHandle = tHandle
283
- self._isInactive = (
284
- item.isInactiveClass() if (item := SHARED.project.tree[tHandle]) else False
285
- )
272
+ self._isNovel = False
273
+ self._isInactive = False
274
+ if item := SHARED.project.tree[tHandle]:
275
+ self._isNovel = item.isDocumentLayout()
276
+ self._isInactive = item.isInactiveClass()
286
277
  logger.debug("Syntax highlighter enabled for item '%s'", tHandle)
287
278
  return
288
279
 
@@ -397,7 +388,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
397
388
 
398
389
  elif text.startswith("["): # Special Command
399
390
  self.setCurrentBlockState(BLOCK_TEXT)
400
- hRules = self._txtRules
391
+ hRules = self._txtRules if self._isNovel else self._minRules
401
392
 
402
393
  sText = text.rstrip().lower()
403
394
  if sText in ("[newpage]", "[new page]", "[vspace]"):
@@ -414,7 +405,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
414
405
 
415
406
  else: # Text Paragraph
416
407
  self.setCurrentBlockState(BLOCK_TEXT)
417
- hRules = self._txtRules
408
+ hRules = self._txtRules if self._isNovel else self._minRules
418
409
 
419
410
  if hRules:
420
411
  for rX, hRule in hRules: