novelWriter 2.6b1__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.6b1.dist-info → novelWriter-2.6b2.dist-info}/METADATA +3 -3
- {novelWriter-2.6b1.dist-info → novelWriter-2.6b2.dist-info}/RECORD +68 -52
- {novelWriter-2.6b1.dist-info → novelWriter-2.6b2.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +49 -10
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_ru_RU.qm +0 -0
- novelwriter/assets/i18n/project_de_DE.json +2 -2
- novelwriter/assets/i18n/project_ru_RU.json +11 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +7 -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 +7 -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/assets/text/credits_en.htm +1 -0
- novelwriter/common.py +37 -2
- novelwriter/config.py +15 -12
- novelwriter/constants.py +24 -9
- novelwriter/core/coretools.py +111 -125
- novelwriter/core/docbuild.py +3 -2
- novelwriter/core/index.py +9 -19
- novelwriter/core/item.py +39 -6
- novelwriter/core/itemmodel.py +518 -0
- novelwriter/core/project.py +67 -89
- novelwriter/core/status.py +7 -5
- novelwriter/core/tree.py +268 -287
- novelwriter/dialogs/docmerge.py +7 -17
- novelwriter/dialogs/preferences.py +3 -3
- novelwriter/dialogs/projectsettings.py +2 -2
- novelwriter/enum.py +7 -0
- novelwriter/extensions/configlayout.py +6 -4
- novelwriter/formats/todocx.py +34 -38
- novelwriter/formats/tohtml.py +14 -15
- novelwriter/formats/tokenizer.py +21 -17
- novelwriter/formats/toodt.py +53 -124
- novelwriter/formats/toqdoc.py +92 -44
- novelwriter/gui/doceditor.py +230 -219
- novelwriter/gui/docviewer.py +38 -9
- novelwriter/gui/docviewerpanel.py +14 -22
- novelwriter/gui/itemdetails.py +17 -24
- novelwriter/gui/mainmenu.py +13 -8
- novelwriter/gui/noveltree.py +12 -12
- novelwriter/gui/outline.py +10 -11
- novelwriter/gui/projtree.py +548 -1202
- novelwriter/gui/search.py +9 -10
- novelwriter/gui/theme.py +7 -3
- novelwriter/guimain.py +59 -43
- novelwriter/shared.py +52 -23
- novelwriter/text/patterns.py +17 -5
- novelwriter/tools/manusbuild.py +13 -11
- novelwriter/tools/manussettings.py +42 -52
- novelwriter/types.py +7 -1
- {novelWriter-2.6b1.dist-info → novelWriter-2.6b2.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.6b1.dist-info → novelWriter-2.6b2.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.6b1.dist-info → novelWriter-2.6b2.dist-info}/top_level.txt +0 -0
@@ -35,6 +35,7 @@ translators for the languages currently available:</p>
|
|
35
35
|
<li><b>Norwegian:</b> Veronica Berglyd Olsen (vkbo)</li>
|
36
36
|
<li><b>Polish:</b> Anna Maria Polak (Nauthiz)</li>
|
37
37
|
<li><b>Portuguese:</b> Bruno Meneguello (bkmeneguello)</li>
|
38
|
+
<li><b>Russian:</b> Konstantin Tan (k1kimosha)</li>
|
38
39
|
<li><b>Simplified Chinese:</b> Qianzhi Long (longqzh)</li>
|
39
40
|
</ul>
|
40
41
|
|
novelwriter/common.py
CHANGED
@@ -37,8 +37,8 @@ from typing import TYPE_CHECKING, Any, Literal, TypeVar
|
|
37
37
|
from urllib.parse import urljoin
|
38
38
|
from urllib.request import pathname2url
|
39
39
|
|
40
|
-
from PyQt5.QtCore import QCoreApplication, QUrl
|
41
|
-
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
|
42
42
|
|
43
43
|
from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
|
44
44
|
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
@@ -434,6 +434,30 @@ def describeFont(font: QFont) -> str:
|
|
434
434
|
return "Error"
|
435
435
|
|
436
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
|
+
|
437
461
|
def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable:
|
438
462
|
"""A replacement for Python lambdas that works for Qt slots."""
|
439
463
|
def wrapper(*a_: Any) -> None:
|
@@ -441,6 +465,17 @@ def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable:
|
|
441
465
|
return wrapper
|
442
466
|
|
443
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
|
+
|
444
479
|
##
|
445
480
|
# Encoder Functions
|
446
481
|
##
|
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
|
|
@@ -369,10 +372,11 @@ class Config:
|
|
369
372
|
def setGuiFont(self, value: QFont | str | None) -> None:
|
370
373
|
"""Update the GUI's font style from settings."""
|
371
374
|
if isinstance(value, QFont):
|
372
|
-
self.guiFont = value
|
375
|
+
self.guiFont = fontMatcher(value)
|
373
376
|
elif value and isinstance(value, str):
|
374
|
-
|
375
|
-
|
377
|
+
font = QFont()
|
378
|
+
font.fromString(value)
|
379
|
+
self.guiFont = fontMatcher(font)
|
376
380
|
else:
|
377
381
|
font = QFont()
|
378
382
|
fontDB = QFontDatabase()
|
@@ -382,11 +386,9 @@ class Config:
|
|
382
386
|
font.setPointSize(10)
|
383
387
|
else:
|
384
388
|
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
|
385
|
-
self.guiFont = font
|
389
|
+
self.guiFont = fontMatcher(font)
|
386
390
|
logger.debug("GUI font set to: %s", describeFont(font))
|
387
|
-
|
388
391
|
QApplication.setFont(self.guiFont)
|
389
|
-
|
390
392
|
return
|
391
393
|
|
392
394
|
def setTextFont(self, value: QFont | str | None) -> None:
|
@@ -394,10 +396,11 @@ class Config:
|
|
394
396
|
set to default font.
|
395
397
|
"""
|
396
398
|
if isinstance(value, QFont):
|
397
|
-
self.textFont = value
|
399
|
+
self.textFont = fontMatcher(value)
|
398
400
|
elif value and isinstance(value, str):
|
399
|
-
|
400
|
-
|
401
|
+
font = QFont()
|
402
|
+
font.fromString(value)
|
403
|
+
self.textFont = fontMatcher(font)
|
401
404
|
else:
|
402
405
|
fontDB = QFontDatabase()
|
403
406
|
fontFam = fontDB.families()
|
@@ -411,8 +414,8 @@ class Config:
|
|
411
414
|
font.setPointSize(12)
|
412
415
|
else:
|
413
416
|
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
|
414
|
-
self.textFont = font
|
415
|
-
logger.debug("Text font set to: %s", describeFont(
|
417
|
+
self.textFont = fontMatcher(font)
|
418
|
+
logger.debug("Text font set to: %s", describeFont(self.textFont))
|
416
419
|
return
|
417
420
|
|
418
421
|
##
|
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
|
@@ -105,6 +108,9 @@ class nwStyles:
|
|
105
108
|
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
|
106
109
|
H_SIZES = {0: 2.50, 1: 2.00, 2: 1.75, 3: 1.50, 4: 1.25}
|
107
110
|
|
111
|
+
T_NORMAL = 1.0
|
112
|
+
T_SMALL = 0.8
|
113
|
+
|
108
114
|
T_LABEL = {
|
109
115
|
"H0": QT_TRANSLATE_NOOP("Constant", "Title"),
|
110
116
|
"H1": QT_TRANSLATE_NOOP("Constant", "Heading 1 (Partition)"),
|
@@ -115,15 +121,15 @@ class nwStyles:
|
|
115
121
|
"SP": QT_TRANSLATE_NOOP("Constant", "Scene Separator"),
|
116
122
|
}
|
117
123
|
T_MARGIN = {
|
118
|
-
"H0": (1.
|
119
|
-
"H1": (1.
|
120
|
-
"H2": (1.
|
121
|
-
"H3": (1.
|
122
|
-
"H4": (1.
|
123
|
-
"TT": (0.00, 0.
|
124
|
-
"SP": (1.
|
125
|
-
"MT": (0.00, 0.
|
126
|
-
"FT": (1.
|
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)
|
127
133
|
}
|
128
134
|
|
129
135
|
|
@@ -268,6 +274,10 @@ class nwLabels:
|
|
268
274
|
"doc_h4": QT_TRANSLATE_NOOP("Constant", "Novel Section"),
|
269
275
|
"note": QT_TRANSLATE_NOOP("Constant", "Project Note"),
|
270
276
|
}
|
277
|
+
ACTIVE_NAME = {
|
278
|
+
"checked": QT_TRANSLATE_NOOP("Constant", "Active"),
|
279
|
+
"unchecked": QT_TRANSLATE_NOOP("Constant", "Inactive"),
|
280
|
+
}
|
271
281
|
KEY_NAME = {
|
272
282
|
nwKeyWords.TAG_KEY: QT_TRANSLATE_NOOP("Constant", "Tag"),
|
273
283
|
nwKeyWords.POV_KEY: QT_TRANSLATE_NOOP("Constant", "Point of View"),
|
@@ -542,6 +552,11 @@ class nwUnicode:
|
|
542
552
|
U_LTRI = "\u25c0" # Left-pointing triangle
|
543
553
|
U_LTRIS = "\u25c2" # Left-pointing triangle, small
|
544
554
|
|
555
|
+
# Special
|
556
|
+
U_UNKN = "\ufffd" # Unknown character
|
557
|
+
U_NAC1 = "\ufffe" # Not a character
|
558
|
+
U_NAC2 = "\uffff" # Not a character
|
559
|
+
|
545
560
|
# HTML Equivalents
|
546
561
|
# ================
|
547
562
|
|
novelwriter/core/coretools.py
CHANGED
@@ -56,10 +56,17 @@ class DocMerger:
|
|
56
56
|
def __init__(self, project: NWProject) -> None:
|
57
57
|
self._project = project
|
58
58
|
self._error = ""
|
59
|
-
self.
|
60
|
-
self.
|
59
|
+
self._target = None
|
60
|
+
self._text = []
|
61
61
|
return
|
62
62
|
|
63
|
+
@property
|
64
|
+
def targetHandle(self) -> str | None:
|
65
|
+
"""Get the handle of the target document."""
|
66
|
+
if self._target:
|
67
|
+
return self._target.itemHandle
|
68
|
+
return None
|
69
|
+
|
63
70
|
##
|
64
71
|
# Methods
|
65
72
|
##
|
@@ -72,63 +79,56 @@ class DocMerger:
|
|
72
79
|
"""Set the target document for the merging. Calling this
|
73
80
|
function resets the class.
|
74
81
|
"""
|
75
|
-
self.
|
76
|
-
self.
|
82
|
+
self._target = self._project.tree[tHandle]
|
83
|
+
self._text = []
|
77
84
|
return
|
78
85
|
|
79
|
-
def newTargetDoc(self,
|
86
|
+
def newTargetDoc(self, sHandle: str, label: str) -> None:
|
80
87
|
"""Create a brand new target document based on a source handle
|
81
88
|
and a new doc label. Calling this function resets the class.
|
82
89
|
"""
|
83
|
-
|
84
|
-
if
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
self._targetDoc = newHandle
|
95
|
-
self._targetText = []
|
96
|
-
|
97
|
-
return newHandle
|
90
|
+
sItem = self._project.tree[sHandle]
|
91
|
+
if sItem and sItem.itemParent:
|
92
|
+
tHandle = self._project.newFile(label, sItem.itemParent)
|
93
|
+
if nwItem := self._project.tree[tHandle]:
|
94
|
+
nwItem.setLayout(sItem.itemLayout)
|
95
|
+
nwItem.setStatus(sItem.itemStatus)
|
96
|
+
nwItem.setImport(sItem.itemImport)
|
97
|
+
nwItem.notifyToRefresh()
|
98
|
+
self._target = nwItem
|
99
|
+
self._text = []
|
100
|
+
return
|
98
101
|
|
99
|
-
def appendText(self,
|
102
|
+
def appendText(self, sHandle: str, addComment: bool, cmtPrefix: str) -> None:
|
100
103
|
"""Append text from an existing document to the text buffer."""
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n"
|
110
|
-
docText = cmtLine + docText
|
111
|
-
|
112
|
-
self._targetText.append(docText)
|
113
|
-
|
114
|
-
return True
|
104
|
+
if item := self._project.tree[sHandle]:
|
105
|
+
text = self._project.storage.getDocumentText(sHandle).rstrip("\n")
|
106
|
+
if addComment:
|
107
|
+
info = item.describeMe()
|
108
|
+
status, _ = item.getImportStatus()
|
109
|
+
text = f"% {cmtPrefix} {info}: {item.itemName} [{status}]\n\n{text}"
|
110
|
+
self._text.append(text)
|
111
|
+
return
|
115
112
|
|
116
113
|
def writeTargetDoc(self) -> bool:
|
117
114
|
"""Write the accumulated text into the designated target
|
118
115
|
document, appending any existing text.
|
119
116
|
"""
|
120
|
-
if self.
|
121
|
-
|
117
|
+
if self._target:
|
118
|
+
outDoc = self._project.storage.getDocument(self._target.itemHandle)
|
119
|
+
if text := (outDoc.readDocument() or "").rstrip("\n"):
|
120
|
+
self._text.insert(0, text)
|
122
121
|
|
123
|
-
|
124
|
-
|
125
|
-
|
122
|
+
status = outDoc.writeDocument("\n\n".join(self._text) + "\n\n")
|
123
|
+
if not status:
|
124
|
+
self._error = outDoc.getError()
|
126
125
|
|
127
|
-
|
128
|
-
|
129
|
-
self._error = outDoc.getError()
|
126
|
+
self._project.index.reIndexHandle(self._target.itemHandle)
|
127
|
+
self._target.notifyToRefresh()
|
130
128
|
|
131
|
-
|
129
|
+
return status
|
130
|
+
|
131
|
+
return False
|
132
132
|
|
133
133
|
|
134
134
|
class DocSplitter:
|
@@ -172,23 +172,19 @@ class DocSplitter:
|
|
172
172
|
self._inFolder = False
|
173
173
|
return
|
174
174
|
|
175
|
-
def newParentFolder(self, pHandle: str, folderLabel: str) ->
|
175
|
+
def newParentFolder(self, pHandle: str, folderLabel: str) -> None:
|
176
176
|
"""Create a new folder that will be the top level parent item
|
177
177
|
for the new documents.
|
178
178
|
"""
|
179
|
-
if self._srcItem
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
self._parHandle = newHandle
|
189
|
-
self._inFolder = True
|
190
|
-
|
191
|
-
return newHandle
|
179
|
+
if self._srcItem:
|
180
|
+
nHandle = self._project.newFolder(folderLabel, pHandle)
|
181
|
+
if nwItem := self._project.tree[nHandle]:
|
182
|
+
nwItem.setStatus(self._srcItem.itemStatus)
|
183
|
+
nwItem.setImport(self._srcItem.itemImport)
|
184
|
+
nwItem.notifyToRefresh()
|
185
|
+
self._parHandle = nHandle
|
186
|
+
self._inFolder = True
|
187
|
+
return
|
192
188
|
|
193
189
|
def splitDocument(self, splitData: list, splitText: list[str]) -> None:
|
194
190
|
"""Loop through the split data record and perform the split job
|
@@ -202,58 +198,50 @@ class DocSplitter:
|
|
202
198
|
self._rawData.insert(0, (chunk, hLevel, hLabel))
|
203
199
|
return
|
204
200
|
|
205
|
-
def writeDocuments(self, docHierarchy: bool) -> Iterable[
|
201
|
+
def writeDocuments(self, docHierarchy: bool) -> Iterable[bool]:
|
206
202
|
"""An iterator that will write each document in the buffer, and
|
207
203
|
return its new handle, parent handle, and sibling handle.
|
208
204
|
"""
|
209
|
-
if self._srcHandle
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
self._error = outDoc.getError()
|
250
|
-
|
251
|
-
yield status, dHandle, nHandle
|
252
|
-
|
253
|
-
hHandle[hLevel] = dHandle
|
254
|
-
nHandle = dHandle
|
255
|
-
pLevel = hLevel
|
256
|
-
|
205
|
+
if self._srcHandle and self._srcItem and self._parHandle:
|
206
|
+
pHandle = self._parHandle
|
207
|
+
hHandle = [self._parHandle, None, None, None, None]
|
208
|
+
pLevel = 0
|
209
|
+
for docText, hLevel, docLabel in self._rawData:
|
210
|
+
|
211
|
+
hLevel = minmax(hLevel, 1, 4)
|
212
|
+
if pLevel == 0:
|
213
|
+
pLevel = hLevel
|
214
|
+
|
215
|
+
if docHierarchy:
|
216
|
+
if hLevel == 1:
|
217
|
+
pHandle = self._parHandle
|
218
|
+
elif hLevel == 2:
|
219
|
+
pHandle = hHandle[1] or hHandle[0]
|
220
|
+
elif hLevel == 3:
|
221
|
+
pHandle = hHandle[2] or hHandle[1] or hHandle[0]
|
222
|
+
elif hLevel == 4:
|
223
|
+
pHandle = hHandle[3] or hHandle[2] or hHandle[1] or hHandle[0]
|
224
|
+
|
225
|
+
if (
|
226
|
+
(dHandle := self._project.newFile(docLabel, pHandle))
|
227
|
+
and (nwItem := self._project.tree[dHandle])
|
228
|
+
):
|
229
|
+
hHandle[hLevel] = dHandle
|
230
|
+
nwItem.setStatus(self._srcItem.itemStatus)
|
231
|
+
nwItem.setImport(self._srcItem.itemImport)
|
232
|
+
|
233
|
+
outDoc = self._project.storage.getDocument(dHandle)
|
234
|
+
status = outDoc.writeDocument("\n".join(docText))
|
235
|
+
if not status:
|
236
|
+
self._error = outDoc.getError()
|
237
|
+
|
238
|
+
self._project.index.reIndexHandle(dHandle)
|
239
|
+
nwItem.notifyToRefresh()
|
240
|
+
|
241
|
+
yield status
|
242
|
+
|
243
|
+
hHandle[hLevel] = dHandle
|
244
|
+
pLevel = hLevel
|
257
245
|
return
|
258
246
|
|
259
247
|
|
@@ -270,29 +258,27 @@ class DocDuplicator:
|
|
270
258
|
# Methods
|
271
259
|
##
|
272
260
|
|
273
|
-
def duplicate(self, items: list[str]) ->
|
261
|
+
def duplicate(self, items: list[str]) -> list[str]:
|
274
262
|
"""Run through a list of items, duplicate them, and copy the
|
275
263
|
text content if they are documents.
|
276
264
|
"""
|
265
|
+
result = []
|
266
|
+
after = True
|
277
267
|
if items:
|
278
|
-
nHandle = items[0]
|
279
268
|
hMap: dict[str, str | None] = {t: None for t in items}
|
280
269
|
for tHandle in items:
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
yield newItem.itemHandle, nHandle
|
294
|
-
nHandle = None
|
295
|
-
return
|
270
|
+
if oldItem := self._project.tree[tHandle]:
|
271
|
+
pHandle = hMap.get(oldItem.itemParent or "") or oldItem.itemParent
|
272
|
+
if newItem := self._project.tree.duplicate(tHandle, pHandle, after):
|
273
|
+
hMap[tHandle] = newItem.itemHandle
|
274
|
+
if newItem.isFileType():
|
275
|
+
self._project.copyFileContent(newItem.itemHandle, tHandle)
|
276
|
+
newItem.notifyToRefresh()
|
277
|
+
result.append(newItem.itemHandle)
|
278
|
+
after = False
|
279
|
+
else:
|
280
|
+
break
|
281
|
+
return result
|
296
282
|
|
297
283
|
|
298
284
|
class DocSearch:
|
@@ -523,7 +509,7 @@ class ProjectBuilder:
|
|
523
509
|
|
524
510
|
# Also add the archive and trash folders
|
525
511
|
project.newRoot(nwItemClass.ARCHIVE)
|
526
|
-
project.
|
512
|
+
project.tree.trash # Triggers the creation of Trash
|
527
513
|
|
528
514
|
project.saveProject()
|
529
515
|
project.closeProject()
|
novelwriter/core/docbuild.py
CHANGED
@@ -176,8 +176,9 @@ class NWBuildDocument:
|
|
176
176
|
|
177
177
|
elif bFormat == nwBuildFmt.PDF:
|
178
178
|
makeObj = ToQTextDocument(self._project)
|
179
|
+
makeObj.disableAnchors()
|
179
180
|
filtered = self._setupBuild(makeObj)
|
180
|
-
makeObj.initDocument()
|
181
|
+
makeObj.initDocument(pdf=True)
|
181
182
|
yield from self._iterBuild(makeObj, filtered)
|
182
183
|
makeObj.closeDocument()
|
183
184
|
|
@@ -217,7 +218,7 @@ class NWBuildDocument:
|
|
217
218
|
textFont = QFont(CONFIG.textFont)
|
218
219
|
textFont.fromString(self._build.getStr("format.textFont"))
|
219
220
|
|
220
|
-
bldObj.
|
221
|
+
bldObj.setTextFont(textFont)
|
221
222
|
bldObj.setLanguage(self._project.data.language)
|
222
223
|
|
223
224
|
bldObj.setPartitionFormat(
|
novelwriter/core/index.py
CHANGED
@@ -116,24 +116,24 @@ class NWIndex:
|
|
116
116
|
# Public Methods
|
117
117
|
##
|
118
118
|
|
119
|
-
def
|
119
|
+
def clear(self) -> None:
|
120
120
|
"""Clear the index dictionaries and time stamps."""
|
121
121
|
self._tagsIndex.clear()
|
122
122
|
self._itemIndex.clear()
|
123
123
|
self._indexChange = 0.0
|
124
124
|
self._rootChange = {}
|
125
|
-
SHARED.
|
125
|
+
SHARED.emitIndexCleared(self._project)
|
126
126
|
return
|
127
127
|
|
128
|
-
def
|
128
|
+
def rebuild(self) -> None:
|
129
129
|
"""Rebuild the entire index from scratch."""
|
130
|
-
self.
|
130
|
+
self.clear()
|
131
131
|
for nwItem in self._project.tree:
|
132
132
|
if nwItem.isFileType():
|
133
133
|
text = self._project.storage.getDocumentText(nwItem.itemHandle)
|
134
134
|
self.scanText(nwItem.itemHandle, text, blockSignal=True)
|
135
135
|
self._indexBroken = False
|
136
|
-
SHARED.
|
136
|
+
SHARED.emitIndexAvailable(self._project)
|
137
137
|
return
|
138
138
|
|
139
139
|
def deleteHandle(self, tHandle: str) -> None:
|
@@ -143,10 +143,7 @@ class NWIndex:
|
|
143
143
|
for tTag in delTags:
|
144
144
|
del self._tagsIndex[tTag]
|
145
145
|
del self._itemIndex[tHandle]
|
146
|
-
SHARED.
|
147
|
-
"event": "updateTags",
|
148
|
-
"deleted": delTags,
|
149
|
-
})
|
146
|
+
SHARED.emitIndexChangedTags(self._project, [], delTags)
|
150
147
|
return
|
151
148
|
|
152
149
|
def reIndexHandle(self, tHandle: str | None) -> None:
|
@@ -212,7 +209,7 @@ class NWIndex:
|
|
212
209
|
self.reIndexHandle(fHandle)
|
213
210
|
|
214
211
|
self._indexChange = time()
|
215
|
-
SHARED.
|
212
|
+
SHARED.emitIndexAvailable(self._project)
|
216
213
|
|
217
214
|
logger.debug("Index loaded in %.3f ms", (time() - tStart)*1000)
|
218
215
|
|
@@ -296,10 +293,7 @@ class NWIndex:
|
|
296
293
|
self._indexChange = nowTime
|
297
294
|
self._rootChange[tItem.itemRoot] = nowTime
|
298
295
|
if not blockSignal:
|
299
|
-
|
300
|
-
"event": "scanText",
|
301
|
-
"handle": tHandle,
|
302
|
-
})
|
296
|
+
tItem.notifyToRefresh()
|
303
297
|
|
304
298
|
return True
|
305
299
|
|
@@ -370,11 +364,7 @@ class NWIndex:
|
|
370
364
|
del self._tagsIndex[tTag]
|
371
365
|
deleted.append(tTag)
|
372
366
|
if updated or deleted:
|
373
|
-
SHARED.
|
374
|
-
"event": "updateTags",
|
375
|
-
"updated": updated,
|
376
|
-
"deleted": deleted,
|
377
|
-
})
|
367
|
+
SHARED.emitIndexChangedTags(self._project, updated, deleted)
|
378
368
|
|
379
369
|
return
|
380
370
|
|