novelWriter 2.2rc1__py3-none-any.whl → 2.3b1__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.2rc1.dist-info → novelWriter-2.3b1.dist-info}/METADATA +1 -1
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/RECORD +141 -129
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +11 -6
- 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_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_de_DE.json +1 -0
- novelwriter/assets/i18n/project_en_US.json +1 -0
- novelwriter/assets/i18n/project_es_419.json +11 -0
- novelwriter/assets/i18n/project_fr_FR.json +11 -0
- novelwriter/assets/i18n/project_it_IT.json +11 -0
- novelwriter/assets/i18n/project_ja_JP.json +2 -1
- novelwriter/assets/i18n/project_nb_NO.json +1 -0
- novelwriter/assets/i18n/project_zh_CN.json +11 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +9 -2
- novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
- novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
- novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
- novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
- novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
- novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
- novelwriter/assets/icons/typicons_dark/typ_document-add-col.svg +8 -0
- novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +9 -2
- novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
- novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
- novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
- novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
- novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
- novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
- novelwriter/assets/icons/typicons_light/typ_document-add-col.svg +8 -0
- novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
- novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
- novelwriter/assets/images/novelwriter-text-light.svg +4 -0
- novelwriter/assets/images/welcome-dark.jpg +0 -0
- novelwriter/assets/images/welcome-light.jpg +0 -0
- 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 +4 -2
- novelwriter/assets/themes/default_dark.conf +2 -2
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +64 -66
- novelwriter/config.py +39 -44
- novelwriter/constants.py +39 -17
- novelwriter/core/buildsettings.py +8 -8
- novelwriter/core/coretools.py +194 -155
- novelwriter/core/docbuild.py +7 -4
- novelwriter/core/document.py +7 -7
- novelwriter/core/index.py +90 -57
- novelwriter/core/item.py +23 -5
- novelwriter/core/options.py +11 -10
- novelwriter/core/project.py +72 -47
- novelwriter/core/projectdata.py +3 -16
- novelwriter/core/projectxml.py +14 -42
- novelwriter/core/sessions.py +4 -3
- novelwriter/core/spellcheck.py +6 -4
- novelwriter/core/status.py +5 -4
- novelwriter/core/storage.py +179 -141
- novelwriter/core/tohtml.py +6 -4
- novelwriter/core/tokenizer.py +74 -46
- novelwriter/core/tomd.py +2 -2
- novelwriter/core/toodt.py +41 -31
- novelwriter/core/tree.py +5 -4
- novelwriter/dialogs/about.py +88 -179
- novelwriter/dialogs/docmerge.py +30 -20
- novelwriter/dialogs/docsplit.py +33 -22
- novelwriter/dialogs/editlabel.py +20 -8
- novelwriter/dialogs/preferences.py +562 -725
- novelwriter/dialogs/{projsettings.py → projectsettings.py} +301 -270
- novelwriter/dialogs/quotes.py +47 -36
- novelwriter/dialogs/wordlist.py +128 -59
- novelwriter/enum.py +25 -22
- novelwriter/error.py +2 -2
- novelwriter/extensions/circularprogress.py +12 -12
- novelwriter/extensions/configlayout.py +185 -146
- novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
- novelwriter/extensions/modified.py +81 -0
- novelwriter/extensions/novelselector.py +27 -13
- novelwriter/extensions/pagedsidebar.py +15 -20
- novelwriter/extensions/simpleprogress.py +8 -9
- novelwriter/extensions/statusled.py +9 -9
- novelwriter/extensions/switch.py +32 -64
- novelwriter/extensions/switchbox.py +2 -7
- novelwriter/extensions/versioninfo.py +153 -0
- novelwriter/gui/doceditor.py +250 -214
- novelwriter/gui/dochighlight.py +66 -94
- novelwriter/gui/docviewer.py +71 -98
- novelwriter/gui/docviewerpanel.py +140 -47
- novelwriter/gui/editordocument.py +3 -3
- novelwriter/gui/itemdetails.py +9 -9
- novelwriter/gui/mainmenu.py +47 -46
- novelwriter/gui/noveltree.py +53 -61
- novelwriter/gui/outline.py +100 -76
- novelwriter/gui/projtree.py +193 -67
- novelwriter/gui/sidebar.py +9 -8
- novelwriter/gui/statusbar.py +49 -7
- novelwriter/gui/theme.py +65 -74
- novelwriter/guimain.py +173 -330
- novelwriter/shared.py +68 -30
- novelwriter/tools/dictionaries.py +7 -8
- novelwriter/tools/lipsum.py +34 -28
- novelwriter/tools/manusbuild.py +3 -4
- novelwriter/tools/manuscript.py +25 -32
- novelwriter/tools/manussettings.py +194 -225
- novelwriter/tools/noveldetails.py +525 -0
- novelwriter/tools/welcome.py +802 -0
- novelwriter/tools/writingstats.py +26 -13
- novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
- novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
- novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
- novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
- novelwriter/assets/images/wizard-back.jpg +0 -0
- novelwriter/assets/text/gplv3_en.htm +0 -641
- novelwriter/assets/text/release_notes.htm +0 -17
- novelwriter/dialogs/projdetails.py +0 -525
- novelwriter/dialogs/projload.py +0 -298
- novelwriter/dialogs/updates.py +0 -182
- novelwriter/extensions/pageddialog.py +0 -130
- novelwriter/tools/projwizard.py +0 -478
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ Created: 2023-02-14 [2.1b1] BuildSettings
|
|
7
7
|
Created: 2023-05-22 [2.1b1] BuildCollection
|
8
8
|
|
9
9
|
This file is a part of novelWriter
|
10
|
-
Copyright 2018–
|
10
|
+
Copyright 2018–2024, Veronica Berglyd Olsen
|
11
11
|
|
12
12
|
This program is free software: you can redistribute it and/or modify
|
13
13
|
it under the terms of the GNU General Public License as published by
|
@@ -29,8 +29,8 @@ import uuid
|
|
29
29
|
import logging
|
30
30
|
|
31
31
|
from enum import Enum
|
32
|
-
from typing import Iterable
|
33
32
|
from pathlib import Path
|
33
|
+
from collections.abc import Iterable
|
34
34
|
|
35
35
|
from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
|
36
36
|
|
@@ -79,6 +79,8 @@ SETTINGS_TEMPLATE = {
|
|
79
79
|
"format.leftMargin": (float, 2.0),
|
80
80
|
"format.rightMargin": (float, 2.0),
|
81
81
|
"odt.addColours": (bool, True),
|
82
|
+
"odt.pageHeader": (str, nwHeadFmt.ODT_AUTO),
|
83
|
+
"odt.pageCountOffset": (int, 0),
|
82
84
|
"html.addStyles": (bool, True),
|
83
85
|
}
|
84
86
|
|
@@ -125,6 +127,8 @@ SETTINGS_LABELS = {
|
|
125
127
|
|
126
128
|
"odt": QT_TRANSLATE_NOOP("Builds", "Open Document (.odt)"),
|
127
129
|
"odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"),
|
130
|
+
"odt.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
|
131
|
+
"odt.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
|
128
132
|
|
129
133
|
"html": QT_TRANSLATE_NOOP("Builds", "HTML (.html)"),
|
130
134
|
"html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
|
@@ -235,16 +239,12 @@ class BuildSettings:
|
|
235
239
|
def getInt(self, key: str) -> int:
|
236
240
|
"""Type safe value access for integers."""
|
237
241
|
value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1])
|
238
|
-
if isinstance(value, (int, float))
|
239
|
-
return int(value)
|
240
|
-
return 0
|
242
|
+
return int(value) if isinstance(value, (int, float)) else 0
|
241
243
|
|
242
244
|
def getFloat(self, key: str) -> float:
|
243
245
|
"""Type safe value access for floats."""
|
244
246
|
value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1])
|
245
|
-
if isinstance(value, (int, float))
|
246
|
-
return float(value)
|
247
|
-
return 0.0
|
247
|
+
return float(value) if isinstance(value, (int, float)) else 0.0
|
248
248
|
|
249
249
|
##
|
250
250
|
# Setters
|
novelwriter/core/coretools.py
CHANGED
@@ -6,9 +6,10 @@ File History:
|
|
6
6
|
Created: 2022-10-02 [2.0rc1] DocMerger
|
7
7
|
Created: 2022-10-11 [2.0rc1] DocSplitter
|
8
8
|
Created: 2022-11-03 [2.0rc2] ProjectBuilder
|
9
|
+
Created: 2023-07-20 [2.1b1] DocDuplicator
|
9
10
|
|
10
11
|
This file is a part of novelWriter
|
11
|
-
Copyright 2018–
|
12
|
+
Copyright 2018–2024, Veronica Berglyd Olsen
|
12
13
|
|
13
14
|
This program is free software: you can redistribute it and/or modify
|
14
15
|
it under the terms of the GNU General Public License as published by
|
@@ -28,16 +29,19 @@ from __future__ import annotations
|
|
28
29
|
import shutil
|
29
30
|
import logging
|
30
31
|
|
31
|
-
from
|
32
|
+
from pathlib import Path
|
32
33
|
from functools import partial
|
34
|
+
from zipfile import ZipFile, is_zipfile
|
35
|
+
from collections.abc import Iterable
|
33
36
|
|
34
37
|
from PyQt5.QtCore import QCoreApplication
|
35
38
|
|
36
39
|
from novelwriter import CONFIG, SHARED
|
37
|
-
from novelwriter.common import minmax, simplified
|
38
|
-
from novelwriter.constants import nwItemClass
|
40
|
+
from novelwriter.common import isHandle, minmax, simplified
|
41
|
+
from novelwriter.constants import nwFiles, nwItemClass
|
39
42
|
from novelwriter.core.item import NWItem
|
40
43
|
from novelwriter.core.project import NWProject
|
44
|
+
from novelwriter.core.storage import NWStorageCreate
|
41
45
|
|
42
46
|
logger = logging.getLogger(__name__)
|
43
47
|
|
@@ -276,28 +280,25 @@ class DocDuplicator:
|
|
276
280
|
"""Run through a list of items, duplicate them, and copy the
|
277
281
|
text content if they are documents.
|
278
282
|
"""
|
279
|
-
if
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
newItem = self._project.tree.duplicate(tHandle)
|
286
|
-
if newItem is None:
|
287
|
-
return
|
288
|
-
hMap[tHandle] = newItem.itemHandle
|
289
|
-
if newItem.itemParent in hMap:
|
290
|
-
newItem.setParent(hMap[newItem.itemParent])
|
291
|
-
self._project.tree.updateItemData(newItem.itemHandle)
|
292
|
-
if newItem.isFileType():
|
293
|
-
oldDoc = self._project.storage.getDocument(tHandle)
|
294
|
-
newDoc = self._project.storage.getDocument(newItem.itemHandle)
|
295
|
-
if newDoc.fileExists():
|
283
|
+
if items:
|
284
|
+
nHandle = items[0]
|
285
|
+
hMap: dict[str, str | None] = {t: None for t in items}
|
286
|
+
for tHandle in items:
|
287
|
+
newItem = self._project.tree.duplicate(tHandle)
|
288
|
+
if newItem is None:
|
296
289
|
return
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
290
|
+
hMap[tHandle] = newItem.itemHandle
|
291
|
+
if newItem.itemParent in hMap:
|
292
|
+
newItem.setParent(hMap[newItem.itemParent])
|
293
|
+
self._project.tree.updateItemData(newItem.itemHandle)
|
294
|
+
if newItem.isFileType():
|
295
|
+
oldDoc = self._project.storage.getDocument(tHandle)
|
296
|
+
newDoc = self._project.storage.getDocument(newItem.itemHandle)
|
297
|
+
if newDoc.fileExists():
|
298
|
+
return
|
299
|
+
newDoc.writeDocument(oldDoc.readDocument() or "")
|
300
|
+
yield newItem.itemHandle, nHandle
|
301
|
+
nHandle = None
|
301
302
|
return
|
302
303
|
|
303
304
|
# END Class DocDuplicator
|
@@ -309,55 +310,67 @@ class ProjectBuilder:
|
|
309
310
|
"""
|
310
311
|
|
311
312
|
def __init__(self) -> None:
|
312
|
-
self.
|
313
|
+
self._path = None
|
314
|
+
self.tr = partial(QCoreApplication.translate, "ProjectBuilder")
|
313
315
|
return
|
314
316
|
|
317
|
+
@property
|
318
|
+
def projPath(self) -> Path | None:
|
319
|
+
"""The path of the newly created project."""
|
320
|
+
return self._path
|
321
|
+
|
315
322
|
##
|
316
323
|
# Methods
|
317
324
|
##
|
318
325
|
|
319
326
|
def buildProject(self, data: dict) -> bool:
|
320
|
-
"""Build a project from a data dictionary
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
327
|
+
"""Build or copy a project from a data dictionary."""
|
328
|
+
if isinstance(data, dict):
|
329
|
+
path = data.get("path", None) or None
|
330
|
+
if isinstance(path, (str, Path)):
|
331
|
+
self._path = Path(path).resolve()
|
332
|
+
if data.get("sample", False):
|
333
|
+
return self._extractSampleProject(self._path)
|
334
|
+
elif data.get("template"):
|
335
|
+
return self._copyProject(self._path, data)
|
336
|
+
else:
|
337
|
+
return self._buildAndPopulate(self._path, data)
|
338
|
+
SHARED.error("A project path is required.")
|
339
|
+
return False
|
330
340
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
if popSample:
|
335
|
-
return self._extractSampleProject(data)
|
336
|
-
|
337
|
-
projPath = data.get("projPath", None)
|
338
|
-
if projPath is None:
|
339
|
-
logger.error("No project path set for the new project")
|
340
|
-
return False
|
341
|
+
##
|
342
|
+
# Internal Functions
|
343
|
+
##
|
341
344
|
|
345
|
+
def _buildAndPopulate(self, path: Path, data: dict) -> bool:
|
346
|
+
"""Build a blank project from a data dictionary."""
|
342
347
|
project = NWProject()
|
343
|
-
|
348
|
+
status = project.storage.createNewProject(path)
|
349
|
+
if status == NWStorageCreate.NOT_EMPTY:
|
350
|
+
SHARED.error(self.tr(
|
351
|
+
"The target folder is not empty. "
|
352
|
+
"Please choose another folder."
|
353
|
+
))
|
354
|
+
return False
|
355
|
+
elif status == NWStorageCreate.OS_ERROR:
|
356
|
+
SHARED.error(self.tr(
|
357
|
+
"An error occurred while trying to create the project."
|
358
|
+
), exc=project.storage.exc)
|
344
359
|
return False
|
345
360
|
|
361
|
+
self._path = project.storage.storagePath
|
362
|
+
|
346
363
|
lblNewProject = self.tr("New Project")
|
347
|
-
lblNewChapter = self.tr("New Chapter")
|
348
|
-
lblNewScene = self.tr("New Scene")
|
349
364
|
lblTitlePage = self.tr("Title Page")
|
350
365
|
lblByAuthors = self.tr("By")
|
351
366
|
|
352
367
|
# Settings
|
353
|
-
projName = data.get("
|
354
|
-
|
355
|
-
|
356
|
-
projLang = data.get("projLang", "en_GB")
|
368
|
+
projName = data.get("name", lblNewProject)
|
369
|
+
projAuthor = data.get("author", "")
|
370
|
+
projLang = data.get("language", "en_GB")
|
357
371
|
|
358
372
|
project.data.setUuid(None)
|
359
373
|
project.data.setName(projName)
|
360
|
-
project.data.setTitle(projTitle)
|
361
374
|
project.data.setAuthor(projAuthor)
|
362
375
|
project.data.setLanguage(projLang)
|
363
376
|
project.setDefaultStatusImport()
|
@@ -366,7 +379,7 @@ class ProjectBuilder:
|
|
366
379
|
# Add Root Folders
|
367
380
|
hNovelRoot = project.newRoot(nwItemClass.NOVEL)
|
368
381
|
hTitlePage = project.newFile(lblTitlePage, hNovelRoot)
|
369
|
-
novelTitle = project.data.
|
382
|
+
novelTitle = project.data.name
|
370
383
|
|
371
384
|
titlePage = f"#! {novelTitle}\n\n"
|
372
385
|
if project.data.author:
|
@@ -375,116 +388,142 @@ class ProjectBuilder:
|
|
375
388
|
aDoc = project.storage.getDocument(hTitlePage)
|
376
389
|
aDoc.writeDocument(titlePage)
|
377
390
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
addNotes = data.get("addNotes", False)
|
443
|
-
for newRoot in data.get("addRoots", []):
|
444
|
-
if newRoot in nwItemClass:
|
445
|
-
rHandle = project.newRoot(newRoot)
|
446
|
-
if addNotes:
|
447
|
-
aHandle = project.newFile(noteTitles[newRoot], rHandle)
|
448
|
-
ntTag = simplified(noteTitles[newRoot]).replace(" ", "")
|
449
|
-
aDoc = project.storage.getDocument(aHandle)
|
450
|
-
aDoc.writeDocument(
|
451
|
-
f"# {noteTitles[newRoot]}\n\n"
|
452
|
-
f"@tag: {ntTag}\n\n"
|
453
|
-
f"% Short: {bfNote}\n\n"
|
454
|
-
)
|
455
|
-
|
456
|
-
# Also add the archive and trash folders
|
457
|
-
project.newRoot(nwItemClass.ARCHIVE)
|
458
|
-
project.trashFolder()
|
459
|
-
|
460
|
-
project.saveProject()
|
461
|
-
project.closeProject()
|
391
|
+
# Create a project structure based on selected root folders
|
392
|
+
# and a number of chapters and scenes selected in the
|
393
|
+
# wizard's custom page.
|
394
|
+
|
395
|
+
# Create chapters and scenes
|
396
|
+
numChapters = data.get("chapters", 0)
|
397
|
+
numScenes = data.get("scenes", 0)
|
398
|
+
|
399
|
+
chSynop = self.tr("Summary of the chapter.")
|
400
|
+
scSynop = self.tr("Summary of the scene.")
|
401
|
+
bfNote = self.tr("A short description.")
|
402
|
+
|
403
|
+
# Create chapters
|
404
|
+
if numChapters > 0:
|
405
|
+
for ch in range(numChapters):
|
406
|
+
chTitle = self.tr("Chapter {0}").format(f"{ch+1:d}")
|
407
|
+
cHandle = project.newFile(chTitle, hNovelRoot)
|
408
|
+
aDoc = project.storage.getDocument(cHandle)
|
409
|
+
aDoc.writeDocument(f"## {chTitle}\n\n%Synopsis: {chSynop}\n\n")
|
410
|
+
|
411
|
+
# Create chapter scenes
|
412
|
+
if numScenes > 0 and cHandle:
|
413
|
+
for sc in range(numScenes):
|
414
|
+
scTitle = self.tr("Scene {0}").format(f"{ch+1:d}.{sc+1:d}")
|
415
|
+
sHandle = project.newFile(scTitle, cHandle)
|
416
|
+
aDoc = project.storage.getDocument(sHandle)
|
417
|
+
aDoc.writeDocument(f"### {scTitle}\n\n%Synopsis: {scSynop}\n\n")
|
418
|
+
|
419
|
+
# Create scenes (no chapters)
|
420
|
+
elif numScenes > 0:
|
421
|
+
for sc in range(numScenes):
|
422
|
+
scTitle = self.tr("Scene {0}").format(f"{sc+1:d}")
|
423
|
+
sHandle = project.newFile(scTitle, hNovelRoot)
|
424
|
+
aDoc = project.storage.getDocument(sHandle)
|
425
|
+
aDoc.writeDocument(f"### {scTitle}\n\n%Synopsis: {scSynop}\n\n")
|
426
|
+
|
427
|
+
# Create notes folders
|
428
|
+
noteTitles = {
|
429
|
+
nwItemClass.PLOT: self.tr("Main Plot"),
|
430
|
+
nwItemClass.CHARACTER: self.tr("Protagonist"),
|
431
|
+
nwItemClass.WORLD: self.tr("Main Location"),
|
432
|
+
}
|
433
|
+
|
434
|
+
addNotes = data.get("notes", False)
|
435
|
+
for newRoot in data.get("roots", []):
|
436
|
+
if newRoot in nwItemClass:
|
437
|
+
rHandle = project.newRoot(newRoot)
|
438
|
+
if addNotes:
|
439
|
+
aHandle = project.newFile(noteTitles[newRoot], rHandle)
|
440
|
+
ntTag = simplified(noteTitles[newRoot]).replace(" ", "")
|
441
|
+
aDoc = project.storage.getDocument(aHandle)
|
442
|
+
aDoc.writeDocument(
|
443
|
+
f"# {noteTitles[newRoot]}\n\n"
|
444
|
+
f"@tag: {ntTag}\n\n"
|
445
|
+
f"%Short: {bfNote}\n\n"
|
446
|
+
)
|
447
|
+
|
448
|
+
# Also add the archive and trash folders
|
449
|
+
project.newRoot(nwItemClass.ARCHIVE)
|
450
|
+
project.trashFolder()
|
451
|
+
|
452
|
+
project.saveProject()
|
453
|
+
project.closeProject()
|
462
454
|
|
463
455
|
return True
|
464
456
|
|
465
|
-
|
466
|
-
|
467
|
-
|
457
|
+
def _copyProject(self, path: Path, data: dict) -> bool:
|
458
|
+
"""Copy an existing project content, but not the meta data, and
|
459
|
+
update new settings.
|
460
|
+
"""
|
461
|
+
source = data.get("template")
|
462
|
+
if not (isinstance(source, Path) and source.is_file()
|
463
|
+
and (source.name == nwFiles.PROJ_FILE or is_zipfile(source))):
|
464
|
+
logger.error("Could not access source project: %s", source)
|
465
|
+
return False
|
468
466
|
|
469
|
-
|
467
|
+
logger.info("Copying project: %s", source)
|
468
|
+
if path.exists():
|
469
|
+
SHARED.error(self.tr(
|
470
|
+
"The target folder already exists. "
|
471
|
+
"Please choose another folder."
|
472
|
+
))
|
473
|
+
return False
|
474
|
+
|
475
|
+
# Begin copying
|
476
|
+
srcPath = source.parent
|
477
|
+
dstPath = path.resolve()
|
478
|
+
srcCont = srcPath / "content"
|
479
|
+
dstCont = dstPath / "content"
|
480
|
+
try:
|
481
|
+
dstPath.mkdir(exist_ok=True)
|
482
|
+
dstCont.mkdir(exist_ok=True)
|
483
|
+
if is_zipfile(source):
|
484
|
+
with ZipFile(source) as zipObj:
|
485
|
+
for member in zipObj.namelist():
|
486
|
+
if member == nwFiles.PROJ_FILE:
|
487
|
+
zipObj.extract(member, dstPath)
|
488
|
+
elif member.startswith("content") and member.endswith(".nwd"):
|
489
|
+
zipObj.extract(member, dstPath)
|
490
|
+
else:
|
491
|
+
shutil.copy2(srcPath / nwFiles.PROJ_FILE, dstPath)
|
492
|
+
for item in srcCont.iterdir():
|
493
|
+
if item.is_file() and item.suffix == ".nwd" and isHandle(item.stem):
|
494
|
+
shutil.copy2(item, dstCont)
|
495
|
+
except Exception as exc:
|
496
|
+
SHARED.error(self.tr("Could not copy project files."), exc=exc)
|
497
|
+
return False
|
498
|
+
|
499
|
+
# Open the copied project and update settings
|
500
|
+
project = NWProject()
|
501
|
+
project.openProject(dstPath)
|
502
|
+
project.data.setUuid("") # Creates a fresh uuid
|
503
|
+
project.data.setName(data.get("name", "None"))
|
504
|
+
project.data.setAuthor(data.get("author", ""))
|
505
|
+
project.data.setLanguage(data.get("language", "en_GB"))
|
506
|
+
project.data.setSpellCheck(True)
|
507
|
+
project.data.setSpellLang(None)
|
508
|
+
project.data.setDoBackup(True)
|
509
|
+
project.data.setSaveCount(0)
|
510
|
+
project.data.setAutoCount(0)
|
511
|
+
project.data.setEditTime(0)
|
512
|
+
project.saveProject()
|
513
|
+
project.closeProject()
|
514
|
+
|
515
|
+
return True
|
516
|
+
|
517
|
+
def _extractSampleProject(self, path: Path) -> bool:
|
470
518
|
"""Make a copy of the sample project by extracting the
|
471
519
|
sample.zip file to the new path.
|
472
520
|
"""
|
473
|
-
|
474
|
-
if projPath is None:
|
475
|
-
logger.error("No project path set for the example project")
|
476
|
-
return False
|
477
|
-
|
478
|
-
pkgSample = CONFIG.assetPath("sample.zip")
|
479
|
-
if pkgSample.is_file():
|
521
|
+
if (sample := CONFIG.assetPath("sample.zip")).is_file():
|
480
522
|
try:
|
481
|
-
shutil.unpack_archive(
|
523
|
+
shutil.unpack_archive(sample, path)
|
482
524
|
except Exception as exc:
|
483
|
-
SHARED.error(self.tr(
|
484
|
-
"Failed to create a new example project."
|
485
|
-
), exc=exc)
|
525
|
+
SHARED.error(self.tr("Failed to create a new example project."), exc=exc)
|
486
526
|
return False
|
487
|
-
|
488
527
|
else:
|
489
528
|
SHARED.error(self.tr(
|
490
529
|
"Failed to create a new example project. "
|
novelwriter/core/docbuild.py
CHANGED
@@ -6,7 +6,7 @@ File History:
|
|
6
6
|
Created: 2022-12-01 [2.1b1] NWBuildDocument
|
7
7
|
|
8
8
|
This file is a part of novelWriter
|
9
|
-
Copyright 2018–
|
9
|
+
Copyright 2018–2024, Veronica Berglyd Olsen
|
10
10
|
|
11
11
|
This program is free software: you can redistribute it and/or modify
|
12
12
|
it under the terms of the GNU General Public License as published by
|
@@ -25,8 +25,8 @@ from __future__ import annotations
|
|
25
25
|
|
26
26
|
import logging
|
27
27
|
|
28
|
-
from typing import Iterable
|
29
28
|
from pathlib import Path
|
29
|
+
from collections.abc import Iterable
|
30
30
|
|
31
31
|
from PyQt5.QtGui import QFont, QFontInfo
|
32
32
|
|
@@ -248,8 +248,8 @@ class NWBuildDocument:
|
|
248
248
|
def _setupBuild(self, bldObj: Tokenizer) -> dict:
|
249
249
|
"""Configure the build object."""
|
250
250
|
# Get Settings
|
251
|
-
textFont
|
252
|
-
textSize
|
251
|
+
textFont = self._build.getStr("format.textFont")
|
252
|
+
textSize = self._build.getInt("format.textSize")
|
253
253
|
|
254
254
|
fontFamily = textFont or CONFIG.textFont
|
255
255
|
bldFont = QFont(fontFamily, textSize)
|
@@ -284,6 +284,9 @@ class NWBuildDocument:
|
|
284
284
|
if isinstance(bldObj, ToOdt):
|
285
285
|
bldObj.setColourHeaders(self._build.getBool("odt.addColours"))
|
286
286
|
bldObj.setLanguage(self._project.data.language)
|
287
|
+
bldObj.setHeaderFormat(
|
288
|
+
self._build.getStr("odt.pageHeader"), self._build.getInt("odt.pageCountOffset")
|
289
|
+
)
|
287
290
|
|
288
291
|
scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0)
|
289
292
|
pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0))
|
novelwriter/core/document.py
CHANGED
@@ -6,7 +6,7 @@ File History:
|
|
6
6
|
Created: 2018-09-29 [0.0.1]
|
7
7
|
|
8
8
|
This file is a part of novelWriter
|
9
|
-
Copyright 2018–
|
9
|
+
Copyright 2018–2024, Veronica Berglyd Olsen
|
10
10
|
|
11
11
|
This program is free software: you can redistribute it and/or modify
|
12
12
|
it under the terms of the GNU General Public License as published by
|
@@ -52,7 +52,7 @@ class NWDocument:
|
|
52
52
|
|
53
53
|
def __init__(self, project: NWProject, tHandle: str | None) -> None:
|
54
54
|
|
55
|
-
self._project
|
55
|
+
self._project = project
|
56
56
|
|
57
57
|
self._item = None # The currently open item
|
58
58
|
self._handle = None # The handle of the currently open item
|
@@ -284,12 +284,12 @@ class NWDocument:
|
|
284
284
|
"""Parse the document meta tag and return the name, parent,
|
285
285
|
class and layout meta values.
|
286
286
|
"""
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
287
|
+
name = self._docMeta.get("name", "")
|
288
|
+
parent = self._docMeta.get("parent", None)
|
289
|
+
itemClass = self._docMeta.get("class", None)
|
290
|
+
itemLayout = self._docMeta.get("layout", None)
|
291
291
|
|
292
|
-
return
|
292
|
+
return name, parent, itemClass, itemLayout
|
293
293
|
|
294
294
|
def getError(self) -> str:
|
295
295
|
"""Return the last recorded exception."""
|