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/common.py
CHANGED
@@ -29,6 +29,7 @@ import unicodedata
|
|
29
29
|
import uuid
|
30
30
|
import xml.etree.ElementTree as ET
|
31
31
|
|
32
|
+
from collections.abc import Callable
|
32
33
|
from configparser import ConfigParser
|
33
34
|
from datetime import datetime
|
34
35
|
from pathlib import Path
|
@@ -36,8 +37,8 @@ from typing import TYPE_CHECKING, Any, Literal, TypeVar
|
|
36
37
|
from urllib.parse import urljoin
|
37
38
|
from urllib.request import pathname2url
|
38
39
|
|
39
|
-
from PyQt5.QtCore import QCoreApplication, QUrl
|
40
|
-
from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontInfo
|
40
|
+
from PyQt5.QtCore import QCoreApplication, QMimeData, QUrl
|
41
|
+
from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo
|
41
42
|
|
42
43
|
from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
|
43
44
|
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
@@ -204,6 +205,14 @@ def checkIntTuple(value: int, valid: tuple | list | set, default: int) -> int:
|
|
204
205
|
return default
|
205
206
|
|
206
207
|
|
208
|
+
def firstFloat(*args: Any) -> float:
|
209
|
+
"""Return the first value that is a float."""
|
210
|
+
for arg in args:
|
211
|
+
if isinstance(arg, float):
|
212
|
+
return arg
|
213
|
+
return 0.0
|
214
|
+
|
215
|
+
|
207
216
|
##
|
208
217
|
# Formatting Functions
|
209
218
|
##
|
@@ -406,6 +415,10 @@ def numberToRoman(value: int, toLower: bool = False) -> str:
|
|
406
415
|
return roman.lower() if toLower else roman
|
407
416
|
|
408
417
|
|
418
|
+
##
|
419
|
+
# Qt Helpers
|
420
|
+
##
|
421
|
+
|
409
422
|
def cssCol(col: QColor, alpha: int | None = None) -> str:
|
410
423
|
"""Convert a QColor object to an rgba entry to use in CSS."""
|
411
424
|
return f"rgba({col.red()}, {col.green()}, {col.blue()}, {alpha or col.alpha()})"
|
@@ -421,6 +434,48 @@ def describeFont(font: QFont) -> str:
|
|
421
434
|
return "Error"
|
422
435
|
|
423
436
|
|
437
|
+
def fontMatcher(font: QFont) -> QFont:
|
438
|
+
"""Make sure the font is the correct family, if possible. This
|
439
|
+
ensures that Qt doesn't re-use another font under the hood. The
|
440
|
+
default Qt5 font matching algorithm doesn't handle well changing
|
441
|
+
application fonts at runtime.
|
442
|
+
"""
|
443
|
+
info = QFontInfo(font)
|
444
|
+
if (famRequest := font.family()) != (famActual := info.family()):
|
445
|
+
logger.warning("Font mismatch: Requested '%s', but got '%s'", famRequest, famActual)
|
446
|
+
db = QFontDatabase()
|
447
|
+
if famRequest in db.families():
|
448
|
+
styleRequest, sizeRequest = font.styleName(), font.pointSize()
|
449
|
+
logger.info("Lookup: %s, %s, %d pt", famRequest, styleRequest, sizeRequest)
|
450
|
+
temp = db.font(famRequest, styleRequest, sizeRequest)
|
451
|
+
temp.setPointSize(sizeRequest) # Make sure it isn't changed
|
452
|
+
famFound, styleFound, sizeFound = temp.family(), temp.styleName(), temp.pointSize()
|
453
|
+
if famFound == famRequest:
|
454
|
+
logger.info("Found: %s, %s, %d pt", famFound, styleFound, sizeFound)
|
455
|
+
return temp
|
456
|
+
logger.warning("Could not find a font match in the font database")
|
457
|
+
logger.warning("If you just changed font, you may need to restart the application")
|
458
|
+
return font
|
459
|
+
|
460
|
+
|
461
|
+
def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable:
|
462
|
+
"""A replacement for Python lambdas that works for Qt slots."""
|
463
|
+
def wrapper(*a_: Any) -> None:
|
464
|
+
func(*args, **kwargs)
|
465
|
+
return wrapper
|
466
|
+
|
467
|
+
|
468
|
+
def encodeMimeHandles(mimeData: QMimeData, handles: list[str]) -> None:
|
469
|
+
"""Encode handles into a mime data object."""
|
470
|
+
mimeData.setData(nwConst.MIME_HANDLE, b"|".join(h.encode() for h in handles))
|
471
|
+
return
|
472
|
+
|
473
|
+
|
474
|
+
def decodeMimeHandles(mimeData: QMimeData) -> list[str]:
|
475
|
+
"""Decode and split a mime data object with handles."""
|
476
|
+
return mimeData.data(nwConst.MIME_HANDLE).data().decode().split("|")
|
477
|
+
|
478
|
+
|
424
479
|
##
|
425
480
|
# Encoder Functions
|
426
481
|
##
|
@@ -472,6 +527,10 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
|
|
472
527
|
return "".join(buffer)
|
473
528
|
|
474
529
|
|
530
|
+
##
|
531
|
+
# XML Helpers
|
532
|
+
##
|
533
|
+
|
475
534
|
def xmlIndent(tree: ET.Element | ET.ElementTree) -> None:
|
476
535
|
"""A modified version of the XML indent function in the standard
|
477
536
|
library. It behaves more closely to how the one from lxml does.
|
@@ -515,6 +574,45 @@ def xmlIndent(tree: ET.Element | ET.ElementTree) -> None:
|
|
515
574
|
return
|
516
575
|
|
517
576
|
|
577
|
+
def xmlElement(
|
578
|
+
tag: str,
|
579
|
+
text: str | int | float | bool | None = None,
|
580
|
+
*,
|
581
|
+
attrib: dict | None = None,
|
582
|
+
tail: str | None = None,
|
583
|
+
) -> ET.Element:
|
584
|
+
"""A custom implementation of Element with more arguments."""
|
585
|
+
xSub = ET.Element(tag, attrib=attrib or {})
|
586
|
+
if text is not None:
|
587
|
+
if isinstance(text, bool):
|
588
|
+
xSub.text = str(text).lower()
|
589
|
+
else:
|
590
|
+
xSub.text = str(text)
|
591
|
+
if tail is not None:
|
592
|
+
xSub.tail = tail
|
593
|
+
return xSub
|
594
|
+
|
595
|
+
|
596
|
+
def xmlSubElem(
|
597
|
+
parent: ET.Element,
|
598
|
+
tag: str,
|
599
|
+
text: str | int | float | bool | None = None,
|
600
|
+
*,
|
601
|
+
attrib: dict | None = None,
|
602
|
+
tail: str | None = None,
|
603
|
+
) -> ET.Element:
|
604
|
+
"""A custom implementation of SubElement with more arguments."""
|
605
|
+
xSub = ET.SubElement(parent, tag, attrib=attrib or {})
|
606
|
+
if text is not None:
|
607
|
+
if isinstance(text, bool):
|
608
|
+
xSub.text = str(text).lower()
|
609
|
+
else:
|
610
|
+
xSub.text = str(text)
|
611
|
+
if tail is not None:
|
612
|
+
xSub.tail = tail
|
613
|
+
return xSub
|
614
|
+
|
615
|
+
|
518
616
|
##
|
519
617
|
# File and File System Functions
|
520
618
|
##
|
novelwriter/config.py
CHANGED
@@ -40,7 +40,10 @@ from PyQt5.QtCore import (
|
|
40
40
|
from PyQt5.QtGui import QFont, QFontDatabase
|
41
41
|
from PyQt5.QtWidgets import QApplication
|
42
42
|
|
43
|
-
from novelwriter.common import
|
43
|
+
from novelwriter.common import (
|
44
|
+
NWConfigParser, checkInt, checkPath, describeFont, fontMatcher,
|
45
|
+
formatTimeStamp
|
46
|
+
)
|
44
47
|
from novelwriter.constants import nwFiles, nwUnicode
|
45
48
|
from novelwriter.error import formatException, logException
|
46
49
|
|
@@ -161,8 +164,9 @@ class Config:
|
|
161
164
|
|
162
165
|
self.dialogStyle = 2 # Quote type to use for dialogue
|
163
166
|
self.allowOpenDial = True # Allow open-ended dialogue quotes
|
164
|
-
self.narratorBreak = "" # Symbol to use for narrator break
|
165
167
|
self.dialogLine = "" # Symbol to use for dialogue line
|
168
|
+
self.narratorBreak = "" # Symbol to use for narrator break
|
169
|
+
self.narratorDialog = "" # Symbol for alternating between dialogue and narrator
|
166
170
|
self.altDialogOpen = "" # Alternative dialog symbol, open
|
167
171
|
self.altDialogClose = "" # Alternative dialog symbol, close
|
168
172
|
self.highlightEmph = True # Add colour to text emphasis
|
@@ -252,6 +256,10 @@ class Config:
|
|
252
256
|
def hasError(self) -> bool:
|
253
257
|
return self._hasError
|
254
258
|
|
259
|
+
@property
|
260
|
+
def locale(self) -> QLocale:
|
261
|
+
return self._dLocale
|
262
|
+
|
255
263
|
@property
|
256
264
|
def recentProjects(self) -> RecentProjects:
|
257
265
|
return self._recentProjects
|
@@ -364,10 +372,11 @@ class Config:
|
|
364
372
|
def setGuiFont(self, value: QFont | str | None) -> None:
|
365
373
|
"""Update the GUI's font style from settings."""
|
366
374
|
if isinstance(value, QFont):
|
367
|
-
self.guiFont = value
|
375
|
+
self.guiFont = fontMatcher(value)
|
368
376
|
elif value and isinstance(value, str):
|
369
|
-
|
370
|
-
|
377
|
+
font = QFont()
|
378
|
+
font.fromString(value)
|
379
|
+
self.guiFont = fontMatcher(font)
|
371
380
|
else:
|
372
381
|
font = QFont()
|
373
382
|
fontDB = QFontDatabase()
|
@@ -377,11 +386,9 @@ class Config:
|
|
377
386
|
font.setPointSize(10)
|
378
387
|
else:
|
379
388
|
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
|
380
|
-
self.guiFont = font
|
389
|
+
self.guiFont = fontMatcher(font)
|
381
390
|
logger.debug("GUI font set to: %s", describeFont(font))
|
382
|
-
|
383
391
|
QApplication.setFont(self.guiFont)
|
384
|
-
|
385
392
|
return
|
386
393
|
|
387
394
|
def setTextFont(self, value: QFont | str | None) -> None:
|
@@ -389,10 +396,11 @@ class Config:
|
|
389
396
|
set to default font.
|
390
397
|
"""
|
391
398
|
if isinstance(value, QFont):
|
392
|
-
self.textFont = value
|
399
|
+
self.textFont = fontMatcher(value)
|
393
400
|
elif value and isinstance(value, str):
|
394
|
-
|
395
|
-
|
401
|
+
font = QFont()
|
402
|
+
font.fromString(value)
|
403
|
+
self.textFont = fontMatcher(font)
|
396
404
|
else:
|
397
405
|
fontDB = QFontDatabase()
|
398
406
|
fontFam = fontDB.families()
|
@@ -406,8 +414,8 @@ class Config:
|
|
406
414
|
font.setPointSize(12)
|
407
415
|
else:
|
408
416
|
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
|
409
|
-
self.textFont = font
|
410
|
-
logger.debug("Text font set to: %s", describeFont(
|
417
|
+
self.textFont = fontMatcher(font)
|
418
|
+
logger.debug("Text font set to: %s", describeFont(self.textFont))
|
411
419
|
return
|
412
420
|
|
413
421
|
##
|
@@ -653,10 +661,11 @@ class Config:
|
|
653
661
|
self.showFullPath = conf.rdBool(sec, "showfullpath", self.showFullPath)
|
654
662
|
self.dialogStyle = conf.rdInt(sec, "dialogstyle", self.dialogStyle)
|
655
663
|
self.allowOpenDial = conf.rdBool(sec, "allowopendial", self.allowOpenDial)
|
664
|
+
self.dialogLine = conf.rdStr(sec, "dialogline", self.dialogLine)
|
656
665
|
self.narratorBreak = conf.rdStr(sec, "narratorbreak", self.narratorBreak)
|
666
|
+
self.narratorDialog = conf.rdStr(sec, "narratordialog", self.narratorDialog)
|
657
667
|
self.altDialogOpen = conf.rdStr(sec, "altdialogopen", self.altDialogOpen)
|
658
668
|
self.altDialogClose = conf.rdStr(sec, "altdialogclose", self.altDialogClose)
|
659
|
-
self.dialogLine = conf.rdStr(sec, "dialogline", self.dialogLine)
|
660
669
|
self.highlightEmph = conf.rdBool(sec, "highlightemph", self.highlightEmph)
|
661
670
|
self.stopWhenIdle = conf.rdBool(sec, "stopwhenidle", self.stopWhenIdle)
|
662
671
|
self.userIdleTime = conf.rdInt(sec, "useridletime", self.userIdleTime)
|
@@ -762,10 +771,11 @@ class Config:
|
|
762
771
|
"showfullpath": str(self.showFullPath),
|
763
772
|
"dialogstyle": str(self.dialogStyle),
|
764
773
|
"allowopendial": str(self.allowOpenDial),
|
774
|
+
"dialogline": str(self.dialogLine),
|
765
775
|
"narratorbreak": str(self.narratorBreak),
|
776
|
+
"narratordialog": str(self.narratorDialog),
|
766
777
|
"altdialogopen": str(self.altDialogOpen),
|
767
778
|
"altdialogclose": str(self.altDialogClose),
|
768
|
-
"dialogline": str(self.dialogLine),
|
769
779
|
"highlightemph": str(self.highlightEmph),
|
770
780
|
"stopwhenidle": str(self.stopWhenIdle),
|
771
781
|
"useridletime": str(self.userIdleTime),
|
novelwriter/constants.py
CHANGED
@@ -53,6 +53,9 @@ class nwConst:
|
|
53
53
|
# Requests
|
54
54
|
USER_AGENT = "Mozilla/5.0 (compatible; novelWriter (Python))"
|
55
55
|
|
56
|
+
# Mime Types
|
57
|
+
MIME_HANDLE = "text/vnd.novelwriter.handle"
|
58
|
+
|
56
59
|
# Gui Settings
|
57
60
|
STATUS_MSG_TIMEOUT = 15000 # milliseconds
|
58
61
|
MAX_SEARCH_RESULT = 1000
|
@@ -60,11 +63,14 @@ class nwConst:
|
|
60
63
|
|
61
64
|
class nwRegEx:
|
62
65
|
|
66
|
+
URL = r"https?://(?:www\.|(?!www))[\w/()@:%_\+-.~#?&=]+"
|
67
|
+
WORDS = r"\b[^\s\-\+\/–—\[\]:]+\b"
|
68
|
+
BREAK = r"(?i)(?<!\\)(\[br\]\n?)"
|
63
69
|
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
|
64
70
|
FMT_EB = r"(?<![\w\\])(\*{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
|
65
71
|
FMT_ST = r"(?<![\w\\])(~{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)"
|
66
|
-
FMT_SC = r"(?i)(?<!\\)(\[
|
67
|
-
FMT_SV = r"(?i)(?<!\\)(\[(?:footnote):)(.+?)(?<!\\)(\])"
|
72
|
+
FMT_SC = r"(?i)(?<!\\)(\[(?:b|/b|i|/i|s|/s|u|/u|m|/m|sup|/sup|sub|/sub|br)\])"
|
73
|
+
FMT_SV = r"(?i)(?<!\\)(\[(?:footnote|field):)(.+?)(?<!\\)(\])"
|
68
74
|
|
69
75
|
|
70
76
|
class nwShortcode:
|
@@ -83,21 +89,49 @@ class nwShortcode:
|
|
83
89
|
SUP_C = "[/sup]"
|
84
90
|
SUB_O = "[sub]"
|
85
91
|
SUB_C = "[/sub]"
|
92
|
+
BREAK = "[br]"
|
86
93
|
|
87
94
|
FOOTNOTE_B = "[footnote:"
|
95
|
+
FIELD_B = "[field:"
|
88
96
|
|
89
97
|
COMMENT_STYLES = {
|
90
98
|
nwComment.FOOTNOTE: "[footnote:{0}]",
|
91
99
|
nwComment.COMMENT: "[comment:{0}]",
|
92
100
|
}
|
93
101
|
|
102
|
+
FIELD_VALUE = "[field:{0}]"
|
103
|
+
|
94
104
|
|
95
|
-
class
|
105
|
+
class nwStyles:
|
96
106
|
|
97
107
|
H_VALID = ("H0", "H1", "H2", "H3", "H4")
|
98
108
|
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
|
99
109
|
H_SIZES = {0: 2.50, 1: 2.00, 2: 1.75, 3: 1.50, 4: 1.25}
|
100
110
|
|
111
|
+
T_NORMAL = 1.0
|
112
|
+
T_SMALL = 0.8
|
113
|
+
|
114
|
+
T_LABEL = {
|
115
|
+
"H0": QT_TRANSLATE_NOOP("Constant", "Title"),
|
116
|
+
"H1": QT_TRANSLATE_NOOP("Constant", "Heading 1 (Partition)"),
|
117
|
+
"H2": QT_TRANSLATE_NOOP("Constant", "Heading 2 (Chapter)"),
|
118
|
+
"H3": QT_TRANSLATE_NOOP("Constant", "Heading 3 (Scene)"),
|
119
|
+
"H4": QT_TRANSLATE_NOOP("Constant", "Heading 4 (Section)"),
|
120
|
+
"TT": QT_TRANSLATE_NOOP("Constant", "Text Paragraph"),
|
121
|
+
"SP": QT_TRANSLATE_NOOP("Constant", "Scene Separator"),
|
122
|
+
}
|
123
|
+
T_MARGIN = {
|
124
|
+
"H0": (1.50, 0.60), # Title margins (top, bottom)
|
125
|
+
"H1": (1.50, 0.60), # Heading 1 margins (top, bottom)
|
126
|
+
"H2": (1.50, 0.60), # Heading 2 margins (top, bottom)
|
127
|
+
"H3": (1.20, 0.60), # Heading 3 margins (top, bottom)
|
128
|
+
"H4": (1.20, 0.60), # Heading 4 margins (top, bottom)
|
129
|
+
"TT": (0.00, 0.60), # Text margins (top, bottom)
|
130
|
+
"SP": (1.20, 1.20), # Separator margins (top, bottom)
|
131
|
+
"MT": (0.00, 0.60), # Meta margins (top, bottom)
|
132
|
+
"FT": (1.40, 0.40), # Footnote margins (left, bottom)
|
133
|
+
}
|
134
|
+
|
101
135
|
|
102
136
|
class nwFiles:
|
103
137
|
|
@@ -121,34 +155,40 @@ class nwFiles:
|
|
121
155
|
|
122
156
|
class nwKeyWords:
|
123
157
|
|
124
|
-
TAG_KEY
|
125
|
-
POV_KEY
|
126
|
-
FOCUS_KEY
|
127
|
-
CHAR_KEY
|
128
|
-
PLOT_KEY
|
129
|
-
TIME_KEY
|
130
|
-
WORLD_KEY
|
131
|
-
OBJECT_KEY
|
132
|
-
ENTITY_KEY
|
133
|
-
CUSTOM_KEY
|
158
|
+
TAG_KEY = "@tag"
|
159
|
+
POV_KEY = "@pov"
|
160
|
+
FOCUS_KEY = "@focus"
|
161
|
+
CHAR_KEY = "@char"
|
162
|
+
PLOT_KEY = "@plot"
|
163
|
+
TIME_KEY = "@time"
|
164
|
+
WORLD_KEY = "@location"
|
165
|
+
OBJECT_KEY = "@object"
|
166
|
+
ENTITY_KEY = "@entity"
|
167
|
+
CUSTOM_KEY = "@custom"
|
168
|
+
STORY_KEY = "@story"
|
169
|
+
MENTION_KEY = "@mention"
|
170
|
+
|
171
|
+
# Note: The order here affects the order of menu entries
|
172
|
+
ALL_KEYS = [
|
173
|
+
TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
|
174
|
+
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_KEY,
|
175
|
+
]
|
134
176
|
|
135
177
|
# Set of Valid Keys
|
136
|
-
VALID_KEYS =
|
137
|
-
TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY,
|
138
|
-
WORLD_KEY, OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY
|
139
|
-
}
|
178
|
+
VALID_KEYS = set(ALL_KEYS)
|
140
179
|
|
141
180
|
# Map from Keys to Item Class
|
142
181
|
KEY_CLASS = {
|
143
|
-
POV_KEY:
|
144
|
-
FOCUS_KEY:
|
145
|
-
CHAR_KEY:
|
146
|
-
PLOT_KEY:
|
147
|
-
TIME_KEY:
|
148
|
-
WORLD_KEY:
|
149
|
-
OBJECT_KEY:
|
150
|
-
ENTITY_KEY:
|
151
|
-
CUSTOM_KEY:
|
182
|
+
POV_KEY: nwItemClass.CHARACTER,
|
183
|
+
FOCUS_KEY: nwItemClass.CHARACTER,
|
184
|
+
CHAR_KEY: nwItemClass.CHARACTER,
|
185
|
+
PLOT_KEY: nwItemClass.PLOT,
|
186
|
+
TIME_KEY: nwItemClass.TIMELINE,
|
187
|
+
WORLD_KEY: nwItemClass.WORLD,
|
188
|
+
OBJECT_KEY: nwItemClass.OBJECT,
|
189
|
+
ENTITY_KEY: nwItemClass.ENTITY,
|
190
|
+
CUSTOM_KEY: nwItemClass.CUSTOM,
|
191
|
+
STORY_KEY: nwItemClass.NOVEL,
|
152
192
|
}
|
153
193
|
|
154
194
|
|
@@ -165,6 +205,29 @@ class nwLists:
|
|
165
205
|
]
|
166
206
|
|
167
207
|
|
208
|
+
class nwStats:
|
209
|
+
|
210
|
+
CHARS_ALL = "allChars"
|
211
|
+
CHARS_TEXT = "textChars"
|
212
|
+
CHARS_TITLE = "titleChars"
|
213
|
+
PARAGRAPHS = "paragraphCount"
|
214
|
+
TITLES = "titleCount"
|
215
|
+
WCHARS_ALL = "allWordChars"
|
216
|
+
WCHARS_TEXT = "textWordChars"
|
217
|
+
WCHARS_TITLE = "titleWordChars"
|
218
|
+
WORDS_ALL = "allWords"
|
219
|
+
WORDS_TEXT = "textWords"
|
220
|
+
WORDS_TITLE = "titleWords"
|
221
|
+
|
222
|
+
# Note: The order here affects the order of menu entries
|
223
|
+
ALL_FIELDS = [
|
224
|
+
WORDS_ALL, WORDS_TEXT, WORDS_TITLE,
|
225
|
+
CHARS_ALL, CHARS_TEXT, CHARS_TITLE,
|
226
|
+
WCHARS_ALL, WCHARS_TEXT, WCHARS_TITLE,
|
227
|
+
PARAGRAPHS, TITLES,
|
228
|
+
]
|
229
|
+
|
230
|
+
|
168
231
|
class nwLabels:
|
169
232
|
|
170
233
|
CLASS_NAME = {
|
@@ -211,54 +274,94 @@ class nwLabels:
|
|
211
274
|
"doc_h4": QT_TRANSLATE_NOOP("Constant", "Novel Section"),
|
212
275
|
"note": QT_TRANSLATE_NOOP("Constant", "Project Note"),
|
213
276
|
}
|
277
|
+
ACTIVE_NAME = {
|
278
|
+
"checked": QT_TRANSLATE_NOOP("Constant", "Active"),
|
279
|
+
"unchecked": QT_TRANSLATE_NOOP("Constant", "Inactive"),
|
280
|
+
}
|
214
281
|
KEY_NAME = {
|
215
|
-
nwKeyWords.TAG_KEY:
|
216
|
-
nwKeyWords.POV_KEY:
|
217
|
-
nwKeyWords.FOCUS_KEY:
|
218
|
-
nwKeyWords.CHAR_KEY:
|
219
|
-
nwKeyWords.PLOT_KEY:
|
220
|
-
nwKeyWords.TIME_KEY:
|
221
|
-
nwKeyWords.WORLD_KEY:
|
222
|
-
nwKeyWords.OBJECT_KEY:
|
223
|
-
nwKeyWords.ENTITY_KEY:
|
224
|
-
nwKeyWords.CUSTOM_KEY:
|
282
|
+
nwKeyWords.TAG_KEY: QT_TRANSLATE_NOOP("Constant", "Tag"),
|
283
|
+
nwKeyWords.POV_KEY: QT_TRANSLATE_NOOP("Constant", "Point of View"),
|
284
|
+
nwKeyWords.FOCUS_KEY: QT_TRANSLATE_NOOP("Constant", "Focus"),
|
285
|
+
nwKeyWords.CHAR_KEY: QT_TRANSLATE_NOOP("Constant", "Characters"),
|
286
|
+
nwKeyWords.PLOT_KEY: QT_TRANSLATE_NOOP("Constant", "Plot"),
|
287
|
+
nwKeyWords.TIME_KEY: QT_TRANSLATE_NOOP("Constant", "Timeline"),
|
288
|
+
nwKeyWords.WORLD_KEY: QT_TRANSLATE_NOOP("Constant", "Locations"),
|
289
|
+
nwKeyWords.OBJECT_KEY: QT_TRANSLATE_NOOP("Constant", "Objects"),
|
290
|
+
nwKeyWords.ENTITY_KEY: QT_TRANSLATE_NOOP("Constant", "Entities"),
|
291
|
+
nwKeyWords.CUSTOM_KEY: QT_TRANSLATE_NOOP("Constant", "Custom"),
|
292
|
+
nwKeyWords.STORY_KEY: QT_TRANSLATE_NOOP("Constant", "Story"),
|
293
|
+
nwKeyWords.MENTION_KEY: QT_TRANSLATE_NOOP("Constant", "Mentions"),
|
294
|
+
}
|
295
|
+
KEY_SHORTCUT = {
|
296
|
+
nwKeyWords.TAG_KEY: "Ctrl+K, G",
|
297
|
+
nwKeyWords.POV_KEY: "Ctrl+K, V",
|
298
|
+
nwKeyWords.FOCUS_KEY: "Ctrl+K, F",
|
299
|
+
nwKeyWords.CHAR_KEY: "Ctrl+K, C",
|
300
|
+
nwKeyWords.PLOT_KEY: "Ctrl+K, P",
|
301
|
+
nwKeyWords.TIME_KEY: "Ctrl+K, T",
|
302
|
+
nwKeyWords.WORLD_KEY: "Ctrl+K, L",
|
303
|
+
nwKeyWords.OBJECT_KEY: "Ctrl+K, O",
|
304
|
+
nwKeyWords.ENTITY_KEY: "Ctrl+K, E",
|
305
|
+
nwKeyWords.CUSTOM_KEY: "Ctrl+K, X",
|
306
|
+
nwKeyWords.STORY_KEY: "Ctrl+K, N",
|
307
|
+
nwKeyWords.MENTION_KEY: "Ctrl+K, M",
|
225
308
|
}
|
226
309
|
OUTLINE_COLS = {
|
227
|
-
nwOutline.TITLE:
|
228
|
-
nwOutline.LEVEL:
|
229
|
-
nwOutline.LABEL:
|
230
|
-
nwOutline.LINE:
|
231
|
-
nwOutline.
|
232
|
-
nwOutline.
|
233
|
-
nwOutline.
|
234
|
-
nwOutline.
|
235
|
-
nwOutline.
|
236
|
-
nwOutline.
|
237
|
-
nwOutline.
|
238
|
-
nwOutline.
|
239
|
-
nwOutline.
|
240
|
-
nwOutline.
|
241
|
-
nwOutline.
|
242
|
-
nwOutline.
|
243
|
-
nwOutline.
|
310
|
+
nwOutline.TITLE: QT_TRANSLATE_NOOP("Constant", "Title"),
|
311
|
+
nwOutline.LEVEL: QT_TRANSLATE_NOOP("Constant", "Level"),
|
312
|
+
nwOutline.LABEL: QT_TRANSLATE_NOOP("Constant", "Document"),
|
313
|
+
nwOutline.LINE: QT_TRANSLATE_NOOP("Constant", "Line"),
|
314
|
+
nwOutline.STATUS: QT_TRANSLATE_NOOP("Constant", "Status"),
|
315
|
+
nwOutline.CCOUNT: QT_TRANSLATE_NOOP("Constant", "Chars"),
|
316
|
+
nwOutline.WCOUNT: QT_TRANSLATE_NOOP("Constant", "Words"),
|
317
|
+
nwOutline.PCOUNT: QT_TRANSLATE_NOOP("Constant", "Pars"),
|
318
|
+
nwOutline.POV: QT_TRANSLATE_NOOP("Constant", "POV"),
|
319
|
+
nwOutline.FOCUS: QT_TRANSLATE_NOOP("Constant", "Focus"),
|
320
|
+
nwOutline.CHAR: KEY_NAME[nwKeyWords.CHAR_KEY],
|
321
|
+
nwOutline.PLOT: KEY_NAME[nwKeyWords.PLOT_KEY],
|
322
|
+
nwOutline.WORLD: KEY_NAME[nwKeyWords.WORLD_KEY],
|
323
|
+
nwOutline.TIME: KEY_NAME[nwKeyWords.TIME_KEY],
|
324
|
+
nwOutline.OBJECT: KEY_NAME[nwKeyWords.OBJECT_KEY],
|
325
|
+
nwOutline.ENTITY: KEY_NAME[nwKeyWords.ENTITY_KEY],
|
326
|
+
nwOutline.CUSTOM: KEY_NAME[nwKeyWords.CUSTOM_KEY],
|
327
|
+
nwOutline.STORY: KEY_NAME[nwKeyWords.STORY_KEY],
|
328
|
+
nwOutline.MENTION: KEY_NAME[nwKeyWords.MENTION_KEY],
|
329
|
+
nwOutline.SYNOP: QT_TRANSLATE_NOOP("Constant", "Synopsis"),
|
330
|
+
}
|
331
|
+
STATS_NAME = {
|
332
|
+
nwStats.CHARS_ALL: QT_TRANSLATE_NOOP("Constant", "Characters"),
|
333
|
+
nwStats.CHARS_TEXT: QT_TRANSLATE_NOOP("Constant", "Characters in Text"),
|
334
|
+
nwStats.CHARS_TITLE: QT_TRANSLATE_NOOP("Constant", "Characters in Headings"),
|
335
|
+
nwStats.PARAGRAPHS: QT_TRANSLATE_NOOP("Constant", "Paragraphs"),
|
336
|
+
nwStats.TITLES: QT_TRANSLATE_NOOP("Constant", "Headings"),
|
337
|
+
nwStats.WCHARS_ALL: QT_TRANSLATE_NOOP("Constant", "Characters, No Spaces"),
|
338
|
+
nwStats.WCHARS_TEXT: QT_TRANSLATE_NOOP("Constant", "Characters in Text, No Spaces"),
|
339
|
+
nwStats.WCHARS_TITLE: QT_TRANSLATE_NOOP("Constant", "Characters in Headings, No Spaces"),
|
340
|
+
nwStats.WORDS_ALL: QT_TRANSLATE_NOOP("Constant", "Words"),
|
341
|
+
nwStats.WORDS_TEXT: QT_TRANSLATE_NOOP("Constant", "Words in Text"),
|
342
|
+
nwStats.WORDS_TITLE: QT_TRANSLATE_NOOP("Constant", "Words in Headings"),
|
244
343
|
}
|
245
344
|
BUILD_FMT = {
|
246
345
|
nwBuildFmt.ODT: QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)"),
|
247
346
|
nwBuildFmt.FODT: QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)"),
|
248
|
-
nwBuildFmt.
|
347
|
+
nwBuildFmt.DOCX: QT_TRANSLATE_NOOP("Constant", "Microsoft Word Document (.docx)"),
|
348
|
+
nwBuildFmt.HTML: QT_TRANSLATE_NOOP("Constant", "HTML 5 (.html)"),
|
249
349
|
nwBuildFmt.NWD: QT_TRANSLATE_NOOP("Constant", "novelWriter Markup (.txt)"),
|
250
350
|
nwBuildFmt.STD_MD: QT_TRANSLATE_NOOP("Constant", "Standard Markdown (.md)"),
|
251
351
|
nwBuildFmt.EXT_MD: QT_TRANSLATE_NOOP("Constant", "Extended Markdown (.md)"),
|
252
|
-
nwBuildFmt.
|
352
|
+
nwBuildFmt.PDF: QT_TRANSLATE_NOOP("Constant", "Portable Document Format (.pdf)"),
|
353
|
+
nwBuildFmt.J_HTML: QT_TRANSLATE_NOOP("Constant", "JSON + HTML 5 (.json)"),
|
253
354
|
nwBuildFmt.J_NWD: QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter Markup (.json)"),
|
254
355
|
}
|
255
356
|
BUILD_EXT = {
|
256
357
|
nwBuildFmt.ODT: ".odt",
|
257
358
|
nwBuildFmt.FODT: ".fodt",
|
359
|
+
nwBuildFmt.DOCX: ".docx",
|
258
360
|
nwBuildFmt.HTML: ".html",
|
259
361
|
nwBuildFmt.NWD: ".txt",
|
260
362
|
nwBuildFmt.STD_MD: ".md",
|
261
363
|
nwBuildFmt.EXT_MD: ".md",
|
364
|
+
nwBuildFmt.PDF: ".pdf",
|
262
365
|
nwBuildFmt.J_HTML: ".json",
|
263
366
|
nwBuildFmt.J_NWD: ".json",
|
264
367
|
}
|
@@ -343,11 +446,11 @@ class nwHeadFmt:
|
|
343
446
|
CHAR_POV, CHAR_FOCUS
|
344
447
|
]
|
345
448
|
|
346
|
-
#
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
449
|
+
# Document Page Header
|
450
|
+
DOC_PROJECT = "{Project}"
|
451
|
+
DOC_AUTHOR = "{Author}"
|
452
|
+
DOC_PAGE = "{Page}"
|
453
|
+
DOC_AUTO = "{Project} / {Author} / {Page}"
|
351
454
|
|
352
455
|
|
353
456
|
class nwQuotes:
|
@@ -449,6 +552,11 @@ class nwUnicode:
|
|
449
552
|
U_LTRI = "\u25c0" # Left-pointing triangle
|
450
553
|
U_LTRIS = "\u25c2" # Left-pointing triangle, small
|
451
554
|
|
555
|
+
# Special
|
556
|
+
U_UNKN = "\ufffd" # Unknown character
|
557
|
+
U_NAC1 = "\ufffe" # Not a character
|
558
|
+
U_NAC2 = "\uffff" # Not a character
|
559
|
+
|
452
560
|
# HTML Equivalents
|
453
561
|
# ================
|
454
562
|
|