novelWriter 2.3.1__py3-none-any.whl → 2.4b1__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.3.1.dist-info → novelWriter-2.4b1.dist-info}/METADATA +1 -1
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/RECORD +81 -70
- novelwriter/__init__.py +5 -5
- novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_dark/typ_arrow-down.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_arrow-right.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_light/icons.conf +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_light/typ_arrow-down.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_arrow-right.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +25 -23
- novelwriter/common.py +1 -1
- novelwriter/config.py +35 -12
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +60 -40
- novelwriter/core/coretools.py +98 -13
- novelwriter/core/docbuild.py +74 -7
- novelwriter/core/document.py +24 -3
- novelwriter/core/index.py +31 -112
- novelwriter/core/project.py +10 -15
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/status.py +4 -4
- novelwriter/core/storage.py +8 -2
- novelwriter/core/tohtml.py +22 -25
- novelwriter/core/tokenizer.py +416 -232
- novelwriter/core/tomd.py +17 -8
- novelwriter/core/toodt.py +65 -7
- novelwriter/core/tree.py +8 -8
- novelwriter/dialogs/docsplit.py +7 -8
- novelwriter/dialogs/preferences.py +3 -6
- novelwriter/enum.py +17 -14
- novelwriter/extensions/modified.py +20 -2
- novelwriter/extensions/versioninfo.py +1 -1
- novelwriter/gui/doceditor.py +257 -279
- novelwriter/gui/dochighlight.py +29 -25
- novelwriter/gui/docviewer.py +139 -148
- novelwriter/gui/docviewerpanel.py +4 -24
- novelwriter/gui/editordocument.py +12 -1
- novelwriter/gui/itemdetails.py +6 -6
- novelwriter/gui/mainmenu.py +37 -16
- novelwriter/gui/noveltree.py +11 -19
- novelwriter/gui/outline.py +43 -20
- novelwriter/gui/projtree.py +35 -43
- novelwriter/gui/search.py +316 -0
- novelwriter/gui/sidebar.py +25 -30
- novelwriter/gui/theme.py +59 -6
- novelwriter/guimain.py +176 -173
- novelwriter/shared.py +26 -1
- novelwriter/text/__init__.py +3 -0
- novelwriter/text/counting.py +137 -0
- novelwriter/tools/manuscript.py +344 -55
- novelwriter/tools/manussettings.py +213 -71
- novelwriter/tools/welcome.py +1 -1
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/WHEEL +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/top_level.txt +0 -0
@@ -8,9 +8,9 @@
|
|
8
8
|
<h3>Contributors</h3>
|
9
9
|
|
10
10
|
<p>
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
• <b>Concept:</b> Marian Lückhof (<a href="https://github.com/Number042">@Number042</a>)<br>
|
12
|
+
• <b>Internationalisation:</b> Bruno Meneguello (<a href="https://github.com/bkmeneguello">@bkmeneguello</a>)<br>
|
13
|
+
• <b>Setup and Packaging:</b> Rachel Powers (<a href="https://github.com/Ryex">@Ryex</a>)
|
14
14
|
</p>
|
15
15
|
<p>For other contributions, see the project's
|
16
16
|
<a href="https://github.com/vkbo/novelWriter/graphs/contributors">Contributors</a> page.</p>
|
@@ -24,20 +24,20 @@
|
|
24
24
|
<p>The default language is English (UK) with English (US) as an option. These are the original
|
25
25
|
translators for the languages currently available:</p>
|
26
26
|
<p>
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
27
|
+
• <b>Dutch:</b> Martijn van der Kleijn (<a href="https://github.com/mvdkleijn">@mvdkleijn</a>)<br>
|
28
|
+
• <b>French:</b> Jan Lüdke (<a href="https://github.com/jyhelle">@jyhelle</a>)<br>
|
29
|
+
• <b>German:</b> Myian (<a href="https://github.com/heymyian">@heymyian</a>)<br>
|
30
|
+
• <b>Italian:</b> Riccardo Mangili<br>
|
31
|
+
• <b>Japanese:</b> hebekeg (<a href="https://github.com/hebekeg">@hebekeg</a>)<br>
|
32
|
+
• <b>Latin American Spanish:</b> Tommy Marplatt (<a href="https://github.com/tmarplatt">@tmarplatt</a>)<br>
|
33
|
+
• <b>Norwegian:</b> Veronica Berglyd Olsen (<a href="https://github.com/vkbo">@vkbo</a>)<br>
|
34
|
+
• <b>Portuguese:</b> Bruno Meneguello (<a href="https://github.com/bkmeneguello">@bkmeneguello</a>)<br>
|
35
|
+
• <b>Simplified Chinese:</b> Qianzhi Long (<a href="https://github.com/longqzh">@longqzh</a>)
|
36
36
|
</p>
|
37
37
|
|
38
38
|
<p>Additional larger translation contributions:</p>
|
39
39
|
<p>
|
40
|
-
|
40
|
+
• <b>French:</b> Albert Aribaud (<a href="https://github.com/aaribaud">@aaribaud</a>)
|
41
41
|
</p>
|
42
42
|
|
43
43
|
<p>Translations are managed on <a href="https://crowdin.com/project/novelwriter">Crowdin</a>, and
|
@@ -47,27 +47,29 @@ more contributions are listed on the project's <a href="https://crowdin.com/proj
|
|
47
47
|
|
48
48
|
<p>The following libraries are dependencies of novelWriter:</p>
|
49
49
|
<p>
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
• <a href="https://www.qt.io">Qt5</a> by Qt Company<br>
|
51
|
+
• <a href="https://www.riverbankcomputing.com/software/pyqt">PyQt5</a> by Riverbank Computing<br>
|
52
|
+
• <a href="https://abiword.github.io/enchant">Enchant</a> by Dom Lachowicz<br>
|
53
|
+
• <a href="https://pyenchant.github.io/pyenchant">PyEnchant</a> by Dimitri Merejkowsky
|
54
54
|
</p>
|
55
55
|
|
56
56
|
<h3>Assets</h3>
|
57
57
|
|
58
58
|
<p>Some of the assets bundled with novelWriter were adapted from the following sources:</p>
|
59
59
|
<p>
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
• <a href="https://github.com/stephenhutchings/typicons.font">Typicons</a> icons by Stephen Hutchings (CC BY-SA 4.0)<br>
|
61
|
+
• <a href="https://github.com/chriskempson/base16">Tomorrow</a> syntax themes by Chris Kempson (MIT License)<br>
|
62
|
+
• <a href="https://github.com/sdras/night-owl-vscode-theme">Owl</a> syntax themes by Sarah Drasner (MIT License)<br>
|
63
|
+
• <a href="https://github.com/altercation/solarized">Solarized</a> themes by Ethan Schoonover (MIT License)
|
64
64
|
</p>
|
65
65
|
|
66
66
|
<h3>Fonts</h3>
|
67
67
|
|
68
|
-
<p>The font used for the main novelWriter logo, mimetype and text banners is
|
68
|
+
<p>The font used for the main novelWriter logo, mimetype and text banners is Pridi. Other fonts are
|
69
|
+
used on buttons and icons.</p>
|
69
70
|
<p>
|
70
|
-
|
71
|
+
• Pridi by Cadson Demak (Open Font License, Version 1.1)<br>
|
72
|
+
• Source Sans Pro by Paul D. Hunt (SIL Open Font License)
|
71
73
|
</p>
|
72
74
|
|
73
75
|
<h3>Special Mentions</h3>
|
novelwriter/common.py
CHANGED
@@ -255,7 +255,7 @@ def formatFileFilter(extensions: list[str | tuple[str, str]]) -> str:
|
|
255
255
|
result = []
|
256
256
|
for ext in extensions:
|
257
257
|
if isinstance(ext, str):
|
258
|
-
result.append(f"{trConst(nwLabels.FILE_FILTERS.get(ext))} ({ext})")
|
258
|
+
result.append(f"{trConst(nwLabels.FILE_FILTERS.get(ext, 'ERR'))} ({ext})")
|
259
259
|
elif isinstance(ext, tuple) and len(ext) == 2:
|
260
260
|
result.append(f"{ext[0]} ({ext[1]})")
|
261
261
|
return ";;".join(result)
|
novelwriter/config.py
CHANGED
@@ -30,6 +30,7 @@ import logging
|
|
30
30
|
|
31
31
|
from time import time
|
32
32
|
from pathlib import Path
|
33
|
+
from datetime import datetime
|
33
34
|
|
34
35
|
from PyQt5.QtGui import QFontDatabase
|
35
36
|
from PyQt5.QtCore import (
|
@@ -84,8 +85,11 @@ class Config:
|
|
84
85
|
self._nwLangPath = self._appPath / "assets" / "i18n"
|
85
86
|
self._qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
|
86
87
|
|
87
|
-
|
88
|
-
self._qLocale = QLocale.system() if
|
88
|
+
hasLocale = (self._nwLangPath / f"nw_{QLocale.system().name()}.qm").exists()
|
89
|
+
self._qLocale = QLocale.system() if hasLocale else QLocale("en_GB")
|
90
|
+
self._dLocale = QLocale.system()
|
91
|
+
self._dShortDate = self._dLocale.dateFormat(QLocale.FormatType.ShortFormat)
|
92
|
+
self._dShortDateTime = self._dLocale.dateTimeFormat(QLocale.FormatType.ShortFormat)
|
89
93
|
self._qtTrans = {}
|
90
94
|
|
91
95
|
# PDF Manual
|
@@ -179,17 +183,19 @@ class Config:
|
|
179
183
|
# State
|
180
184
|
self.showViewerPanel = True # The panel for the viewer is visible
|
181
185
|
self.showEditToolBar = False # The document editor toolbar visibility
|
182
|
-
self.useShortcodes = False # Use shortcodes for basic formatting
|
183
186
|
self.viewComments = True # Comments are shown in the viewer
|
184
187
|
self.viewSynopsis = True # Synopsis is shown in the viewer
|
185
188
|
|
186
|
-
# Search
|
187
|
-
self.searchCase
|
188
|
-
self.searchWord
|
189
|
-
self.searchRegEx
|
190
|
-
self.searchLoop
|
191
|
-
self.searchNextFile
|
192
|
-
self.searchMatchCap
|
189
|
+
# Search Box States
|
190
|
+
self.searchCase = False
|
191
|
+
self.searchWord = False
|
192
|
+
self.searchRegEx = False
|
193
|
+
self.searchLoop = False
|
194
|
+
self.searchNextFile = False
|
195
|
+
self.searchMatchCap = False
|
196
|
+
self.searchProjCase = False
|
197
|
+
self.searchProjWord = False
|
198
|
+
self.searchProjRegEx = False
|
193
199
|
|
194
200
|
# System and App Information
|
195
201
|
# ==========================
|
@@ -414,6 +420,14 @@ class Config:
|
|
414
420
|
self._errData = []
|
415
421
|
return message
|
416
422
|
|
423
|
+
def localDate(self, value: datetime) -> str:
|
424
|
+
"""Return a localised date format."""
|
425
|
+
return self._dLocale.toString(value, self._dShortDate)
|
426
|
+
|
427
|
+
def localDateTime(self, value: datetime) -> str:
|
428
|
+
"""Return a localised datetime format."""
|
429
|
+
return self._dLocale.toString(value, self._dShortDateTime)
|
430
|
+
|
417
431
|
def listLanguages(self, lngSet: int) -> list[tuple[str, str]]:
|
418
432
|
"""List localisation files in the i18n folder. The default GUI
|
419
433
|
language is British English (en_GB).
|
@@ -496,6 +510,11 @@ class Config:
|
|
496
510
|
QLocale.setDefault(self._qLocale)
|
497
511
|
self._qtTrans = {}
|
498
512
|
|
513
|
+
hasLocale = (self._nwLangPath / f"nw_{self._qLocale.name()}.qm").exists()
|
514
|
+
self._dLocale = self._qLocale if hasLocale else QLocale.system()
|
515
|
+
self._dShortDate = self._dLocale.dateFormat(QLocale.FormatType.ShortFormat)
|
516
|
+
self._dShortDateTime = self._dLocale.dateTimeFormat(QLocale.FormatType.ShortFormat)
|
517
|
+
|
499
518
|
langList = [
|
500
519
|
(self._qtLangPath, "qtbase"), # Qt 5.x
|
501
520
|
(str(self._nwLangPath), "nw"), # novelWriter
|
@@ -602,7 +621,6 @@ class Config:
|
|
602
621
|
sec = "State"
|
603
622
|
self.showViewerPanel = conf.rdBool(sec, "showviewerpanel", self.showViewerPanel)
|
604
623
|
self.showEditToolBar = conf.rdBool(sec, "showedittoolbar", self.showEditToolBar)
|
605
|
-
self.useShortcodes = conf.rdBool(sec, "useshortcodes", self.useShortcodes)
|
606
624
|
self.viewComments = conf.rdBool(sec, "viewcomments", self.viewComments)
|
607
625
|
self.viewSynopsis = conf.rdBool(sec, "viewsynopsis", self.viewSynopsis)
|
608
626
|
self.searchCase = conf.rdBool(sec, "searchcase", self.searchCase)
|
@@ -611,6 +629,9 @@ class Config:
|
|
611
629
|
self.searchLoop = conf.rdBool(sec, "searchloop", self.searchLoop)
|
612
630
|
self.searchNextFile = conf.rdBool(sec, "searchnextfile", self.searchNextFile)
|
613
631
|
self.searchMatchCap = conf.rdBool(sec, "searchmatchcap", self.searchMatchCap)
|
632
|
+
self.searchProjCase = conf.rdBool(sec, "searchprojcase", self.searchProjCase)
|
633
|
+
self.searchProjWord = conf.rdBool(sec, "searchprojword", self.searchProjWord)
|
634
|
+
self.searchProjRegEx = conf.rdBool(sec, "searchprojregex", self.searchProjRegEx)
|
614
635
|
|
615
636
|
# Check Values
|
616
637
|
# ============
|
@@ -708,7 +729,6 @@ class Config:
|
|
708
729
|
conf["State"] = {
|
709
730
|
"showviewerpanel": str(self.showViewerPanel),
|
710
731
|
"showedittoolbar": str(self.showEditToolBar),
|
711
|
-
"useshortcodes": str(self.useShortcodes),
|
712
732
|
"viewcomments": str(self.viewComments),
|
713
733
|
"viewsynopsis": str(self.viewSynopsis),
|
714
734
|
"searchcase": str(self.searchCase),
|
@@ -717,6 +737,9 @@ class Config:
|
|
717
737
|
"searchloop": str(self.searchLoop),
|
718
738
|
"searchnextfile": str(self.searchNextFile),
|
719
739
|
"searchmatchcap": str(self.searchMatchCap),
|
740
|
+
"searchprojcase": str(self.searchProjCase),
|
741
|
+
"searchprojword": str(self.searchProjWord),
|
742
|
+
"searchprojregex": str(self.searchProjRegEx),
|
720
743
|
}
|
721
744
|
|
722
745
|
# Write config file
|
novelwriter/constants.py
CHANGED
@@ -23,8 +23,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
23
23
|
"""
|
24
24
|
from __future__ import annotations
|
25
25
|
|
26
|
-
import re
|
27
|
-
|
28
26
|
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
|
29
27
|
|
30
28
|
from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
|
@@ -55,6 +53,7 @@ class nwConst:
|
|
55
53
|
|
56
54
|
# Gui Settings
|
57
55
|
STATUS_MSG_TIMEOUT = 15000 # milliseconds
|
56
|
+
MAX_SEARCH_RESULT = 1000
|
58
57
|
|
59
58
|
# Dialogs
|
60
59
|
DLG_FINISHED = 2
|
@@ -67,12 +66,9 @@ class nwRegEx:
|
|
67
66
|
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
|
68
67
|
FMT_EB = r"(?<![\w\\])([\*]{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
|
69
68
|
FMT_ST = r"(?<![\w\\])([~]{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)"
|
70
|
-
FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:i|b|s|u|sup|sub)\])"
|
69
|
+
FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:i|b|s|u|m|sup|sub)\])"
|
71
70
|
FMT_SV = r"(?<!\\)(\[(?i)(?:fn|footnote):)(.+?)(?<!\\)(\])"
|
72
71
|
|
73
|
-
# Pre-Compiled RegEx
|
74
|
-
RX_SC = re.compile(FMT_SC)
|
75
|
-
|
76
72
|
# END Class nwRegEx
|
77
73
|
|
78
74
|
|
@@ -86,6 +82,8 @@ class nwShortcode:
|
|
86
82
|
STRIKE_C = "[/s]"
|
87
83
|
ULINE_O = "[u]"
|
88
84
|
ULINE_C = "[/u]"
|
85
|
+
MARK_O = "[m]"
|
86
|
+
MARK_C = "[/m]"
|
89
87
|
SUP_O = "[sup]"
|
90
88
|
SUP_C = "[/sup]"
|
91
89
|
SUB_O = "[sub]"
|
@@ -408,6 +406,7 @@ class nwUnicode:
|
|
408
406
|
U_THNBSP = "\u202f" # Thin non-breaking space
|
409
407
|
U_ENSP = "\u2002" # Short (en) space
|
410
408
|
U_EMSP = "\u2003" # Long (em) space
|
409
|
+
U_MMSP = "\u205f" # Medium mathematical space
|
411
410
|
U_LSEP = "\u2028" # Line separator
|
412
411
|
U_PSEP = "\u2029" # Paragraph separator
|
413
412
|
|
@@ -49,39 +49,54 @@ logger = logging.getLogger(__name__)
|
|
49
49
|
# (type, default, [min value, max value])
|
50
50
|
|
51
51
|
SETTINGS_TEMPLATE = {
|
52
|
-
"filter.includeNovel":
|
53
|
-
"filter.includeNotes":
|
54
|
-
"filter.includeInactive":
|
55
|
-
"headings.fmtTitle":
|
56
|
-
"headings.fmtChapter":
|
57
|
-
"headings.fmtUnnumbered":
|
58
|
-
"headings.fmtScene":
|
59
|
-
"headings.
|
60
|
-
"headings.
|
61
|
-
"headings.
|
62
|
-
"
|
63
|
-
"
|
64
|
-
"
|
65
|
-
"
|
66
|
-
"
|
67
|
-
"
|
68
|
-
"
|
69
|
-
"
|
70
|
-
"
|
71
|
-
"
|
72
|
-
"
|
73
|
-
"
|
74
|
-
"
|
75
|
-
"
|
76
|
-
"
|
77
|
-
"
|
78
|
-
"
|
79
|
-
"format.
|
80
|
-
"format.
|
81
|
-
"
|
82
|
-
"
|
83
|
-
"
|
84
|
-
"
|
52
|
+
"filter.includeNovel": (bool, True),
|
53
|
+
"filter.includeNotes": (bool, False),
|
54
|
+
"filter.includeInactive": (bool, False),
|
55
|
+
"headings.fmtTitle": (str, nwHeadFmt.TITLE),
|
56
|
+
"headings.fmtChapter": (str, nwHeadFmt.TITLE),
|
57
|
+
"headings.fmtUnnumbered": (str, nwHeadFmt.TITLE),
|
58
|
+
"headings.fmtScene": (str, "* * *"),
|
59
|
+
"headings.fmtHardScene": (str, ""),
|
60
|
+
"headings.fmtSection": (str, ""),
|
61
|
+
"headings.hideTitle": (bool, False),
|
62
|
+
"headings.hideChapter": (bool, False),
|
63
|
+
"headings.hideUnnumbered": (bool, False),
|
64
|
+
"headings.hideScene": (bool, False),
|
65
|
+
"headings.hideHardScene": (bool, False),
|
66
|
+
"headings.hideSection": (bool, True),
|
67
|
+
"headings.centerTitle": (bool, True),
|
68
|
+
"headings.centerChapter": (bool, False),
|
69
|
+
"headings.centerScene": (bool, False),
|
70
|
+
"headings.breakTitle": (bool, True),
|
71
|
+
"headings.breakChapter": (bool, True),
|
72
|
+
"headings.breakScene": (bool, False),
|
73
|
+
"text.includeSynopsis": (bool, False),
|
74
|
+
"text.includeComments": (bool, False),
|
75
|
+
"text.includeKeywords": (bool, False),
|
76
|
+
"text.includeBodyText": (bool, True),
|
77
|
+
"text.ignoredKeywords": (str, ""),
|
78
|
+
"text.addNoteHeadings": (bool, True),
|
79
|
+
"format.textFont": (str, CONFIG.textFont),
|
80
|
+
"format.textSize": (int, 12),
|
81
|
+
"format.lineHeight": (float, 1.15, 0.75, 3.0),
|
82
|
+
"format.justifyText": (bool, False),
|
83
|
+
"format.stripUnicode": (bool, False),
|
84
|
+
"format.replaceTabs": (bool, False),
|
85
|
+
"format.pageUnit": (str, "cm"),
|
86
|
+
"format.pageSize": (str, "A4"),
|
87
|
+
"format.pageWidth": (float, 21.0),
|
88
|
+
"format.pageHeight": (float, 29.7),
|
89
|
+
"format.topMargin": (float, 2.0),
|
90
|
+
"format.bottomMargin": (float, 2.0),
|
91
|
+
"format.leftMargin": (float, 2.0),
|
92
|
+
"format.rightMargin": (float, 2.0),
|
93
|
+
"odt.addColours": (bool, True),
|
94
|
+
"odt.pageHeader": (str, nwHeadFmt.ODT_AUTO),
|
95
|
+
"odt.pageCountOffset": (int, 0),
|
96
|
+
"odt.firstLineIndent": (bool, False),
|
97
|
+
"md.preserveBreaks": (bool, True),
|
98
|
+
"html.addStyles": (bool, True),
|
99
|
+
"html.preserveTabs": (bool, False),
|
85
100
|
}
|
86
101
|
|
87
102
|
SETTINGS_LABELS = {
|
@@ -91,19 +106,19 @@ SETTINGS_LABELS = {
|
|
91
106
|
"filter.includeInactive": QT_TRANSLATE_NOOP("Builds", "Inactive Documents"),
|
92
107
|
|
93
108
|
"headings": QT_TRANSLATE_NOOP("Builds", "Headings"),
|
94
|
-
"headings.fmtTitle": QT_TRANSLATE_NOOP("Builds", "
|
95
|
-
"headings.fmtChapter": QT_TRANSLATE_NOOP("Builds", "Chapter
|
96
|
-
"headings.fmtUnnumbered": QT_TRANSLATE_NOOP("Builds", "Unnumbered
|
97
|
-
"headings.fmtScene": QT_TRANSLATE_NOOP("Builds", "Scene
|
98
|
-
"headings.
|
99
|
-
"headings.
|
100
|
-
"headings.hideSection": QT_TRANSLATE_NOOP("Builds", "Hide Section Headings"),
|
109
|
+
"headings.fmtTitle": QT_TRANSLATE_NOOP("Builds", "Partition Format"),
|
110
|
+
"headings.fmtChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Format"),
|
111
|
+
"headings.fmtUnnumbered": QT_TRANSLATE_NOOP("Builds", "Unnumbered Format"),
|
112
|
+
"headings.fmtScene": QT_TRANSLATE_NOOP("Builds", "Scene Format"),
|
113
|
+
"headings.fmtHardScene": QT_TRANSLATE_NOOP("Builds", "Hard Scene Format"),
|
114
|
+
"headings.fmtSection": QT_TRANSLATE_NOOP("Builds", "Section Format"),
|
101
115
|
|
102
116
|
"text.grpContent": QT_TRANSLATE_NOOP("Builds", "Text Content"),
|
103
117
|
"text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"),
|
104
118
|
"text.includeComments": QT_TRANSLATE_NOOP("Builds", "Include Comments"),
|
105
119
|
"text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Include Keywords"),
|
106
120
|
"text.includeBodyText": QT_TRANSLATE_NOOP("Builds", "Include Body Text"),
|
121
|
+
"text.ignoredKeywords": QT_TRANSLATE_NOOP("Builds", "Ignore These Keywords"),
|
107
122
|
"text.grpInsert": QT_TRANSLATE_NOOP("Builds", "Insert Content"),
|
108
123
|
"text.addNoteHeadings": QT_TRANSLATE_NOOP("Builds", "Add Titles for Notes"),
|
109
124
|
|
@@ -129,9 +144,14 @@ SETTINGS_LABELS = {
|
|
129
144
|
"odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"),
|
130
145
|
"odt.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
|
131
146
|
"odt.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
|
147
|
+
"odt.firstLineIndent": QT_TRANSLATE_NOOP("Builds", "First Line Indent"),
|
148
|
+
|
149
|
+
"md": QT_TRANSLATE_NOOP("Builds", "Markdown (.md)"),
|
150
|
+
"md.preserveBreaks": QT_TRANSLATE_NOOP("Builds", "Preserve Hard Line Breaks"),
|
132
151
|
|
133
152
|
"html": QT_TRANSLATE_NOOP("Builds", "HTML (.html)"),
|
134
153
|
"html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
|
154
|
+
"html.preserveTabs": QT_TRANSLATE_NOOP("Builds", "Preserve Tab Characters"),
|
135
155
|
}
|
136
156
|
|
137
157
|
|
novelwriter/core/coretools.py
CHANGED
@@ -26,19 +26,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
26
26
|
"""
|
27
27
|
from __future__ import annotations
|
28
28
|
|
29
|
-
import shutil
|
30
29
|
import logging
|
30
|
+
import shutil
|
31
31
|
|
32
|
-
from
|
32
|
+
from collections.abc import Iterable
|
33
33
|
from functools import partial
|
34
|
+
from pathlib import Path
|
34
35
|
from zipfile import ZipFile, is_zipfile
|
35
|
-
from collections.abc import Iterable
|
36
36
|
|
37
|
-
from PyQt5.QtCore import QCoreApplication
|
37
|
+
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
38
38
|
|
39
39
|
from novelwriter import CONFIG, SHARED
|
40
40
|
from novelwriter.common import isHandle, minmax, simplified
|
41
|
-
from novelwriter.constants import nwFiles, nwItemClass
|
41
|
+
from novelwriter.constants import nwConst, nwFiles, nwItemClass
|
42
42
|
from novelwriter.core.item import NWItem
|
43
43
|
from novelwriter.core.project import NWProject
|
44
44
|
from novelwriter.core.storage import NWStorageCreate
|
@@ -101,9 +101,7 @@ class DocMerger:
|
|
101
101
|
if srcItem is None:
|
102
102
|
return False
|
103
103
|
|
104
|
-
|
105
|
-
docText = (inDoc.readDocument() or "").rstrip("\n")
|
106
|
-
|
104
|
+
docText = self._project.storage.getDocumentText(srcHandle).rstrip("\n")
|
107
105
|
if addComment:
|
108
106
|
docInfo = srcItem.describeMe()
|
109
107
|
docSt, _ = srcItem.getImportStatus(incIcon=False)
|
@@ -122,9 +120,8 @@ class DocMerger:
|
|
122
120
|
return False
|
123
121
|
|
124
122
|
outDoc = self._project.storage.getDocument(self._targetDoc)
|
125
|
-
|
126
|
-
|
127
|
-
self._targetText.insert(0, docText)
|
123
|
+
if text := (outDoc.readDocument() or "").rstrip("\n"):
|
124
|
+
self._targetText.insert(0, text)
|
128
125
|
|
129
126
|
status = outDoc.writeDocument("\n\n".join(self._targetText) + "\n\n")
|
130
127
|
if not status:
|
@@ -292,11 +289,10 @@ class DocDuplicator:
|
|
292
289
|
newItem.setParent(hMap[newItem.itemParent])
|
293
290
|
self._project.tree.updateItemData(newItem.itemHandle)
|
294
291
|
if newItem.isFileType():
|
295
|
-
oldDoc = self._project.storage.getDocument(tHandle)
|
296
292
|
newDoc = self._project.storage.getDocument(newItem.itemHandle)
|
297
293
|
if newDoc.fileExists():
|
298
294
|
return
|
299
|
-
newDoc.writeDocument(
|
295
|
+
newDoc.writeDocument(self._project.storage.getDocumentText(tHandle))
|
300
296
|
yield newItem.itemHandle, nHandle
|
301
297
|
nHandle = None
|
302
298
|
return
|
@@ -304,6 +300,95 @@ class DocDuplicator:
|
|
304
300
|
# END Class DocDuplicator
|
305
301
|
|
306
302
|
|
303
|
+
class DocSearch:
|
304
|
+
|
305
|
+
def __init__(self) -> None:
|
306
|
+
self._regEx = QRegularExpression()
|
307
|
+
self.setCaseSensitive(False)
|
308
|
+
self._words = False
|
309
|
+
self._escape = True
|
310
|
+
return
|
311
|
+
|
312
|
+
##
|
313
|
+
# Methods
|
314
|
+
##
|
315
|
+
|
316
|
+
def setCaseSensitive(self, state: bool) -> None:
|
317
|
+
"""Set the case sensitive search flag."""
|
318
|
+
opts = QRegularExpression.PatternOption.UseUnicodePropertiesOption
|
319
|
+
if not state:
|
320
|
+
opts |= QRegularExpression.PatternOption.CaseInsensitiveOption
|
321
|
+
self._regEx.setPatternOptions(opts)
|
322
|
+
return
|
323
|
+
|
324
|
+
def setWholeWords(self, state: bool) -> None:
|
325
|
+
"""Set the whole words search flag."""
|
326
|
+
self._words = state
|
327
|
+
return
|
328
|
+
|
329
|
+
def setUserRegEx(self, state: bool) -> None:
|
330
|
+
"""Set the escape flag to the opposite state."""
|
331
|
+
self._escape = not state
|
332
|
+
return
|
333
|
+
|
334
|
+
def iterSearch(
|
335
|
+
self, project: NWProject, search: str
|
336
|
+
) -> Iterable[tuple[NWItem, list[tuple[int, int, str]], bool]]:
|
337
|
+
"""Iteratively search through documents in a project."""
|
338
|
+
self._regEx.setPattern(self._buildPattern(search))
|
339
|
+
logger.debug("Searching with pattern '%s'", self._regEx.pattern())
|
340
|
+
|
341
|
+
num = len(search)
|
342
|
+
storage = project.storage
|
343
|
+
for item in project.tree:
|
344
|
+
if item.isFileType():
|
345
|
+
text = storage.getDocumentText(item.itemHandle)
|
346
|
+
rxItt = self._regEx.globalMatch(text)
|
347
|
+
count = 0
|
348
|
+
capped = False
|
349
|
+
results = []
|
350
|
+
while rxItt.hasNext():
|
351
|
+
rxMatch = rxItt.next()
|
352
|
+
pos = rxMatch.capturedStart()
|
353
|
+
num = rxMatch.capturedLength()
|
354
|
+
context = text[pos:pos+100].partition("\n")[0]
|
355
|
+
if context:
|
356
|
+
results.append((pos, num, context))
|
357
|
+
count += 1
|
358
|
+
if count >= nwConst.MAX_SEARCH_RESULT:
|
359
|
+
capped = True
|
360
|
+
break
|
361
|
+
|
362
|
+
yield item, results, capped
|
363
|
+
|
364
|
+
return
|
365
|
+
|
366
|
+
##
|
367
|
+
# Internal Functions
|
368
|
+
##
|
369
|
+
|
370
|
+
def _buildPattern(self, search: str) -> str:
|
371
|
+
"""Build the search pattern string."""
|
372
|
+
if self._escape:
|
373
|
+
if CONFIG.verQtValue >= 0x050f00:
|
374
|
+
search = QRegularExpression.escape(search)
|
375
|
+
else:
|
376
|
+
# For older Qt versions, we escape manually
|
377
|
+
escaped = ""
|
378
|
+
for c in search:
|
379
|
+
if c.isalnum() or c == "_":
|
380
|
+
escaped += c
|
381
|
+
else:
|
382
|
+
escaped += f"\\{c}"
|
383
|
+
search = escaped
|
384
|
+
if self._words:
|
385
|
+
search = search if search.startswith("\\b") else f"\\b{search}"
|
386
|
+
search = search if search.endswith("\\b") else f"{search}\\b"
|
387
|
+
return search
|
388
|
+
|
389
|
+
# END Class DocSearch
|
390
|
+
|
391
|
+
|
307
392
|
class ProjectBuilder:
|
308
393
|
"""A class to build a new project from a set of user-defined
|
309
394
|
parameter provided by the New Project Wizard.
|