novelWriter 2.2b1__py3-none-any.whl → 2.2.1__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.2b1.dist-info → novelWriter-2.2.1.dist-info}/METADATA +3 -3
- {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/RECORD +128 -114
- {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +10 -5
- 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_GB.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/novelwriter.ico +0 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +11 -3
- novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_panel.svg +4 -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_eye.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +11 -3
- novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-note.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_panel.svg +4 -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_eye.svg +4 -0
- novelwriter/assets/icons/x-novelwriter-project.ico +0 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/text/release_notes.htm +50 -7
- novelwriter/common.py +35 -27
- novelwriter/config.py +13 -28
- novelwriter/constants.py +21 -4
- novelwriter/core/buildsettings.py +2 -2
- novelwriter/core/coretools.py +8 -2
- novelwriter/core/docbuild.py +1 -1
- novelwriter/core/document.py +1 -1
- novelwriter/core/index.py +102 -36
- novelwriter/core/item.py +2 -2
- novelwriter/core/options.py +6 -3
- novelwriter/core/project.py +5 -5
- novelwriter/core/projectdata.py +3 -3
- novelwriter/core/projectxml.py +1 -1
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/spellcheck.py +4 -3
- novelwriter/core/status.py +3 -3
- novelwriter/core/storage.py +1 -1
- novelwriter/core/tohtml.py +11 -5
- novelwriter/core/tokenizer.py +28 -21
- novelwriter/core/tomd.py +6 -2
- novelwriter/core/toodt.py +12 -5
- novelwriter/core/tree.py +2 -2
- novelwriter/dialogs/about.py +30 -31
- novelwriter/dialogs/docmerge.py +24 -15
- novelwriter/dialogs/docsplit.py +27 -16
- novelwriter/dialogs/editlabel.py +19 -7
- novelwriter/dialogs/preferences.py +116 -131
- novelwriter/dialogs/projdetails.py +29 -36
- novelwriter/dialogs/projload.py +32 -36
- novelwriter/dialogs/projsettings.py +20 -15
- novelwriter/dialogs/quotes.py +32 -25
- novelwriter/dialogs/updates.py +17 -16
- novelwriter/dialogs/wordlist.py +34 -21
- novelwriter/enum.py +19 -8
- novelwriter/error.py +1 -1
- novelwriter/extensions/circularprogress.py +1 -1
- novelwriter/extensions/configlayout.py +3 -15
- novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
- novelwriter/extensions/novelselector.py +1 -1
- novelwriter/extensions/pageddialog.py +1 -1
- novelwriter/extensions/pagedsidebar.py +2 -5
- novelwriter/extensions/simpleprogress.py +8 -9
- novelwriter/extensions/statusled.py +1 -1
- novelwriter/extensions/switch.py +4 -4
- novelwriter/extensions/switchbox.py +1 -6
- novelwriter/gui/doceditor.py +349 -236
- novelwriter/gui/dochighlight.py +10 -11
- novelwriter/gui/docviewer.py +158 -360
- novelwriter/gui/docviewerpanel.py +502 -0
- novelwriter/gui/editordocument.py +4 -4
- novelwriter/gui/itemdetails.py +2 -2
- novelwriter/gui/mainmenu.py +50 -36
- novelwriter/gui/noveltree.py +44 -53
- novelwriter/gui/outline.py +12 -7
- novelwriter/gui/projtree.py +465 -381
- novelwriter/gui/sidebar.py +9 -7
- novelwriter/gui/statusbar.py +48 -5
- novelwriter/gui/theme.py +26 -8
- novelwriter/guimain.py +212 -208
- novelwriter/shared.py +76 -30
- novelwriter/tools/dictionaries.py +268 -0
- novelwriter/tools/lipsum.py +34 -28
- novelwriter/tools/manusbuild.py +20 -10
- novelwriter/tools/manuscript.py +20 -27
- novelwriter/tools/manussettings.py +2 -4
- novelwriter/tools/projwizard.py +3 -3
- novelwriter/tools/writingstats.py +18 -5
- 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_dark/typ_at.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
- novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
- novelwriter/assets/icons/typicons_light/typ_at.svg +0 -4
- {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/top_level.txt +0 -0
novelwriter/common.py
CHANGED
@@ -3,10 +3,10 @@ novelWriter – Common Functions
|
|
3
3
|
==============================
|
4
4
|
|
5
5
|
File History:
|
6
|
-
Created: 2019-05-12 [0.1]
|
6
|
+
Created: 2019-05-12 [0.1.0]
|
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
|
@@ -33,9 +33,11 @@ from typing import Any, Literal
|
|
33
33
|
from pathlib import Path
|
34
34
|
from datetime import datetime
|
35
35
|
from configparser import ConfigParser
|
36
|
+
from urllib.parse import urljoin
|
37
|
+
from urllib.request import pathname2url
|
36
38
|
|
37
|
-
from PyQt5.
|
38
|
-
from PyQt5.
|
39
|
+
from PyQt5.QtGui import QDesktopServices
|
40
|
+
from PyQt5.QtCore import QCoreApplication, QUrl
|
39
41
|
|
40
42
|
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
|
41
43
|
from novelwriter.error import logException
|
@@ -44,9 +46,9 @@ from novelwriter.constants import nwConst, nwUnicode
|
|
44
46
|
logger = logging.getLogger(__name__)
|
45
47
|
|
46
48
|
|
47
|
-
|
49
|
+
##
|
48
50
|
# Checker Functions
|
49
|
-
|
51
|
+
##
|
50
52
|
|
51
53
|
def checkStringNone(value: Any, default: str | None) -> str | None:
|
52
54
|
"""Check if a variable is a string or a None."""
|
@@ -129,9 +131,9 @@ def checkPath(value: Any, default: Path) -> Path:
|
|
129
131
|
return default
|
130
132
|
|
131
133
|
|
132
|
-
|
134
|
+
##
|
133
135
|
# Validator Functions
|
134
|
-
|
136
|
+
##
|
135
137
|
|
136
138
|
def isHandle(value: Any) -> bool:
|
137
139
|
"""Check if a string is a valid novelWriter handle.
|
@@ -202,9 +204,9 @@ def checkIntTuple(value: int, valid: tuple | list | set, default: int) -> int:
|
|
202
204
|
return default
|
203
205
|
|
204
206
|
|
205
|
-
|
207
|
+
##
|
206
208
|
# Formatting Functions
|
207
|
-
|
209
|
+
##
|
208
210
|
|
209
211
|
def formatInt(value: int) -> str:
|
210
212
|
"""Formats an integer with k, M, G etc."""
|
@@ -248,9 +250,9 @@ def formatTime(t: int) -> str:
|
|
248
250
|
return "ERROR"
|
249
251
|
|
250
252
|
|
251
|
-
|
253
|
+
##
|
252
254
|
# String Functions
|
253
|
-
|
255
|
+
##
|
254
256
|
|
255
257
|
def simplified(text: str) -> str:
|
256
258
|
"""Take a string and strip leading and trailing whitespaces, and
|
@@ -369,9 +371,9 @@ def numberToRoman(value: int, toLower: bool = False) -> str:
|
|
369
371
|
return roman.lower() if toLower else roman
|
370
372
|
|
371
373
|
|
372
|
-
|
374
|
+
##
|
373
375
|
# Encoder Functions
|
374
|
-
|
376
|
+
##
|
375
377
|
|
376
378
|
def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
|
377
379
|
"""Encode a dictionary, list or tuple as a json object or array, and
|
@@ -461,9 +463,9 @@ def xmlIndent(tree: ET.Element | ET.ElementTree) -> None:
|
|
461
463
|
return
|
462
464
|
|
463
465
|
|
464
|
-
|
466
|
+
##
|
465
467
|
# File and File System Functions
|
466
|
-
|
468
|
+
##
|
467
469
|
|
468
470
|
def readTextFile(path: str | Path) -> str:
|
469
471
|
"""Read the content of a text file in a robust manner."""
|
@@ -487,21 +489,27 @@ def makeFileNameSafe(text: str) -> str:
|
|
487
489
|
return "".join(c for c in text if c.isalnum() or c in allowed)
|
488
490
|
|
489
491
|
|
490
|
-
|
491
|
-
|
492
|
-
|
492
|
+
def getFileSize(path: Path) -> int:
|
493
|
+
"""Return the size of a file."""
|
494
|
+
try:
|
495
|
+
return path.stat().st_size
|
496
|
+
except Exception:
|
497
|
+
return -1
|
493
498
|
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
499
|
+
|
500
|
+
def openExternalPath(path: Path) -> bool:
|
501
|
+
"""Open a path by passing it to the desktop environment."""
|
502
|
+
if Path(path).exists():
|
503
|
+
QDesktopServices.openUrl(
|
504
|
+
QUrl(urljoin("file:", pathname2url(str(path))))
|
505
|
+
)
|
506
|
+
return True
|
507
|
+
return False
|
500
508
|
|
501
509
|
|
502
|
-
|
510
|
+
##
|
503
511
|
# Classes
|
504
|
-
|
512
|
+
##
|
505
513
|
|
506
514
|
class NWConfigParser(ConfigParser):
|
507
515
|
"""Common: Adapted Config Parser
|
novelwriter/config.py
CHANGED
@@ -7,7 +7,7 @@ Created: 2018-09-22 [0.0.1] Config
|
|
7
7
|
Created: 2022-11-09 [2.0rc2] RecentProjects
|
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
|
@@ -148,6 +148,7 @@ class Config:
|
|
148
148
|
|
149
149
|
self.autoScroll = False # Typewriter-like scrolling
|
150
150
|
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
|
151
|
+
self.scrollPastEnd = True # Scroll past end of document, and centre cursor
|
151
152
|
|
152
153
|
self.wordCountTimer = 5.0 # Interval for word count update in seconds
|
153
154
|
self.incNotesWCount = True # The status bar word count includes notes
|
@@ -178,9 +179,9 @@ class Config:
|
|
178
179
|
self.spellLanguage = "en"
|
179
180
|
|
180
181
|
# State
|
181
|
-
self.
|
182
|
+
self.showViewerPanel = True # The panel for the viewer is visible
|
182
183
|
self.showEditToolBar = False # The document editor toolbar visibility
|
183
|
-
self.useShortcodes = False # Use
|
184
|
+
self.useShortcodes = False # Use shortcodes for basic formatting
|
184
185
|
self.viewComments = True # Comments are shown in the viewer
|
185
186
|
self.viewSynopsis = True # Synopsis is shown in the viewer
|
186
187
|
|
@@ -224,6 +225,8 @@ class Config:
|
|
224
225
|
# Other System Info
|
225
226
|
self.hostName = QSysInfo.machineHostName()
|
226
227
|
self.kernelVer = QSysInfo.kernelVersion()
|
228
|
+
self.isDebug = False # True if running in debug mode
|
229
|
+
self.memInfo = False # True if displaying mem info in status bar
|
227
230
|
|
228
231
|
# Packages
|
229
232
|
self.hasEnchant = False # The pyenchant package
|
@@ -391,8 +394,7 @@ class Config:
|
|
391
394
|
return self._appPath / "assets"
|
392
395
|
|
393
396
|
def lastPath(self) -> Path:
|
394
|
-
"""Return the last path used by the user,
|
395
|
-
"""
|
397
|
+
"""Return the last path used by the user, if it exists."""
|
396
398
|
if isinstance(self._lastPath, Path):
|
397
399
|
if self._lastPath.is_dir():
|
398
400
|
return self._lastPath
|
@@ -400,9 +402,8 @@ class Config:
|
|
400
402
|
|
401
403
|
def backupPath(self) -> Path:
|
402
404
|
"""Return the backup path."""
|
403
|
-
if isinstance(self._backupPath, Path):
|
404
|
-
|
405
|
-
return self._backupPath
|
405
|
+
if isinstance(self._backupPath, Path) and self._backupPath.is_dir():
|
406
|
+
return self._backupPath
|
406
407
|
return self._backPath
|
407
408
|
|
408
409
|
def errorText(self) -> str:
|
@@ -577,6 +578,7 @@ class Config:
|
|
577
578
|
self.doReplaceDots = conf.rdBool(sec, "repdots", self.doReplaceDots)
|
578
579
|
self.autoScroll = conf.rdBool(sec, "autoscroll", self.autoScroll)
|
579
580
|
self.autoScrollPos = conf.rdInt(sec, "autoscrollpos", self.autoScrollPos)
|
581
|
+
self.scrollPastEnd = conf.rdBool(sec, "scrollpastend", self.scrollPastEnd)
|
580
582
|
self.fmtSQuoteOpen = conf.rdStr(sec, "fmtsquoteopen", self.fmtSQuoteOpen)
|
581
583
|
self.fmtSQuoteClose = conf.rdStr(sec, "fmtsquoteclose", self.fmtSQuoteClose)
|
582
584
|
self.fmtDQuoteOpen = conf.rdStr(sec, "fmtdquoteopen", self.fmtDQuoteOpen)
|
@@ -600,7 +602,7 @@ class Config:
|
|
600
602
|
|
601
603
|
# State
|
602
604
|
sec = "State"
|
603
|
-
self.
|
605
|
+
self.showViewerPanel = conf.rdBool(sec, "showviewerpanel", self.showViewerPanel)
|
604
606
|
self.showEditToolBar = conf.rdBool(sec, "showedittoolbar", self.showEditToolBar)
|
605
607
|
self.useShortcodes = conf.rdBool(sec, "useshortcodes", self.useShortcodes)
|
606
608
|
self.viewComments = conf.rdBool(sec, "viewcomments", self.viewComments)
|
@@ -612,24 +614,6 @@ class Config:
|
|
612
614
|
self.searchNextFile = conf.rdBool(sec, "searchnextfile", self.searchNextFile)
|
613
615
|
self.searchMatchCap = conf.rdBool(sec, "searchmatchcap", self.searchMatchCap)
|
614
616
|
|
615
|
-
# Deprecated Settings or Locations as of 2.0
|
616
|
-
# ToDo: These will be loaded for a few minor releases until the users have converted them
|
617
|
-
self.guiFont = conf.rdStr("Main", "guifont", self.guiFont)
|
618
|
-
self.guiFontSize = conf.rdInt("Main", "guifontsize", self.guiFontSize)
|
619
|
-
self.guiLocale = conf.rdStr("Main", "guilang", self.guiLocale)
|
620
|
-
self._backupPath = conf.rdPath("Backup", "backuppath", self._backupPath)
|
621
|
-
self.backupOnClose = conf.rdBool("Backup", "backuponclose", self.backupOnClose)
|
622
|
-
self.askBeforeBackup = conf.rdBool("Backup", "askbeforebackup", self.askBeforeBackup)
|
623
|
-
fmtSingleQuotes = conf.rdStrList(sec, "fmtsinglequote", [])
|
624
|
-
fmtDoubleQuotes = conf.rdStrList(sec, "fmtdoublequote", [])
|
625
|
-
|
626
|
-
if isinstance(fmtSingleQuotes, list) and len(fmtSingleQuotes) == 2:
|
627
|
-
self.fmtSQuoteOpen = fmtSingleQuotes[0]
|
628
|
-
self.fmtSQuoteClose = fmtSingleQuotes[1]
|
629
|
-
if isinstance(fmtDoubleQuotes, list) and len(fmtDoubleQuotes) == 2:
|
630
|
-
self.fmtDQuoteOpen = fmtDoubleQuotes[0]
|
631
|
-
self.fmtDQuoteClose = fmtDoubleQuotes[1]
|
632
|
-
|
633
617
|
# Check Values
|
634
618
|
# ============
|
635
619
|
|
@@ -701,6 +685,7 @@ class Config:
|
|
701
685
|
"repdots": str(self.doReplaceDots),
|
702
686
|
"autoscroll": str(self.autoScroll),
|
703
687
|
"autoscrollpos": str(self.autoScrollPos),
|
688
|
+
"scrollpastend": str(self.scrollPastEnd),
|
704
689
|
"fmtsquoteopen": str(self.fmtSQuoteOpen),
|
705
690
|
"fmtsquoteclose": str(self.fmtSQuoteClose),
|
706
691
|
"fmtdquoteopen": str(self.fmtDQuoteOpen),
|
@@ -724,7 +709,7 @@ class Config:
|
|
724
709
|
}
|
725
710
|
|
726
711
|
conf["State"] = {
|
727
|
-
"
|
712
|
+
"showviewerpanel": str(self.showViewerPanel),
|
728
713
|
"showedittoolbar": str(self.showEditToolBar),
|
729
714
|
"useshortcodes": str(self.useShortcodes),
|
730
715
|
"viewcomments": str(self.viewComments),
|
novelwriter/constants.py
CHANGED
@@ -6,7 +6,7 @@ File History:
|
|
6
6
|
Created: 2019-04-28 [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
|
@@ -50,6 +50,9 @@ class nwConst:
|
|
50
50
|
URL_HELP = "https://github.com/vkbo/novelWriter/discussions"
|
51
51
|
URL_RELEASE = "https://github.com/vkbo/novelWriter/releases/latest"
|
52
52
|
|
53
|
+
# Requests
|
54
|
+
USER_AGENT = "Mozilla/5.0 (compatible; novelWriter (Python))"
|
55
|
+
|
53
56
|
# Gui Settings
|
54
57
|
STATUS_MSG_TIMEOUT = 15000 # milliseconds
|
55
58
|
|
@@ -153,6 +156,21 @@ class nwKeyWords:
|
|
153
156
|
# END Class nwKeyWords
|
154
157
|
|
155
158
|
|
159
|
+
class nwLists:
|
160
|
+
|
161
|
+
USER_CLASSES = [
|
162
|
+
nwItemClass.CHARACTER,
|
163
|
+
nwItemClass.PLOT,
|
164
|
+
nwItemClass.WORLD,
|
165
|
+
nwItemClass.TIMELINE,
|
166
|
+
nwItemClass.OBJECT,
|
167
|
+
nwItemClass.ENTITY,
|
168
|
+
nwItemClass.CUSTOM,
|
169
|
+
]
|
170
|
+
|
171
|
+
# END Class nwLists
|
172
|
+
|
173
|
+
|
156
174
|
class nwLabels:
|
157
175
|
|
158
176
|
CLASS_NAME = {
|
@@ -221,8 +239,8 @@ class nwLabels:
|
|
221
239
|
nwOutline.FOCUS: QT_TRANSLATE_NOOP("Constant", "Focus"),
|
222
240
|
nwOutline.CHAR: KEY_NAME[nwKeyWords.CHAR_KEY],
|
223
241
|
nwOutline.PLOT: KEY_NAME[nwKeyWords.PLOT_KEY],
|
224
|
-
nwOutline.TIME: KEY_NAME[nwKeyWords.TIME_KEY],
|
225
242
|
nwOutline.WORLD: KEY_NAME[nwKeyWords.WORLD_KEY],
|
243
|
+
nwOutline.TIME: KEY_NAME[nwKeyWords.TIME_KEY],
|
226
244
|
nwOutline.OBJECT: KEY_NAME[nwKeyWords.OBJECT_KEY],
|
227
245
|
nwOutline.ENTITY: KEY_NAME[nwKeyWords.ENTITY_KEY],
|
228
246
|
nwOutline.CUSTOM: KEY_NAME[nwKeyWords.CUSTOM_KEY],
|
@@ -327,8 +345,7 @@ class nwQuotes:
|
|
327
345
|
|
328
346
|
|
329
347
|
class nwUnicode:
|
330
|
-
"""Supported unicode character constants and their HTML equivalents.
|
331
|
-
"""
|
348
|
+
"""Supported unicode character constants and their HTML equivalents."""
|
332
349
|
# Unicode Constants
|
333
350
|
# =================
|
334
351
|
|
@@ -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
|
@@ -349,7 +349,7 @@ class BuildSettings:
|
|
349
349
|
def buildItemFilter(
|
350
350
|
self, project: NWProject, withRoots: bool = False
|
351
351
|
) -> dict[str, tuple[bool, FilterMode]]:
|
352
|
-
"""Return a dictionary of item handles with filter
|
352
|
+
"""Return a dictionary of item handles with filter decisions
|
353
353
|
applied.
|
354
354
|
"""
|
355
355
|
result: dict[str, tuple[bool, FilterMode]] = {}
|
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
|
@@ -406,6 +407,7 @@ class ProjectBuilder:
|
|
406
407
|
|
407
408
|
chSynop = self.tr("Summary of the chapter.")
|
408
409
|
scSynop = self.tr("Summary of the scene.")
|
410
|
+
bfNote = self.tr("A short description.")
|
409
411
|
|
410
412
|
# Create chapters
|
411
413
|
if numChapters > 0:
|
@@ -446,7 +448,11 @@ class ProjectBuilder:
|
|
446
448
|
aHandle = project.newFile(noteTitles[newRoot], rHandle)
|
447
449
|
ntTag = simplified(noteTitles[newRoot]).replace(" ", "")
|
448
450
|
aDoc = project.storage.getDocument(aHandle)
|
449
|
-
aDoc.writeDocument(
|
451
|
+
aDoc.writeDocument(
|
452
|
+
f"# {noteTitles[newRoot]}\n\n"
|
453
|
+
f"@tag: {ntTag}\n\n"
|
454
|
+
f"% Short: {bfNote}\n\n"
|
455
|
+
)
|
450
456
|
|
451
457
|
# Also add the archive and trash folders
|
452
458
|
project.newRoot(nwItemClass.ARCHIVE)
|
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
|
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
|
novelwriter/core/index.py
CHANGED
@@ -11,7 +11,7 @@ Created: 2022-05-29 [2.0rc1] TagsIndex
|
|
11
11
|
Created: 2022-05-29 [2.0rc1] ItemIndex
|
12
12
|
|
13
13
|
This file is a part of novelWriter
|
14
|
-
Copyright 2018–
|
14
|
+
Copyright 2018–2024, Veronica Berglyd Olsen
|
15
15
|
|
16
16
|
This program is free software: you can redistribute it and/or modify
|
17
17
|
it under the terms of the GNU General Public License as published by
|
@@ -35,7 +35,8 @@ from time import time
|
|
35
35
|
from typing import TYPE_CHECKING, ItemsView, Iterable, Iterator
|
36
36
|
from pathlib import Path
|
37
37
|
|
38
|
-
from novelwriter
|
38
|
+
from novelwriter import SHARED
|
39
|
+
from novelwriter.enum import nwComment, nwItemClass, nwItemType, nwItemLayout
|
39
40
|
from novelwriter.error import logException
|
40
41
|
from novelwriter.common import checkInt, isHandle, isItemClass, isTitleTag, jsonEncode
|
41
42
|
from novelwriter.constants import nwFiles, nwKeyWords, nwRegEx, nwUnicode, nwHeaders
|
@@ -112,6 +113,7 @@ class NWIndex:
|
|
112
113
|
self._itemIndex.clear()
|
113
114
|
self._indexChange = 0.0
|
114
115
|
self._rootChange = {}
|
116
|
+
SHARED.indexSignalProxy({"event": "clearIndex"})
|
115
117
|
return
|
116
118
|
|
117
119
|
def rebuildIndex(self) -> None:
|
@@ -121,16 +123,22 @@ class NWIndex:
|
|
121
123
|
if nwItem.isFileType():
|
122
124
|
tHandle = nwItem.itemHandle
|
123
125
|
theDoc = self._project.storage.getDocument(tHandle)
|
124
|
-
self.scanText(tHandle, theDoc.readDocument() or "")
|
126
|
+
self.scanText(tHandle, theDoc.readDocument() or "", blockSignal=True)
|
125
127
|
self._indexBroken = False
|
128
|
+
SHARED.indexSignalProxy({"event": "buildIndex"})
|
126
129
|
return
|
127
130
|
|
128
131
|
def deleteHandle(self, tHandle: str) -> None:
|
129
132
|
"""Delete all entries of a given document handle."""
|
130
133
|
logger.debug("Removing item '%s' from the index", tHandle)
|
131
|
-
|
134
|
+
delTags = self._itemIndex.allItemTags(tHandle)
|
135
|
+
for tTag in delTags:
|
132
136
|
del self._tagsIndex[tTag]
|
133
137
|
del self._itemIndex[tHandle]
|
138
|
+
SHARED.indexSignalProxy({
|
139
|
+
"event": "updateTags",
|
140
|
+
"deleted": delTags,
|
141
|
+
})
|
134
142
|
return
|
135
143
|
|
136
144
|
def reIndexHandle(self, tHandle: str | None) -> bool:
|
@@ -138,14 +146,12 @@ class NWIndex:
|
|
138
146
|
moved from the archive or trash folders back into the active
|
139
147
|
project.
|
140
148
|
"""
|
141
|
-
if tHandle
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
return True
|
149
|
+
if tHandle and self._project.tree.checkType(tHandle, nwItemType.FILE):
|
150
|
+
logger.debug("Re-indexing item '%s'", tHandle)
|
151
|
+
theDoc = self._project.storage.getDocument(tHandle)
|
152
|
+
self.scanText(tHandle, theDoc.readDocument() or "")
|
153
|
+
return True
|
154
|
+
return False
|
149
155
|
|
150
156
|
def indexChangedSince(self, checkTime: int | float) -> bool:
|
151
157
|
"""Check if the index has changed since a given time."""
|
@@ -200,6 +206,7 @@ class NWIndex:
|
|
200
206
|
self.reIndexHandle(fHandle)
|
201
207
|
|
202
208
|
self._indexChange = time()
|
209
|
+
SHARED.indexSignalProxy({"event": "buildIndex"})
|
203
210
|
|
204
211
|
logger.debug("Index loaded in %.3f ms", (time() - tStart)*1000)
|
205
212
|
|
@@ -238,7 +245,7 @@ class NWIndex:
|
|
238
245
|
# Index Building
|
239
246
|
##
|
240
247
|
|
241
|
-
def scanText(self, tHandle: str, text: str) -> bool:
|
248
|
+
def scanText(self, tHandle: str, text: str, blockSignal: bool = False) -> bool:
|
242
249
|
"""Scan a piece of text associated with a handle. This will
|
243
250
|
update the indices accordingly. This function takes the handle
|
244
251
|
and text as separate inputs as we want to primarily scan the
|
@@ -282,6 +289,11 @@ class NWIndex:
|
|
282
289
|
nowTime = time()
|
283
290
|
self._indexChange = nowTime
|
284
291
|
self._rootChange[tItem.itemRoot] = nowTime
|
292
|
+
if not blockSignal:
|
293
|
+
SHARED.indexSignalProxy({
|
294
|
+
"event": "scanText",
|
295
|
+
"handle": tHandle,
|
296
|
+
})
|
285
297
|
|
286
298
|
return True
|
287
299
|
|
@@ -289,7 +301,7 @@ class NWIndex:
|
|
289
301
|
# Internal Indexer Helpers
|
290
302
|
##
|
291
303
|
|
292
|
-
def _scanActive(self, tHandle: str, nwItem: NWItem, text: str, tags: dict) -> None:
|
304
|
+
def _scanActive(self, tHandle: str, nwItem: NWItem, text: str, tags: dict[str, bool]) -> None:
|
293
305
|
"""Scan an active document for meta data."""
|
294
306
|
nTitle = 0 # Line Number of the previous title
|
295
307
|
cTitle = TT_NONE # Tag of the current title
|
@@ -326,14 +338,9 @@ class NWIndex:
|
|
326
338
|
|
327
339
|
elif line.startswith("%"):
|
328
340
|
if cTitle != TT_NONE:
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
cLen = len(toCheck)
|
333
|
-
cOff = tLen - cLen
|
334
|
-
if synTag == "synopsis:":
|
335
|
-
sText = line[cOff+9:].strip()
|
336
|
-
self._itemIndex.setHeadingSynopsis(tHandle, cTitle, sText)
|
341
|
+
cStyle, cText, _ = processComment(line)
|
342
|
+
if cStyle in (nwComment.SYNOPSIS, nwComment.SHORT):
|
343
|
+
self._itemIndex.setHeadingSynopsis(tHandle, cTitle, cText)
|
337
344
|
|
338
345
|
# Count words for remaining text after last heading
|
339
346
|
if pTitle != TT_NONE:
|
@@ -346,9 +353,21 @@ class NWIndex:
|
|
346
353
|
|
347
354
|
# Prune no longer used tags
|
348
355
|
for tTag, isActive in tags.items():
|
349
|
-
|
350
|
-
|
356
|
+
updated = []
|
357
|
+
deleted = []
|
358
|
+
if isActive:
|
359
|
+
logger.debug("Added/updated tag '%s'", tTag)
|
360
|
+
updated.append(tTag)
|
361
|
+
else:
|
362
|
+
logger.debug("Removed tag '%s'", tTag)
|
351
363
|
del self._tagsIndex[tTag]
|
364
|
+
deleted.append(tTag)
|
365
|
+
if updated or deleted:
|
366
|
+
SHARED.indexSignalProxy({
|
367
|
+
"event": "updateTags",
|
368
|
+
"updated": updated,
|
369
|
+
"deleted": deleted,
|
370
|
+
})
|
352
371
|
|
353
372
|
return
|
354
373
|
|
@@ -385,7 +404,7 @@ class NWIndex:
|
|
385
404
|
return
|
386
405
|
|
387
406
|
def _indexKeyword(self, tHandle: str, line: str, sTitle: str,
|
388
|
-
itemClass: nwItemClass, tags: dict) -> None:
|
407
|
+
itemClass: nwItemClass, tags: dict[str, bool]) -> None:
|
389
408
|
"""Validate and save the information about a reference to a tag
|
390
409
|
in another file, or the setting of a tag in the file. A record
|
391
410
|
of active tags is updated so that no longer used tags can be
|
@@ -532,7 +551,7 @@ class NWIndex:
|
|
532
551
|
return 0
|
533
552
|
|
534
553
|
def getTableOfContents(
|
535
|
-
self, rHandle: str, maxDepth: int, skipExcl: bool = True
|
554
|
+
self, rHandle: str | None, maxDepth: int, skipExcl: bool = True
|
536
555
|
) -> list[tuple[str, int, str, int]]:
|
537
556
|
"""Generate a table of contents up to a maximum depth."""
|
538
557
|
tOrder = []
|
@@ -596,10 +615,8 @@ class NWIndex:
|
|
596
615
|
|
597
616
|
return tRefs
|
598
617
|
|
599
|
-
def getBackReferenceList(self, tHandle: str) -> dict[str, str]:
|
600
|
-
"""Build a
|
601
|
-
by tHandle.
|
602
|
-
"""
|
618
|
+
def getBackReferenceList(self, tHandle: str) -> dict[str, tuple[str, IndexHeading]]:
|
619
|
+
"""Build a dict of files referring back to our file."""
|
603
620
|
if tHandle is None or tHandle not in self._itemIndex:
|
604
621
|
return {}
|
605
622
|
|
@@ -611,20 +628,43 @@ class NWIndex:
|
|
611
628
|
for aHandle, sTitle, hItem in self._itemIndex.iterAllHeaders():
|
612
629
|
for aTag in hItem.references:
|
613
630
|
if aTag in tTags and aHandle not in tRefs:
|
614
|
-
tRefs[aHandle] = sTitle
|
631
|
+
tRefs[aHandle] = (sTitle, hItem)
|
615
632
|
|
616
633
|
return tRefs
|
617
634
|
|
618
|
-
def getTagSource(self, tagKey: str) -> tuple[str, str]:
|
635
|
+
def getTagSource(self, tagKey: str) -> tuple[str | None, str]:
|
619
636
|
"""Return the source location of a given tag."""
|
620
637
|
tHandle = self._tagsIndex.tagHandle(tagKey)
|
621
638
|
sTitle = self._tagsIndex.tagHeading(tagKey)
|
622
639
|
return tHandle, sTitle
|
623
640
|
|
624
|
-
def
|
641
|
+
def getDocumentTags(self, tHandle: str | None) -> list[str]:
|
642
|
+
"""Return all tags used by a specific document."""
|
643
|
+
return self._itemIndex.allItemTags(tHandle) if tHandle else []
|
644
|
+
|
645
|
+
def getClassTags(self, itemClass: nwItemClass) -> list[str]:
|
625
646
|
"""Return all tags based on itemClass."""
|
626
647
|
return self._tagsIndex.filterTagNames(itemClass.name)
|
627
648
|
|
649
|
+
def getTagsData(self) -> Iterator[tuple[str, str, str, IndexItem | None, IndexHeading | None]]:
|
650
|
+
"""Return all known tags."""
|
651
|
+
for tag, data in self._tagsIndex.items():
|
652
|
+
iItem = self._itemIndex[data.get("handle")]
|
653
|
+
hItem = None if iItem is None else iItem[data.get("heading")]
|
654
|
+
yield tag, data.get("name", ""), data.get("class", ""), iItem, hItem
|
655
|
+
return
|
656
|
+
|
657
|
+
def getSingleTag(self, tagKey: str) -> tuple[str, str, IndexItem | None, IndexHeading | None]:
|
658
|
+
"""Return tag data for a specific tag."""
|
659
|
+
tName = self._tagsIndex.tagName(tagKey)
|
660
|
+
tClass = self._tagsIndex.tagClass(tagKey)
|
661
|
+
tHandle = self._tagsIndex.tagHandle(tagKey)
|
662
|
+
tHeading = self._tagsIndex.tagHeading(tagKey)
|
663
|
+
if tName and tClass and tHandle and tHeading:
|
664
|
+
iItem = self._itemIndex[tHandle]
|
665
|
+
return tName, tClass, iItem, None if iItem is None else iItem[tHeading]
|
666
|
+
return "", "", None, None
|
667
|
+
|
628
668
|
# END Class NWIndex
|
629
669
|
|
630
670
|
|
@@ -643,7 +683,7 @@ class TagsIndex:
|
|
643
683
|
__slots__ = ("_tags")
|
644
684
|
|
645
685
|
def __init__(self) -> None:
|
646
|
-
self._tags: dict[str, dict] = {}
|
686
|
+
self._tags: dict[str, dict[str, str]] = {}
|
647
687
|
return
|
648
688
|
|
649
689
|
def __contains__(self, tagKey: str) -> bool:
|
@@ -665,6 +705,10 @@ class TagsIndex:
|
|
665
705
|
self._tags = {}
|
666
706
|
return
|
667
707
|
|
708
|
+
def items(self) -> ItemsView:
|
709
|
+
"""Return a dictionary view of all tags."""
|
710
|
+
return self._tags.items()
|
711
|
+
|
668
712
|
def add(self, tagKey: str, tHandle: str, sTitle: str, itemClass: nwItemClass) -> None:
|
669
713
|
"""Add a key to the index and set all values."""
|
670
714
|
self._tags[tagKey.lower()] = {
|
@@ -676,7 +720,7 @@ class TagsIndex:
|
|
676
720
|
"""Get the display name of a given tag."""
|
677
721
|
return self._tags.get(tagKey.lower(), {}).get("name", "")
|
678
722
|
|
679
|
-
def tagHandle(self, tagKey: str) -> str:
|
723
|
+
def tagHandle(self, tagKey: str) -> str | None:
|
680
724
|
"""Get the handle of a given tag."""
|
681
725
|
return self._tags.get(tagKey.lower(), {}).get("handle", None)
|
682
726
|
|
@@ -937,6 +981,11 @@ class IndexItem:
|
|
937
981
|
# Properties
|
938
982
|
##
|
939
983
|
|
984
|
+
@property
|
985
|
+
def handle(self) -> str:
|
986
|
+
"""Return the item handle of the index item."""
|
987
|
+
return self._handle
|
988
|
+
|
940
989
|
@property
|
941
990
|
def item(self) -> NWItem:
|
942
991
|
"""Return the project item of the index item."""
|
@@ -1215,9 +1264,26 @@ class IndexHeading:
|
|
1215
1264
|
|
1216
1265
|
|
1217
1266
|
# =============================================================================================== #
|
1218
|
-
#
|
1267
|
+
# Text Processing Functions
|
1219
1268
|
# =============================================================================================== #
|
1220
1269
|
|
1270
|
+
CLASSIFIERS = {
|
1271
|
+
"short": nwComment.SHORT,
|
1272
|
+
"synopsis": nwComment.SYNOPSIS,
|
1273
|
+
}
|
1274
|
+
|
1275
|
+
|
1276
|
+
def processComment(text: str) -> tuple[nwComment, str, int]:
|
1277
|
+
"""Extract comment style and text. Should only be called on text
|
1278
|
+
starting with a %.
|
1279
|
+
"""
|
1280
|
+
check = text[1:].lstrip()
|
1281
|
+
classifier, _, content = check.partition(":")
|
1282
|
+
if content and (clean := classifier.strip().lower()) in CLASSIFIERS:
|
1283
|
+
return CLASSIFIERS[clean], content.strip(), text.find(":") + 1
|
1284
|
+
return nwComment.PLAIN, check, 0
|
1285
|
+
|
1286
|
+
|
1221
1287
|
def countWords(text: str) -> tuple[int, int, int]:
|
1222
1288
|
"""Count words in a piece of text, skipping special syntax and
|
1223
1289
|
comments.
|