novelWriter 2.6rc1__py3-none-any.whl → 2.6.2__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.2.dist-info}/METADATA +1 -1
- {novelWriter-2.6rc1.dist-info → novelWriter-2.6.2.dist-info}/RECORD +62 -59
- novelwriter/__init__.py +3 -3
- novelwriter/assets/i18n/nw_cs_CZ.qm +0 -0
- 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_cs_CZ.json +118 -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/assets/text/credits_en.htm +1 -0
- novelwriter/config.py +41 -19
- novelwriter/constants.py +4 -0
- novelwriter/core/buildsettings.py +7 -0
- novelwriter/core/itemmodel.py +4 -2
- novelwriter/core/project.py +2 -6
- novelwriter/dialogs/docsplit.py +49 -45
- 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 +16 -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.2.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.6rc1.dist-info → novelWriter-2.6.2.dist-info}/WHEEL +0 -0
- {novelWriter-2.6rc1.dist-info → novelWriter-2.6.2.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.6rc1.dist-info → novelWriter-2.6.2.dist-info}/top_level.txt +0 -0
novelwriter/config.py
CHANGED
@@ -32,6 +32,7 @@ import sys
|
|
32
32
|
from datetime import datetime
|
33
33
|
from pathlib import Path
|
34
34
|
from time import time
|
35
|
+
from typing import TYPE_CHECKING
|
35
36
|
|
36
37
|
from PyQt5.QtCore import (
|
37
38
|
PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo,
|
@@ -47,6 +48,9 @@ from novelwriter.common import (
|
|
47
48
|
from novelwriter.constants import nwFiles, nwUnicode
|
48
49
|
from novelwriter.error import formatException, logException
|
49
50
|
|
51
|
+
if TYPE_CHECKING: # pragma: no cover
|
52
|
+
from novelwriter.core.projectdata import NWProjectData
|
53
|
+
|
50
54
|
logger = logging.getLogger(__name__)
|
51
55
|
|
52
56
|
|
@@ -101,8 +105,11 @@ class Config:
|
|
101
105
|
self._qtTrans = {}
|
102
106
|
|
103
107
|
# PDF Manual
|
104
|
-
|
105
|
-
self.
|
108
|
+
self._manuals: dict[str, Path] = {}
|
109
|
+
if (assets := self._appPath / "assets").is_dir():
|
110
|
+
for item in assets.iterdir():
|
111
|
+
if item.is_file() and item.stem.startswith("manual") and item.suffix == ".pdf":
|
112
|
+
self._manuals[item.stem] = item
|
106
113
|
|
107
114
|
# User Settings
|
108
115
|
# =============
|
@@ -112,7 +119,7 @@ class Config:
|
|
112
119
|
|
113
120
|
# General GUI Settings
|
114
121
|
self.guiLocale = self._qLocale.name()
|
115
|
-
self.guiTheme = "
|
122
|
+
self.guiTheme = "default_light" # GUI theme
|
116
123
|
self.guiSyntax = "default_light" # Syntax theme
|
117
124
|
self.guiFont = QFont() # Main GUI font
|
118
125
|
self.guiScale = 1.0 # Set automatically by Theme class
|
@@ -135,6 +142,7 @@ class Config:
|
|
135
142
|
self.emphLabels = True # Add emphasis to H1 and H2 item labels
|
136
143
|
self.backupOnClose = False # Flag for running automatic backups
|
137
144
|
self.askBeforeBackup = True # Flag for asking before running automatic backup
|
145
|
+
self.askBeforeExit = True # Flag for asking before exiting the app
|
138
146
|
|
139
147
|
# Text Editor Settings
|
140
148
|
self.textFont = QFont() # Editor font
|
@@ -257,6 +265,10 @@ class Config:
|
|
257
265
|
def hasError(self) -> bool:
|
258
266
|
return self._hasError
|
259
267
|
|
268
|
+
@property
|
269
|
+
def pdfDocs(self) -> Path | None:
|
270
|
+
return self._manuals.get(f"manual_{self.locale.name()}", self._manuals.get("manual"))
|
271
|
+
|
260
272
|
@property
|
261
273
|
def locale(self) -> QLocale:
|
262
274
|
return self._dLocale
|
@@ -628,6 +640,7 @@ class Config:
|
|
628
640
|
self._backupPath = conf.rdPath(sec, "backuppath", self._backupPath)
|
629
641
|
self.backupOnClose = conf.rdBool(sec, "backuponclose", self.backupOnClose)
|
630
642
|
self.askBeforeBackup = conf.rdBool(sec, "askbeforebackup", self.askBeforeBackup)
|
643
|
+
self.askBeforeExit = conf.rdBool(sec, "askbeforeexit", self.askBeforeExit)
|
631
644
|
|
632
645
|
# Editor
|
633
646
|
sec = "Editor"
|
@@ -739,6 +752,7 @@ class Config:
|
|
739
752
|
"backuppath": str(self._backupPath),
|
740
753
|
"backuponclose": str(self.backupOnClose),
|
741
754
|
"askbeforebackup": str(self.askBeforeBackup),
|
755
|
+
"askbeforeexit": str(self.askBeforeExit),
|
742
756
|
}
|
743
757
|
|
744
758
|
conf["Editor"] = {
|
@@ -842,29 +856,30 @@ class RecentProjects:
|
|
842
856
|
|
843
857
|
def __init__(self, config: Config) -> None:
|
844
858
|
self._conf = config
|
845
|
-
self._data = {}
|
859
|
+
self._data: dict[str, dict[str, str | int]] = {}
|
860
|
+
self._map: dict[str, str] = {}
|
846
861
|
return
|
847
862
|
|
848
863
|
def loadCache(self) -> bool:
|
849
864
|
"""Load the cache file for recent projects."""
|
850
865
|
self._data = {}
|
851
|
-
|
866
|
+
self._map = {}
|
852
867
|
cacheFile = self._conf.dataPath(nwFiles.RECENT_FILE)
|
853
868
|
if cacheFile.is_file():
|
854
869
|
try:
|
855
870
|
with open(cacheFile, mode="r", encoding="utf-8") as inFile:
|
856
871
|
data = json.load(inFile)
|
857
872
|
for path, entry in data.items():
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
873
|
+
puuid = str(entry.get("uuid", ""))
|
874
|
+
title = str(entry.get("title", ""))
|
875
|
+
words = checkInt(entry.get("words", 0), 0)
|
876
|
+
saved = checkInt(entry.get("time", 0), 0)
|
877
|
+
if path and title:
|
878
|
+
self._setEntry(puuid, path, title, words, saved)
|
863
879
|
except Exception:
|
864
880
|
logger.error("Could not load recent project cache")
|
865
881
|
logException()
|
866
882
|
return False
|
867
|
-
|
868
883
|
return True
|
869
884
|
|
870
885
|
def saveCache(self) -> bool:
|
@@ -879,7 +894,6 @@ class RecentProjects:
|
|
879
894
|
logger.error("Could not save recent project cache")
|
880
895
|
logException()
|
881
896
|
return False
|
882
|
-
|
883
897
|
return True
|
884
898
|
|
885
899
|
def listEntries(self) -> list[tuple[str, str, int, int]]:
|
@@ -889,14 +903,15 @@ class RecentProjects:
|
|
889
903
|
for k, e in self._data.items()
|
890
904
|
]
|
891
905
|
|
892
|
-
def update(self, path: str | Path,
|
906
|
+
def update(self, path: str | Path, data: NWProjectData, saved: float | int) -> None:
|
893
907
|
"""Add or update recent cache information on a given project."""
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
908
|
+
try:
|
909
|
+
if (remove := self._map.get(data.uuid)) and (remove != str(path)):
|
910
|
+
self.remove(remove)
|
911
|
+
self._setEntry(data.uuid, str(path), data.name, sum(data.currCounts), int(saved))
|
912
|
+
self.saveCache()
|
913
|
+
except Exception:
|
914
|
+
pass
|
900
915
|
return
|
901
916
|
|
902
917
|
def remove(self, path: str | Path) -> None:
|
@@ -906,6 +921,13 @@ class RecentProjects:
|
|
906
921
|
self.saveCache()
|
907
922
|
return
|
908
923
|
|
924
|
+
def _setEntry(self, puuid: str, path: str, title: str, words: int, saved: int) -> None:
|
925
|
+
"""Set an entry in the recent projects record."""
|
926
|
+
self._data[path] = {"uuid": puuid, "title": title, "words": words, "time": saved}
|
927
|
+
if puuid:
|
928
|
+
self._map[puuid] = path
|
929
|
+
return
|
930
|
+
|
909
931
|
|
910
932
|
class RecentPaths:
|
911
933
|
|
novelwriter/constants.py
CHANGED
@@ -173,6 +173,10 @@ class nwKeyWords:
|
|
173
173
|
TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
|
174
174
|
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_KEY,
|
175
175
|
]
|
176
|
+
CAN_CREATE = [
|
177
|
+
POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
|
178
|
+
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY,
|
179
|
+
]
|
176
180
|
|
177
181
|
# Set of Valid Keys
|
178
182
|
VALID_KEYS = set(ALL_KEYS)
|
@@ -159,6 +159,13 @@ SETTINGS_LABELS = {
|
|
159
159
|
"format.indentFirstPar": QT_TRANSLATE_NOOP("Builds", "Indent First Paragraph"),
|
160
160
|
|
161
161
|
"format.grpMargins": QT_TRANSLATE_NOOP("Builds", "Text Margins"),
|
162
|
+
"format.titleMargin": QT_TRANSLATE_NOOP("Builds", "Title and Partition"),
|
163
|
+
"format.h1Margin": QT_TRANSLATE_NOOP("Builds", "Heading 1 and Chapter"),
|
164
|
+
"format.h2Margin": QT_TRANSLATE_NOOP("Builds", "Heading 2 and Scene"),
|
165
|
+
"format.h3Margin": QT_TRANSLATE_NOOP("Builds", "Heading 3 and Section"),
|
166
|
+
"format.h4Margin": QT_TRANSLATE_NOOP("Builds", "Heading 4"),
|
167
|
+
"format.textMargin": QT_TRANSLATE_NOOP("Builds", "Text Paragraph"),
|
168
|
+
"format.sepMargin": QT_TRANSLATE_NOOP("Builds", "Scene Separator"),
|
162
169
|
|
163
170
|
"format.grpPage": QT_TRANSLATE_NOOP("Builds", "Page Layout"),
|
164
171
|
"format.pageUnit": QT_TRANSLATE_NOOP("Builds", "Unit"),
|
novelwriter/core/itemmodel.py
CHANGED
@@ -328,7 +328,7 @@ class ProjectModel(QAbstractItemModel):
|
|
328
328
|
|
329
329
|
def parent(self, index: QModelIndex) -> QModelIndex:
|
330
330
|
"""Get the parent model index of another index."""
|
331
|
-
if index.isValid() and (
|
331
|
+
if index.isValid() and (node := index.internalPointer()) and (parent := node.parent()):
|
332
332
|
return self.createIndex(parent.row(), 0, parent)
|
333
333
|
return QModelIndex()
|
334
334
|
|
@@ -379,7 +379,9 @@ class ProjectModel(QAbstractItemModel):
|
|
379
379
|
row: int, column: int, parent: QModelIndex
|
380
380
|
) -> bool:
|
381
381
|
"""Check if mime data can be dropped on the current location."""
|
382
|
-
|
382
|
+
if parent.isValid() and parent.internalPointer() is not self._root:
|
383
|
+
return data.hasFormat(nwConst.MIME_HANDLE) and action == Qt.DropAction.MoveAction
|
384
|
+
return False
|
383
385
|
|
384
386
|
def dropMimeData(
|
385
387
|
self, data: QMimeData, action: Qt.DropAction,
|
novelwriter/core/project.py
CHANGED
@@ -353,9 +353,7 @@ class NWProject:
|
|
353
353
|
|
354
354
|
# Update recent projects
|
355
355
|
if storePath := self._storage.storagePath:
|
356
|
-
CONFIG.recentProjects.update(
|
357
|
-
storePath, self._data.name, sum(self._data.initCounts), time()
|
358
|
-
)
|
356
|
+
CONFIG.recentProjects.update(storePath, self._data, time())
|
359
357
|
|
360
358
|
# Check the project tree consistency
|
361
359
|
# This also handles any orphaned files found
|
@@ -421,9 +419,7 @@ class NWProject:
|
|
421
419
|
|
422
420
|
# Update recent projects
|
423
421
|
if storagePath := self._storage.storagePath:
|
424
|
-
CONFIG.recentProjects.update(
|
425
|
-
storagePath, self._data.name, sum(self._data.currCounts), saveTime
|
426
|
-
)
|
422
|
+
CONFIG.recentProjects.update(storagePath, self._data, saveTime)
|
427
423
|
|
428
424
|
SHARED.newStatusMessage(self.tr("Saved Project: {0}").format(self._data.name))
|
429
425
|
self.setProjectChanged(False)
|
novelwriter/dialogs/docsplit.py
CHANGED
@@ -71,10 +71,10 @@ class GuiDocSplit(NDialog):
|
|
71
71
|
vSp = CONFIG.pxInt(8)
|
72
72
|
bSp = CONFIG.pxInt(12)
|
73
73
|
|
74
|
-
|
75
|
-
spLevel =
|
76
|
-
intoFolder =
|
77
|
-
docHierarchy =
|
74
|
+
options = SHARED.project.options
|
75
|
+
spLevel = options.getInt("GuiDocSplit", "spLevel", 3)
|
76
|
+
intoFolder = options.getBool("GuiDocSplit", "intoFolder", True)
|
77
|
+
docHierarchy = options.getBool("GuiDocSplit", "docHierarchy", True)
|
78
78
|
|
79
79
|
# Heading Selection
|
80
80
|
self.listBox = QListWidget(self)
|
@@ -171,10 +171,10 @@ class GuiDocSplit(NDialog):
|
|
171
171
|
self._data["moveToTrash"] = moveToTrash
|
172
172
|
|
173
173
|
logger.debug("Saving State: GuiDocSplit")
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
174
|
+
options = SHARED.project.options
|
175
|
+
options.setValue("GuiDocSplit", "spLevel", spLevel)
|
176
|
+
options.setValue("GuiDocSplit", "intoFolder", intoFolder)
|
177
|
+
options.setValue("GuiDocSplit", "docHierarchy", docHierarchy)
|
178
178
|
|
179
179
|
return self._data, self._text
|
180
180
|
|
@@ -218,42 +218,46 @@ class GuiDocSplit(NDialog):
|
|
218
218
|
if not self._text:
|
219
219
|
self._text = SHARED.project.storage.getDocumentText(sHandle).splitlines()
|
220
220
|
|
221
|
-
for
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
if
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
elif
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
elif
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
elif
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
elif
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
elif
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
221
|
+
for i, line in enumerate(self._text):
|
222
|
+
|
223
|
+
pos = -1
|
224
|
+
level = 0
|
225
|
+
label = line.strip()
|
226
|
+
if line.startswith("# ") and spLevel >= 1:
|
227
|
+
pos = i
|
228
|
+
level = 1
|
229
|
+
label = line[2:].strip()
|
230
|
+
elif line.startswith("## ") and spLevel >= 2:
|
231
|
+
pos = i
|
232
|
+
level = 2
|
233
|
+
label = line[3:].strip()
|
234
|
+
elif line.startswith("### ") and spLevel >= 3:
|
235
|
+
pos = i
|
236
|
+
level = 3
|
237
|
+
label = line[4:].strip()
|
238
|
+
elif line.startswith("#### ") and spLevel >= 4:
|
239
|
+
pos = i
|
240
|
+
level = 4
|
241
|
+
label = line[5:].strip()
|
242
|
+
elif line.startswith("#! ") and spLevel >= 1:
|
243
|
+
pos = i
|
244
|
+
level = 1
|
245
|
+
label = line[3:].strip()
|
246
|
+
elif line.startswith("##! ") and spLevel >= 2:
|
247
|
+
pos = i
|
248
|
+
level = 2
|
249
|
+
label = line[4:].strip()
|
250
|
+
elif line.startswith("###! ") and spLevel >= 3:
|
251
|
+
pos = i
|
252
|
+
level = 3
|
253
|
+
label = line[5:].strip()
|
254
|
+
|
255
|
+
if pos >= 0 and level > 0:
|
256
|
+
trItem = QListWidgetItem()
|
257
|
+
trItem.setText(line.strip())
|
258
|
+
trItem.setData(self.LINE_ROLE, pos)
|
259
|
+
trItem.setData(self.LEVEL_ROLE, level)
|
260
|
+
trItem.setData(self.LABEL_ROLE, label)
|
261
|
+
self.listBox.addItem(trItem)
|
258
262
|
|
259
263
|
return
|
@@ -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;}}"
|