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.
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/METADATA +1 -1
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/RECORD +80 -60
- novelwriter/__init__.py +49 -10
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
- novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
- novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
- novelwriter/assets/icons/typicons_light/icons.conf +8 -0
- novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
- novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/common.py +100 -2
- novelwriter/config.py +25 -15
- novelwriter/constants.py +168 -60
- novelwriter/core/buildsettings.py +66 -39
- novelwriter/core/coretools.py +145 -147
- novelwriter/core/docbuild.py +132 -170
- novelwriter/core/index.py +38 -37
- novelwriter/core/item.py +41 -8
- novelwriter/core/itemmodel.py +518 -0
- novelwriter/core/options.py +4 -1
- novelwriter/core/project.py +67 -89
- novelwriter/core/spellcheck.py +9 -14
- novelwriter/core/status.py +7 -5
- novelwriter/core/tree.py +268 -287
- novelwriter/dialogs/docmerge.py +7 -17
- novelwriter/dialogs/preferences.py +46 -33
- novelwriter/dialogs/projectsettings.py +5 -5
- novelwriter/enum.py +36 -23
- novelwriter/extensions/configlayout.py +27 -12
- novelwriter/extensions/modified.py +13 -1
- novelwriter/extensions/pagedsidebar.py +5 -5
- novelwriter/formats/shared.py +155 -0
- novelwriter/formats/todocx.py +1191 -0
- novelwriter/formats/tohtml.py +451 -0
- novelwriter/{core → formats}/tokenizer.py +487 -491
- novelwriter/formats/tomarkdown.py +217 -0
- novelwriter/{core → formats}/toodt.py +311 -432
- novelwriter/formats/toqdoc.py +484 -0
- novelwriter/formats/toraw.py +91 -0
- novelwriter/gui/doceditor.py +342 -284
- novelwriter/gui/dochighlight.py +96 -84
- novelwriter/gui/docviewer.py +88 -31
- novelwriter/gui/docviewerpanel.py +17 -25
- novelwriter/gui/editordocument.py +17 -2
- novelwriter/gui/itemdetails.py +25 -28
- novelwriter/gui/mainmenu.py +129 -63
- novelwriter/gui/noveltree.py +45 -47
- novelwriter/gui/outline.py +196 -249
- novelwriter/gui/projtree.py +594 -1241
- novelwriter/gui/search.py +9 -10
- novelwriter/gui/sidebar.py +7 -6
- novelwriter/gui/theme.py +10 -5
- novelwriter/guimain.py +100 -196
- novelwriter/shared.py +66 -27
- novelwriter/text/counting.py +2 -0
- novelwriter/text/patterns.py +168 -60
- novelwriter/tools/manusbuild.py +14 -12
- novelwriter/tools/manuscript.py +120 -78
- novelwriter/tools/manussettings.py +424 -291
- novelwriter/tools/welcome.py +4 -4
- novelwriter/tools/writingstats.py +3 -3
- novelwriter/types.py +23 -7
- novelwriter/core/tohtml.py +0 -530
- novelwriter/core/tomarkdown.py +0 -252
- novelwriter/core/toqdoc.py +0 -419
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/WHEEL +0 -0
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/top_level.txt +0 -0
novelwriter/dialogs/docmerge.py
CHANGED
@@ -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
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
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 =
|
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(
|
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
|
570
|
-
self.tr("Lines starting with
|
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.
|
574
|
-
self.
|
575
|
-
self.
|
576
|
-
self.
|
577
|
-
self.
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
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("
|
587
|
-
self.tr("
|
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
|
-
|
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.
|
188
|
+
project.updateStatus("s", self.statusPage.getNewList())
|
189
189
|
|
190
190
|
if self.importPage.changed:
|
191
191
|
logger.debug("Updating importance labels")
|
192
|
-
project.
|
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(
|
368
|
+
self.upButton.clicked.connect(qtLambda(self._moveItem, -1))
|
369
369
|
|
370
370
|
self.dnButton = NIconToolButton(self, iSz, "down")
|
371
|
-
self.dnButton.clicked.connect(
|
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
|
161
|
-
LEVEL
|
162
|
-
LABEL
|
163
|
-
LINE
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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.
|
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(
|
80
|
-
self.setVerticalScrollBarPolicy(
|
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(
|
117
|
-
self.setVerticalScrollBarPolicy(
|
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(
|
184
|
-
|
185
|
-
|
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
|
-
|
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
|
-
|
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__(
|
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,
|
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) ->
|
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 =
|
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
|
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]
|