novelWriter 2.6rc1__py3-none-any.whl → 2.6.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.
- {novelWriter-2.6rc1.dist-info → novelWriter-2.6.1.dist-info}/METADATA +1 -1
- {novelWriter-2.6rc1.dist-info → novelWriter-2.6.1.dist-info}/RECORD +57 -56
- novelwriter/__init__.py +3 -3
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_ru_RU.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_de_DE.json +2 -0
- novelwriter/assets/i18n/project_en_US.json +2 -0
- novelwriter/assets/i18n/project_es_419.json +2 -0
- novelwriter/assets/i18n/project_fr_FR.json +3 -1
- novelwriter/assets/i18n/project_it_IT.json +2 -0
- novelwriter/assets/i18n/project_ja_JP.json +2 -0
- novelwriter/assets/i18n/project_nb_NO.json +2 -0
- novelwriter/assets/i18n/project_nl_NL.json +2 -0
- novelwriter/assets/i18n/project_pl_PL.json +2 -0
- novelwriter/assets/i18n/project_pt_BR.json +2 -0
- novelwriter/assets/i18n/project_zh_CN.json +2 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/manual_fr_FR.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/config.py +41 -19
- novelwriter/constants.py +4 -0
- novelwriter/core/buildsettings.py +7 -0
- novelwriter/core/project.py +2 -6
- novelwriter/dialogs/preferences.py +14 -5
- novelwriter/enum.py +0 -7
- novelwriter/extensions/novelselector.py +3 -2
- novelwriter/extensions/statusled.py +5 -6
- novelwriter/formats/shared.py +12 -11
- novelwriter/formats/todocx.py +1 -1
- novelwriter/formats/tohtml.py +30 -27
- novelwriter/formats/tokenizer.py +14 -8
- novelwriter/formats/tomarkdown.py +3 -2
- novelwriter/formats/toodt.py +1 -1
- novelwriter/formats/toqdoc.py +2 -0
- novelwriter/gui/doceditor.py +35 -19
- novelwriter/gui/docviewer.py +1 -1
- novelwriter/gui/itemdetails.py +1 -0
- novelwriter/gui/projtree.py +15 -18
- novelwriter/gui/statusbar.py +6 -7
- novelwriter/guimain.py +1 -1
- novelwriter/tools/dictionaries.py +2 -2
- novelwriter/tools/manussettings.py +8 -8
- novelwriter/tools/welcome.py +1 -1
- {novelWriter-2.6rc1.dist-info → novelWriter-2.6.1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.6rc1.dist-info → novelWriter-2.6.1.dist-info}/WHEEL +0 -0
- {novelWriter-2.6rc1.dist-info → novelWriter-2.6.1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.6rc1.dist-info → novelWriter-2.6.1.dist-info}/top_level.txt +0 -0
@@ -271,10 +271,10 @@ class GuiPreferences(NDialog):
|
|
271
271
|
self.tr("Include project notes in status bar word count"), self.incNotesWCount
|
272
272
|
)
|
273
273
|
|
274
|
-
#
|
274
|
+
# Behaviour
|
275
275
|
# =========
|
276
276
|
|
277
|
-
title = self.tr("
|
277
|
+
title = self.tr("Behaviour")
|
278
278
|
section += 1
|
279
279
|
self.sidebar.addButton(title, section)
|
280
280
|
self.mainForm.addGroupLabel(title, section)
|
@@ -301,6 +301,14 @@ class GuiPreferences(NDialog):
|
|
301
301
|
self.tr("How often the project is automatically saved."), unit=self.tr("seconds")
|
302
302
|
)
|
303
303
|
|
304
|
+
# Ask before exiting novelWriter
|
305
|
+
self.askBeforeExit = NSwitch(self)
|
306
|
+
self.askBeforeExit.setChecked(CONFIG.askBeforeExit)
|
307
|
+
self.mainForm.addRow(
|
308
|
+
self.tr("Ask before exiting novelWriter"), self.askBeforeExit,
|
309
|
+
self.tr("Only applies when a project is open.")
|
310
|
+
)
|
311
|
+
|
304
312
|
# Project Backup
|
305
313
|
# ==============
|
306
314
|
|
@@ -927,9 +935,10 @@ class GuiPreferences(NDialog):
|
|
927
935
|
CONFIG.incNotesWCount = self.incNotesWCount.isChecked()
|
928
936
|
CONFIG.setTextFont(self._textFont)
|
929
937
|
|
930
|
-
#
|
931
|
-
CONFIG.autoSaveDoc
|
932
|
-
CONFIG.autoSaveProj
|
938
|
+
# Behaviour
|
939
|
+
CONFIG.autoSaveDoc = self.autoSaveDoc.value()
|
940
|
+
CONFIG.autoSaveProj = self.autoSaveProj.value()
|
941
|
+
CONFIG.askBeforeExit = self.askBeforeExit.isChecked()
|
933
942
|
|
934
943
|
# Project Backup
|
935
944
|
CONFIG.setBackupPath(self.backupPath)
|
novelwriter/enum.py
CHANGED
@@ -90,12 +90,13 @@ class NovelSelector(QComboBox):
|
|
90
90
|
@pyqtSlot()
|
91
91
|
def refreshNovelList(self) -> None:
|
92
92
|
"""Rebuild the list of novel items."""
|
93
|
+
cHandle = self.currentData()
|
94
|
+
|
93
95
|
self._blockSignal = True
|
94
96
|
self._firstHandle = None
|
95
97
|
self.clear()
|
96
98
|
|
97
99
|
icon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])
|
98
|
-
handle = self.currentData()
|
99
100
|
for tHandle, nwItem in SHARED.project.tree.iterRoots(nwItemClass.NOVEL):
|
100
101
|
if self._listFormat:
|
101
102
|
name = self._listFormat.format(nwItem.itemName)
|
@@ -110,7 +111,7 @@ class NovelSelector(QComboBox):
|
|
110
111
|
self.insertSeparator(self.count())
|
111
112
|
self.addItem(icon, self.tr("All Novel Folders"), "")
|
112
113
|
|
113
|
-
self.setHandle(
|
114
|
+
self.setHandle(cHandle)
|
114
115
|
self.setEnabled(self.count() > 1)
|
115
116
|
self._blockSignal = False
|
116
117
|
|
@@ -29,7 +29,6 @@ from PyQt5.QtGui import QColor, QPainter, QPaintEvent
|
|
29
29
|
from PyQt5.QtWidgets import QAbstractButton, QWidget
|
30
30
|
|
31
31
|
from novelwriter import CONFIG
|
32
|
-
from novelwriter.enum import nwTrinary
|
33
32
|
from novelwriter.types import QtBlack, QtPaintAntiAlias
|
34
33
|
|
35
34
|
logger = logging.getLogger(__name__)
|
@@ -47,14 +46,14 @@ class StatusLED(QAbstractButton):
|
|
47
46
|
self._postitve = QtBlack
|
48
47
|
self._negative = QtBlack
|
49
48
|
self._color = QtBlack
|
50
|
-
self._state =
|
49
|
+
self._state = None
|
51
50
|
self._bPx = CONFIG.pxInt(1)
|
52
51
|
self.setFixedWidth(sW)
|
53
52
|
self.setFixedHeight(sH)
|
54
53
|
return
|
55
54
|
|
56
55
|
@property
|
57
|
-
def state(self) ->
|
56
|
+
def state(self) -> bool | None:
|
58
57
|
"""The current state of the LED."""
|
59
58
|
return self._state
|
60
59
|
|
@@ -66,11 +65,11 @@ class StatusLED(QAbstractButton):
|
|
66
65
|
self.setState(self._state)
|
67
66
|
return
|
68
67
|
|
69
|
-
def setState(self, state:
|
68
|
+
def setState(self, state: bool | None) -> None:
|
70
69
|
"""Set the colour state."""
|
71
|
-
if state
|
70
|
+
if state is True:
|
72
71
|
self._color = self._postitve
|
73
|
-
elif state
|
72
|
+
elif state is False:
|
74
73
|
self._color = self._negative
|
75
74
|
else:
|
76
75
|
self._color = self._neutral
|
novelwriter/formats/shared.py
CHANGED
@@ -104,17 +104,18 @@ class BlockTyp(IntEnum):
|
|
104
104
|
|
105
105
|
EMPTY = 1 # Empty line (new paragraph)
|
106
106
|
TITLE = 2 # Title
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
107
|
+
PART = 3 # Partition
|
108
|
+
HEAD1 = 4 # Heading 1 or Chapter
|
109
|
+
HEAD2 = 5 # Heading 2 or Scene
|
110
|
+
HEAD3 = 6 # Heading 3 or Section
|
111
|
+
HEAD4 = 7 # Heading 4
|
112
|
+
TEXT = 8 # Text line
|
113
|
+
SEP = 9 # Scene separator
|
114
|
+
SKIP = 10 # Paragraph break
|
115
|
+
SUMMARY = 11 # Synopsis/short comment
|
116
|
+
NOTE = 12 # Note
|
117
|
+
COMMENT = 13 # Comment
|
118
|
+
KEYWORD = 14 # Tag/reference keywords
|
118
119
|
|
119
120
|
|
120
121
|
class BlockFmt(Flag):
|
novelwriter/formats/todocx.py
CHANGED
@@ -271,7 +271,7 @@ class ToDocX(Tokenizer):
|
|
271
271
|
if tType == BlockTyp.TEXT:
|
272
272
|
self._processFragments(par, S_NORM, tText, tFormat)
|
273
273
|
|
274
|
-
elif tType
|
274
|
+
elif tType in (BlockTyp.TITLE, BlockTyp.PART):
|
275
275
|
self._processFragments(par, S_TITLE, tText, tFormat)
|
276
276
|
|
277
277
|
elif tType == BlockTyp.HEAD1:
|
novelwriter/formats/tohtml.py
CHANGED
@@ -30,7 +30,7 @@ from pathlib import Path
|
|
30
30
|
from time import time
|
31
31
|
|
32
32
|
from novelwriter.common import formatTimeStamp
|
33
|
-
from novelwriter.constants import nwHtmlUnicode
|
33
|
+
from novelwriter.constants import nwHtmlUnicode, nwStyles
|
34
34
|
from novelwriter.core.project import NWProject
|
35
35
|
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
|
36
36
|
from novelwriter.formats.tokenizer import Tokenizer
|
@@ -130,20 +130,6 @@ class ToHtml(Tokenizer):
|
|
130
130
|
|
131
131
|
def doConvert(self) -> None:
|
132
132
|
"""Convert the list of text tokens into an HTML document."""
|
133
|
-
if self._isNovel:
|
134
|
-
# For story files, we bump the titles one level up
|
135
|
-
h1Cl = " class='title'"
|
136
|
-
h1 = "h1"
|
137
|
-
h2 = "h1"
|
138
|
-
h3 = "h2"
|
139
|
-
h4 = "h3"
|
140
|
-
else:
|
141
|
-
h1Cl = ""
|
142
|
-
h1 = "h1"
|
143
|
-
h2 = "h2"
|
144
|
-
h3 = "h3"
|
145
|
-
h4 = "h4"
|
146
|
-
|
147
133
|
lines = []
|
148
134
|
for tType, tMeta, tText, tFmt, tStyle in self._blocks:
|
149
135
|
|
@@ -213,25 +199,25 @@ class ToHtml(Tokenizer):
|
|
213
199
|
if tType == BlockTyp.TEXT:
|
214
200
|
lines.append(f"<p{hStyle}>{self._formatText(tText, tFmt)}</p>\n")
|
215
201
|
|
216
|
-
elif tType
|
202
|
+
elif tType in (BlockTyp.TITLE, BlockTyp.PART):
|
217
203
|
tHead = tText.replace("\n", "<br>")
|
218
204
|
lines.append(f"<h1 class='title'{hStyle}>{aNm}{tHead}</h1>\n")
|
219
205
|
|
220
206
|
elif tType == BlockTyp.HEAD1:
|
221
207
|
tHead = tText.replace("\n", "<br>")
|
222
|
-
lines.append(f"<
|
208
|
+
lines.append(f"<h1{hStyle}>{aNm}{tHead}</h1>\n")
|
223
209
|
|
224
210
|
elif tType == BlockTyp.HEAD2:
|
225
211
|
tHead = tText.replace("\n", "<br>")
|
226
|
-
lines.append(f"<
|
212
|
+
lines.append(f"<h2{hStyle}>{aNm}{tHead}</h2>\n")
|
227
213
|
|
228
214
|
elif tType == BlockTyp.HEAD3:
|
229
215
|
tHead = tText.replace("\n", "<br>")
|
230
|
-
lines.append(f"<
|
216
|
+
lines.append(f"<h3{hStyle}>{aNm}{tHead}</h3>\n")
|
231
217
|
|
232
218
|
elif tType == BlockTyp.HEAD4:
|
233
219
|
tHead = tText.replace("\n", "<br>")
|
234
|
-
lines.append(f"<
|
220
|
+
lines.append(f"<h4{hStyle}>{aNm}{tHead}</h4>\n")
|
235
221
|
|
236
222
|
elif tType == BlockTyp.SEP:
|
237
223
|
lines.append(f"<p class='sep'{hStyle}>{tText}</p>\n")
|
@@ -310,9 +296,7 @@ class ToHtml(Tokenizer):
|
|
310
296
|
"</style>\n"
|
311
297
|
"</head>\n"
|
312
298
|
"<body>\n"
|
313
|
-
"<article>\n"
|
314
299
|
"{body:s}\n"
|
315
|
-
"</article>\n"
|
316
300
|
"</body>\n"
|
317
301
|
"</html>\n"
|
318
302
|
).format(
|
@@ -359,6 +343,12 @@ class ToHtml(Tokenizer):
|
|
359
343
|
mtSP = self._marginSep[0]
|
360
344
|
mbSP = self._marginSep[1]
|
361
345
|
|
346
|
+
fSz0 = nwStyles.H_SIZES[0]
|
347
|
+
fSz1 = nwStyles.H_SIZES[1]
|
348
|
+
fSz2 = nwStyles.H_SIZES[2]
|
349
|
+
fSz3 = nwStyles.H_SIZES[3]
|
350
|
+
fSz4 = nwStyles.H_SIZES[4]
|
351
|
+
|
362
352
|
font = self._textFont
|
363
353
|
fFam = font.family()
|
364
354
|
fSz = font.pointSize()
|
@@ -379,12 +369,25 @@ class ToHtml(Tokenizer):
|
|
379
369
|
styles.append(f"a {{color: {lColor};}}")
|
380
370
|
styles.append(f"mark {{background: {mColor};}}")
|
381
371
|
styles.append(f"h1, h2, h3, h4 {{color: {hColor}; page-break-after: avoid;}}")
|
382
|
-
styles.append(f"h1 {{margin-top: {mtH1:.2f}em; margin-bottom: {mbH1:.2f}em;}}")
|
383
|
-
styles.append(f"h2 {{margin-top: {mtH2:.2f}em; margin-bottom: {mbH2:.2f}em;}}")
|
384
|
-
styles.append(f"h3 {{margin-top: {mtH3:.2f}em; margin-bottom: {mbH3:.2f}em;}}")
|
385
|
-
styles.append(f"h4 {{margin-top: {mtH4:.2f}em; margin-bottom: {mbH4:.2f}em;}}")
|
386
372
|
styles.append(
|
387
|
-
f"
|
373
|
+
f"h1 {{font-size: {fSz1:.2f}em; "
|
374
|
+
f"margin-top: {mtH1:.2f}em; margin-bottom: {mbH1:.2f}em;}}"
|
375
|
+
)
|
376
|
+
styles.append(
|
377
|
+
f"h2 {{font-size: {fSz2:.2f}em; "
|
378
|
+
f"margin-top: {mtH2:.2f}em; margin-bottom: {mbH2:.2f}em;}}"
|
379
|
+
)
|
380
|
+
styles.append(
|
381
|
+
f"h3 {{font-size: {fSz3:.2f}em; "
|
382
|
+
f"margin-top: {mtH3:.2f}em; margin-bottom: {mbH3:.2f}em;}}"
|
383
|
+
)
|
384
|
+
styles.append(
|
385
|
+
f"h4 {{font-size: {fSz4:.2f}em; "
|
386
|
+
f"margin-top: {mtH4:.2f}em; margin-bottom: {mbH4:.2f}em;}}"
|
387
|
+
)
|
388
|
+
styles.append(
|
389
|
+
f".title {{font-size: {fSz0:.2f}em; "
|
390
|
+
f"margin-top: {mtH0:.2f}em; margin-bottom: {mbH0:.2f}em;}}"
|
388
391
|
)
|
389
392
|
styles.append(
|
390
393
|
f".sep {{text-align: center; margin-top: {mtSP:.2f}em; margin-bottom: {mbSP:.2f}em;}}"
|
novelwriter/formats/tokenizer.py
CHANGED
@@ -68,11 +68,11 @@ COMMENT_STYLE = {
|
|
68
68
|
nwComment.COMMENT: ComStyle(),
|
69
69
|
nwComment.STORY: ComStyle("", "modifier", "note"),
|
70
70
|
}
|
71
|
-
HEADINGS = [
|
72
|
-
|
73
|
-
BlockTyp.
|
74
|
-
BlockTyp.HEAD4, BlockTyp.SEP, BlockTyp.SKIP,
|
71
|
+
HEADINGS = [
|
72
|
+
BlockTyp.TITLE, BlockTyp.PART, BlockTyp.HEAD1,
|
73
|
+
BlockTyp.HEAD2, BlockTyp.HEAD3, BlockTyp.HEAD4,
|
75
74
|
]
|
75
|
+
SKIP_INDENT = HEADINGS + [BlockTyp.SEP, BlockTyp.SKIP]
|
76
76
|
B_EMPTY: T_Block = (BlockTyp.EMPTY, "", "", [], BlockFmt.NONE)
|
77
77
|
|
78
78
|
|
@@ -654,6 +654,7 @@ class Tokenizer(ABC):
|
|
654
654
|
if not (isPlain or isNovel and sHide):
|
655
655
|
tStyle |= self._titleStyle
|
656
656
|
if isNovel:
|
657
|
+
tType = BlockTyp.PART if isPlain else BlockTyp.TITLE
|
657
658
|
if sHide:
|
658
659
|
tText = ""
|
659
660
|
tType = BlockTyp.EMPTY
|
@@ -687,6 +688,7 @@ class Tokenizer(ABC):
|
|
687
688
|
sHide = self._hideChapter if isPlain else self._hideUnNum
|
688
689
|
tFormat = self._fmtChapter if isPlain else self._fmtUnNum
|
689
690
|
if isNovel:
|
691
|
+
tType = BlockTyp.HEAD1 # Promote
|
690
692
|
if isPlain:
|
691
693
|
self._hFormatter.incChapter()
|
692
694
|
if sHide:
|
@@ -721,6 +723,7 @@ class Tokenizer(ABC):
|
|
721
723
|
sHide = self._hideScene if isPlain else self._hideHScene
|
722
724
|
tFormat = self._fmtScene if isPlain else self._fmtHScene
|
723
725
|
if isNovel:
|
726
|
+
tType = BlockTyp.HEAD2 # Promote
|
724
727
|
self._hFormatter.incScene()
|
725
728
|
if sHide:
|
726
729
|
tText = ""
|
@@ -752,6 +755,7 @@ class Tokenizer(ABC):
|
|
752
755
|
tText = aLine[5:].strip()
|
753
756
|
tType = BlockTyp.HEAD4
|
754
757
|
if isNovel:
|
758
|
+
tType = BlockTyp.HEAD3 # Promote
|
755
759
|
if self._hideSection:
|
756
760
|
tText = ""
|
757
761
|
tType = BlockTyp.EMPTY
|
@@ -927,12 +931,14 @@ class Tokenizer(ABC):
|
|
927
931
|
for tType, tKey, tText, _, _ in self._blocks:
|
928
932
|
if tType == BlockTyp.TITLE:
|
929
933
|
prefix = "TT"
|
934
|
+
elif tType == BlockTyp.PART:
|
935
|
+
prefix = "PT"
|
930
936
|
elif tType == BlockTyp.HEAD1:
|
931
|
-
prefix = "
|
937
|
+
prefix = "CH" if isNovel else "H1"
|
932
938
|
elif tType == BlockTyp.HEAD2:
|
933
|
-
prefix = "
|
934
|
-
elif tType == BlockTyp.HEAD3:
|
935
|
-
prefix = "
|
939
|
+
prefix = "SC" if isNovel else "H2"
|
940
|
+
elif tType == BlockTyp.HEAD3 and not isNovel:
|
941
|
+
prefix = "H3"
|
936
942
|
else:
|
937
943
|
continue
|
938
944
|
|
@@ -113,9 +113,10 @@ class ToMarkdown(Tokenizer):
|
|
113
113
|
tTemp = self._formatText(tText, tFormat, mTags).replace("\n", " \n")
|
114
114
|
lines.append(f"{tTemp}\n\n")
|
115
115
|
|
116
|
-
elif tType
|
116
|
+
elif tType in (BlockTyp.TITLE, BlockTyp.PART):
|
117
117
|
tHead = tText.replace("\n", " - ")
|
118
|
-
lines.append(f"
|
118
|
+
lines.append(f"{tHead}\n")
|
119
|
+
lines.append("="*len(tHead) + "\n\n")
|
119
120
|
|
120
121
|
elif tType == BlockTyp.HEAD1:
|
121
122
|
tHead = tText.replace("\n", " - ")
|
novelwriter/formats/toodt.py
CHANGED
@@ -363,7 +363,7 @@ class ToOdt(Tokenizer):
|
|
363
363
|
else:
|
364
364
|
self._addTextPar(xText, S_TEXT, oStyle, tText, tFmt=tFormat)
|
365
365
|
|
366
|
-
elif tType
|
366
|
+
elif tType in (BlockTyp.TITLE, BlockTyp.PART):
|
367
367
|
# Title must be text:p
|
368
368
|
self._addTextPar(xText, S_TITLE, oStyle, tText, isHead=False)
|
369
369
|
|
novelwriter/formats/toqdoc.py
CHANGED
@@ -157,6 +157,7 @@ class ToQTextDocument(Tokenizer):
|
|
157
157
|
|
158
158
|
self._mHead = {
|
159
159
|
BlockTyp.TITLE: (fPx * self._marginTitle[0], fPx * self._marginTitle[1]),
|
160
|
+
BlockTyp.PART: (fPx * self._marginTitle[0], fPx * self._marginTitle[1]),
|
160
161
|
BlockTyp.HEAD1: (fPx * self._marginHead1[0], fPx * self._marginHead1[1]),
|
161
162
|
BlockTyp.HEAD2: (fPx * self._marginHead2[0], fPx * self._marginHead2[1]),
|
162
163
|
BlockTyp.HEAD3: (fPx * self._marginHead3[0], fPx * self._marginHead3[1]),
|
@@ -166,6 +167,7 @@ class ToQTextDocument(Tokenizer):
|
|
166
167
|
hScale = self._scaleHeads
|
167
168
|
self._sHead = {
|
168
169
|
BlockTyp.TITLE: (nwStyles.H_SIZES.get(0, 1.0) * fPt) if hScale else fPt,
|
170
|
+
BlockTyp.PART: (nwStyles.H_SIZES.get(0, 1.0) * fPt) if hScale else fPt,
|
169
171
|
BlockTyp.HEAD1: (nwStyles.H_SIZES.get(1, 1.0) * fPt) if hScale else fPt,
|
170
172
|
BlockTyp.HEAD2: (nwStyles.H_SIZES.get(2, 1.0) * fPt) if hScale else fPt,
|
171
173
|
BlockTyp.HEAD3: (nwStyles.H_SIZES.get(3, 1.0) * fPt) if hScale else fPt,
|
novelwriter/gui/doceditor.py
CHANGED
@@ -34,7 +34,7 @@ from __future__ import annotations
|
|
34
34
|
import bisect
|
35
35
|
import logging
|
36
36
|
|
37
|
-
from enum import Enum
|
37
|
+
from enum import Enum, IntFlag
|
38
38
|
from time import time
|
39
39
|
|
40
40
|
from PyQt5.QtCore import (
|
@@ -57,7 +57,7 @@ from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
|
|
57
57
|
from novelwriter.core.document import NWDocument
|
58
58
|
from novelwriter.enum import (
|
59
59
|
nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass,
|
60
|
-
nwItemType
|
60
|
+
nwItemType
|
61
61
|
)
|
62
62
|
from novelwriter.extensions.configlayout import NColourLabel
|
63
63
|
from novelwriter.extensions.eventfilters import WheelEventFilter
|
@@ -84,6 +84,13 @@ class _SelectAction(Enum):
|
|
84
84
|
MOVE_AFTER = 3
|
85
85
|
|
86
86
|
|
87
|
+
class _TagAction(IntFlag):
|
88
|
+
|
89
|
+
NONE = 0b00
|
90
|
+
FOLLOW = 0b01
|
91
|
+
CREATE = 0b10
|
92
|
+
|
93
|
+
|
87
94
|
class GuiDocEditor(QPlainTextEdit):
|
88
95
|
"""Gui Widget: Main Document Editor"""
|
89
96
|
|
@@ -1104,10 +1111,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1104
1111
|
else:
|
1105
1112
|
self._completer.setVisible(False)
|
1106
1113
|
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1114
|
+
if self._doReplace and added == 1:
|
1115
|
+
cursor = self.textCursor()
|
1116
|
+
if self._autoReplace.process(text, cursor):
|
1117
|
+
self._qDocument.syntaxHighlighter.rehighlightBlock(cursor.block())
|
1111
1118
|
|
1112
1119
|
return
|
1113
1120
|
|
@@ -1158,11 +1165,11 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1158
1165
|
|
1159
1166
|
# Follow
|
1160
1167
|
status = self._processTag(cursor=pCursor, follow=False)
|
1161
|
-
if status
|
1168
|
+
if status & _TagAction.FOLLOW:
|
1162
1169
|
action = ctxMenu.addAction(self.tr("Follow Tag"))
|
1163
1170
|
action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, follow=True))
|
1164
1171
|
ctxMenu.addSeparator()
|
1165
|
-
elif status
|
1172
|
+
elif status & _TagAction.CREATE:
|
1166
1173
|
action = ctxMenu.addAction(self.tr("Create Note for Tag"))
|
1167
1174
|
action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, create=True))
|
1168
1175
|
ctxMenu.addSeparator()
|
@@ -1217,7 +1224,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1217
1224
|
|
1218
1225
|
# Execute the context menu
|
1219
1226
|
ctxMenu.exec(self.viewport().mapToGlobal(pos))
|
1220
|
-
ctxMenu.
|
1227
|
+
ctxMenu.setParent(None)
|
1221
1228
|
|
1222
1229
|
return
|
1223
1230
|
|
@@ -1925,8 +1932,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1925
1932
|
self._qDocument.syntaxHighlighter.rehighlightBlock(block)
|
1926
1933
|
return
|
1927
1934
|
|
1928
|
-
def _processTag(
|
1929
|
-
|
1935
|
+
def _processTag(
|
1936
|
+
self, cursor: QTextCursor | None = None, follow: bool = True, create: bool = False
|
1937
|
+
) -> _TagAction:
|
1930
1938
|
"""Activated by Ctrl+Enter. Checks that we're in a block
|
1931
1939
|
starting with '@'. We then find the tag under the cursor and
|
1932
1940
|
check that it is not the tag itself. If all this is fine, we
|
@@ -1936,19 +1944,22 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1936
1944
|
if cursor is None:
|
1937
1945
|
cursor = self.textCursor()
|
1938
1946
|
|
1947
|
+
status = _TagAction.NONE
|
1939
1948
|
block = cursor.block()
|
1940
1949
|
text = block.text()
|
1941
1950
|
if len(text) == 0:
|
1942
|
-
return
|
1951
|
+
return status
|
1943
1952
|
|
1944
1953
|
if text.startswith("@") and self._docHandle:
|
1945
1954
|
|
1946
1955
|
isGood, tBits, tPos = SHARED.project.index.scanThis(text)
|
1947
1956
|
if (
|
1948
|
-
not isGood
|
1949
|
-
or
|
1957
|
+
not isGood
|
1958
|
+
or not tBits
|
1959
|
+
or (key := tBits[0]) == nwKeyWords.TAG_KEY
|
1960
|
+
or key not in nwKeyWords.VALID_KEYS
|
1950
1961
|
):
|
1951
|
-
return
|
1962
|
+
return status
|
1952
1963
|
|
1953
1964
|
tag = ""
|
1954
1965
|
exist = False
|
@@ -1965,7 +1976,14 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1965
1976
|
|
1966
1977
|
if not tag or tag.startswith("@"):
|
1967
1978
|
# The keyword cannot be looked up, so we ignore that
|
1968
|
-
return
|
1979
|
+
return status
|
1980
|
+
|
1981
|
+
if not exist and key in nwKeyWords.CAN_CREATE:
|
1982
|
+
# Must only be set if we have a tag selected
|
1983
|
+
status |= _TagAction.CREATE
|
1984
|
+
|
1985
|
+
if exist:
|
1986
|
+
status |= _TagAction.FOLLOW
|
1969
1987
|
|
1970
1988
|
if follow and exist:
|
1971
1989
|
logger.debug("Attempting to follow tag '%s'", tag)
|
@@ -1977,9 +1995,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1977
1995
|
itemClass = nwKeyWords.KEY_CLASS.get(tBits[0], nwItemClass.NO_CLASS)
|
1978
1996
|
self.requestNewNoteCreation.emit(tag, itemClass)
|
1979
1997
|
|
1980
|
-
|
1981
|
-
|
1982
|
-
return nwTrinary.NEUTRAL
|
1998
|
+
return status
|
1983
1999
|
|
1984
2000
|
def _emitRenameItem(self, block: QTextBlock) -> None:
|
1985
2001
|
"""Emit a signal to request an item be renamed."""
|
novelwriter/gui/docviewer.py
CHANGED
novelwriter/gui/itemdetails.py
CHANGED
novelwriter/gui/projtree.py
CHANGED
@@ -955,23 +955,18 @@ class GuiProjectTree(QTreeView):
|
|
955
955
|
if model := self._getModel():
|
956
956
|
if point is None:
|
957
957
|
point = self.visualRect(self.currentIndex()).center()
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
ctxMenu.
|
969
|
-
|
970
|
-
ctxMenu.buildSingleSelectMenu()
|
971
|
-
|
972
|
-
ctxMenu.exec(self.viewport().mapToGlobal(point))
|
973
|
-
ctxMenu.deleteLater()
|
974
|
-
|
958
|
+
if point is not None:
|
959
|
+
index = self.indexAt(point)
|
960
|
+
if (node := self._getNode(index)) and (indices := self._selectedRows()):
|
961
|
+
ctxMenu = _TreeContextMenu(self, model, node, indices)
|
962
|
+
if node is SHARED.project.tree.trash:
|
963
|
+
ctxMenu.buildTrashMenu()
|
964
|
+
elif len(indices) > 1:
|
965
|
+
ctxMenu.buildMultiSelectMenu()
|
966
|
+
else:
|
967
|
+
ctxMenu.buildSingleSelectMenu()
|
968
|
+
ctxMenu.exec(self.viewport().mapToGlobal(point))
|
969
|
+
ctxMenu.setParent(None)
|
975
970
|
return
|
976
971
|
|
977
972
|
##
|
@@ -1020,7 +1015,9 @@ class GuiProjectTree(QTreeView):
|
|
1020
1015
|
def _clearSelection(self) -> None:
|
1021
1016
|
"""Clear the currently selected items."""
|
1022
1017
|
self.clearSelection()
|
1023
|
-
self.selectionModel()
|
1018
|
+
if model := self.selectionModel():
|
1019
|
+
# Selection model can be None (#2173)
|
1020
|
+
model.clearCurrentIndex()
|
1024
1021
|
return
|
1025
1022
|
|
1026
1023
|
def _selectedRows(self) -> list[QModelIndex]:
|
novelwriter/gui/statusbar.py
CHANGED
@@ -34,7 +34,6 @@ from PyQt5.QtWidgets import QApplication, QLabel, QStatusBar, QWidget
|
|
34
34
|
from novelwriter import CONFIG, SHARED
|
35
35
|
from novelwriter.common import formatTime
|
36
36
|
from novelwriter.constants import nwConst
|
37
|
-
from novelwriter.enum import nwTrinary
|
38
37
|
from novelwriter.extensions.modified import NClickableLabel
|
39
38
|
from novelwriter.extensions.statusled import StatusLED
|
40
39
|
|
@@ -121,8 +120,8 @@ class GuiMainStatus(QStatusBar):
|
|
121
120
|
self.setRefTime(-1.0)
|
122
121
|
self.setLanguage(*SHARED.spelling.describeDict())
|
123
122
|
self.setProjectStats(0, 0)
|
124
|
-
self.setProjectStatus(
|
125
|
-
self.setDocumentStatus(
|
123
|
+
self.setProjectStatus(None)
|
124
|
+
self.setDocumentStatus(None)
|
126
125
|
self.updateTime()
|
127
126
|
return
|
128
127
|
|
@@ -152,12 +151,12 @@ class GuiMainStatus(QStatusBar):
|
|
152
151
|
self._refTime = refTime
|
153
152
|
return
|
154
153
|
|
155
|
-
def setProjectStatus(self, state:
|
154
|
+
def setProjectStatus(self, state: bool | None) -> None:
|
156
155
|
"""Set the project status colour icon."""
|
157
156
|
self.projIcon.setState(state)
|
158
157
|
return
|
159
158
|
|
160
|
-
def setDocumentStatus(self, state:
|
159
|
+
def setDocumentStatus(self, state: bool | None) -> None:
|
161
160
|
"""Set the document status colour icon."""
|
162
161
|
self.docIcon.setState(state)
|
163
162
|
return
|
@@ -220,13 +219,13 @@ class GuiMainStatus(QStatusBar):
|
|
220
219
|
@pyqtSlot(bool)
|
221
220
|
def updateProjectStatus(self, status: bool) -> None:
|
222
221
|
"""Update the project status."""
|
223
|
-
self.setProjectStatus(
|
222
|
+
self.setProjectStatus(not status)
|
224
223
|
return
|
225
224
|
|
226
225
|
@pyqtSlot(bool)
|
227
226
|
def updateDocumentStatus(self, status: bool) -> None:
|
228
227
|
"""Update the document status."""
|
229
|
-
self.setDocumentStatus(
|
228
|
+
self.setDocumentStatus(not status)
|
230
229
|
return
|
231
230
|
|
232
231
|
##
|
novelwriter/guimain.py
CHANGED
@@ -852,7 +852,7 @@ class GuiMain(QMainWindow):
|
|
852
852
|
|
853
853
|
def closeMain(self) -> bool:
|
854
854
|
"""Save everything, and close novelWriter."""
|
855
|
-
if SHARED.hasProject and not SHARED.question("%s<br>%s" % (
|
855
|
+
if SHARED.hasProject and CONFIG.askBeforeExit and not SHARED.question("%s<br>%s" % (
|
856
856
|
self.tr("Do you want to exit novelWriter?"),
|
857
857
|
self.tr("Changes are saved automatically.")
|
858
858
|
)):
|
@@ -65,12 +65,12 @@ class GuiDictionaries(NNonBlockingDialog):
|
|
65
65
|
self.setMinimumHeight(CONFIG.pxInt(300))
|
66
66
|
|
67
67
|
# Hunspell Dictionaries
|
68
|
-
foUrl = "https://www.freeoffice.com/en/download/dictionaries"
|
69
68
|
loUrl = "https://extensions.libreoffice.org"
|
69
|
+
ooUrl = "https://extensions.openoffice.org"
|
70
70
|
self.huInfo = QLabel("<br>".join([
|
71
71
|
self.tr("Download a dictionary from one of the links, and add it below."),
|
72
|
-
f" \u203a <a href='{foUrl}'>{foUrl}</a>",
|
73
72
|
f" \u203a <a href='{loUrl}'>{loUrl}</a>",
|
73
|
+
f" \u203a <a href='{ooUrl}'>{ooUrl}</a>",
|
74
74
|
]), self)
|
75
75
|
self.huInfo.setOpenExternalLinks(True)
|
76
76
|
self.huInfo.setWordWrap(True)
|