novelWriter 2.7b1__py3-none-any.whl → 2.7.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/__init__.py +17 -4
- novelwriter/assets/i18n/nw_cs_CZ.qm +0 -0
- 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_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_ru_RU.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_cs_CZ.json +2 -0
- novelwriter/assets/i18n/project_de_DE.json +3 -1
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/i18n/project_en_US.json +2 -0
- novelwriter/assets/i18n/project_it_IT.json +2 -0
- novelwriter/assets/i18n/project_ja_JP.json +2 -0
- novelwriter/assets/i18n/project_nb_NO.json +2 -0
- novelwriter/assets/i18n/project_nn_NO.json +5 -0
- novelwriter/assets/i18n/project_pl_PL.json +2 -0
- novelwriter/assets/i18n/project_pt_BR.json +2 -0
- novelwriter/assets/i18n/project_ru_RU.json +2 -0
- novelwriter/assets/i18n/project_zh_CN.json +2 -0
- novelwriter/assets/icons/remix_filled.icons +1 -0
- novelwriter/assets/icons/remix_outline.icons +1 -0
- novelwriter/assets/images/splash.png +0 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/manual_fr.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/snazzy.conf +3 -3
- novelwriter/assets/text/credits_en.htm +6 -0
- novelwriter/assets/themes/snazzy.conf +48 -0
- novelwriter/common.py +10 -1
- novelwriter/config.py +96 -25
- novelwriter/constants.py +21 -0
- novelwriter/core/buildsettings.py +3 -1
- novelwriter/core/coretools.py +41 -34
- novelwriter/core/docbuild.py +1 -0
- novelwriter/core/index.py +35 -5
- novelwriter/core/indexdata.py +4 -1
- novelwriter/core/item.py +35 -24
- novelwriter/core/itemmodel.py +17 -13
- novelwriter/core/novelmodel.py +2 -0
- novelwriter/core/project.py +14 -14
- novelwriter/core/projectdata.py +42 -24
- novelwriter/core/projectxml.py +17 -7
- novelwriter/core/sessions.py +29 -13
- novelwriter/core/tree.py +9 -5
- novelwriter/dialogs/docmerge.py +2 -1
- novelwriter/dialogs/docsplit.py +6 -3
- novelwriter/dialogs/editlabel.py +11 -8
- novelwriter/dialogs/preferences.py +37 -26
- novelwriter/dialogs/projectsettings.py +3 -0
- novelwriter/extensions/configlayout.py +6 -2
- novelwriter/extensions/switch.py +16 -15
- novelwriter/extensions/switchbox.py +1 -0
- novelwriter/formats/tokenizer.py +2 -1
- novelwriter/gui/doceditor.py +106 -47
- novelwriter/gui/noveltree.py +11 -5
- novelwriter/gui/outline.py +9 -1
- novelwriter/gui/projtree.py +1 -0
- novelwriter/gui/search.py +1 -0
- novelwriter/gui/statusbar.py +14 -6
- novelwriter/gui/theme.py +25 -10
- novelwriter/guimain.py +29 -9
- novelwriter/splash.py +74 -0
- novelwriter/tools/lipsum.py +2 -1
- novelwriter/tools/manuscript.py +1 -1
- novelwriter/tools/manussettings.py +52 -20
- novelwriter/tools/noveldetails.py +9 -8
- novelwriter/tools/welcome.py +1 -0
- novelwriter/tools/writingstats.py +68 -45
- {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/METADATA +2 -2
- {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/RECORD +81 -78
- {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/WHEEL +1 -1
- {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/entry_points.txt +0 -0
- {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/licenses/LICENSE.md +0 -0
- {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/top_level.txt +0 -0
novelwriter/gui/doceditor.py
CHANGED
@@ -3,15 +3,16 @@ novelWriter – GUI Document Editor
|
|
3
3
|
=================================
|
4
4
|
|
5
5
|
File History:
|
6
|
-
Created: 2018-09-29 [0.0.1]
|
7
|
-
Created: 2019-04-22 [0.0.1]
|
8
|
-
Created: 2019-09-29 [0.2.1]
|
9
|
-
Created: 2020-04-25 [0.4.5]
|
10
|
-
Rewritten: 2020-06-15 [0.9]
|
11
|
-
Created: 2020-06-27 [0.10]
|
12
|
-
Rewritten: 2020-10-07 [1.0b3]
|
13
|
-
Created: 2023-11-06 [2.2b1]
|
14
|
-
Created: 2023-11-07 [2.2b1]
|
6
|
+
Created: 2018-09-29 [0.0.1] GuiDocEditor
|
7
|
+
Created: 2019-04-22 [0.0.1] BackgroundWordCounter
|
8
|
+
Created: 2019-09-29 [0.2.1] GuiDocEditSearch
|
9
|
+
Created: 2020-04-25 [0.4.5] GuiDocEditHeader
|
10
|
+
Rewritten: 2020-06-15 [0.9] GuiDocEditSearch
|
11
|
+
Created: 2020-06-27 [0.10] GuiDocEditFooter
|
12
|
+
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
|
13
|
+
Created: 2023-11-06 [2.2b1] MetaCompleter
|
14
|
+
Created: 2023-11-07 [2.2b1] GuiDocToolBar
|
15
|
+
Extended: 2025-05-18 [2.7rc1] CommandCompleter
|
15
16
|
|
16
17
|
This file is a part of novelWriter
|
17
18
|
Copyright (C) 2018 Veronica Berglyd Olsen and novelWriter contributors
|
@@ -55,7 +56,9 @@ from novelwriter import CONFIG, SHARED
|
|
55
56
|
from novelwriter.common import (
|
56
57
|
decodeMimeHandles, fontMatcher, minmax, qtAddAction, qtLambda, transferCase
|
57
58
|
)
|
58
|
-
from novelwriter.constants import
|
59
|
+
from novelwriter.constants import (
|
60
|
+
nwConst, nwKeyWords, nwLabels, nwShortcode, nwStats, nwUnicode, trStats
|
61
|
+
)
|
59
62
|
from novelwriter.core.document import NWDocument
|
60
63
|
from novelwriter.enum import (
|
61
64
|
nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass,
|
@@ -147,7 +150,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
147
150
|
self._autoReplace = TextAutoReplace()
|
148
151
|
|
149
152
|
# Completer
|
150
|
-
self._completer =
|
153
|
+
self._completer = CommandCompleter(self)
|
151
154
|
self._completer.complete.connect(self._insertCompletion)
|
152
155
|
|
153
156
|
# Create Custom Document
|
@@ -321,6 +324,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
321
324
|
"""
|
322
325
|
# Auto-Replace
|
323
326
|
self._autoReplace.initSettings()
|
327
|
+
self.docFooter.initSettings()
|
324
328
|
|
325
329
|
# Reload spell check and dictionaries
|
326
330
|
SHARED.updateSpellCheckLanguage()
|
@@ -1076,18 +1080,24 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1076
1080
|
|
1077
1081
|
if (block := self._qDocument.findBlock(pos)).isValid():
|
1078
1082
|
text = block.text()
|
1079
|
-
if text
|
1083
|
+
if text and text[0] in "@%" and added + removed == 1:
|
1080
1084
|
# Only run on single character changes, or it will trigger
|
1081
1085
|
# at unwanted times when other changes are made to the document
|
1082
1086
|
cursor = self.textCursor()
|
1083
1087
|
bPos = cursor.positionInBlock()
|
1084
1088
|
if bPos > 0 and (viewport := self.viewport()):
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
+
if text[0] == "@":
|
1090
|
+
show = self._completer.updateMetaText(text, bPos)
|
1091
|
+
else:
|
1092
|
+
show = self._completer.updateCommentText(text, bPos)
|
1093
|
+
if show:
|
1094
|
+
point = self.cursorRect().bottomRight()
|
1095
|
+
self._completer.move(viewport.mapToGlobal(point))
|
1096
|
+
self._completer.show()
|
1097
|
+
else:
|
1098
|
+
self._completer.close()
|
1089
1099
|
else:
|
1090
|
-
self._completer.
|
1100
|
+
self._completer.close()
|
1091
1101
|
|
1092
1102
|
if self._doReplace and added == 1:
|
1093
1103
|
cursor = self.textCursor()
|
@@ -1233,7 +1243,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1233
1243
|
"""Process the word counter's finished signal."""
|
1234
1244
|
if self._docHandle and self._nwItem:
|
1235
1245
|
logger.debug("Updating word count")
|
1236
|
-
|
1246
|
+
mCount = cCount if CONFIG.useCharCount else wCount
|
1247
|
+
needsRefresh = mCount != self._nwItem.mainCount
|
1237
1248
|
self._nwItem.setCharCount(cCount)
|
1238
1249
|
self._nwItem.setWordCount(wCount)
|
1239
1250
|
self._nwItem.setParaCount(pCount)
|
@@ -1241,7 +1252,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1241
1252
|
self._nwItem.notifyToRefresh()
|
1242
1253
|
if not self.textCursor().hasSelection():
|
1243
1254
|
# Selection counter should take precedence (#2155)
|
1244
|
-
self.docFooter.
|
1255
|
+
self.docFooter.updateMainCount(mCount, False)
|
1245
1256
|
return
|
1246
1257
|
|
1247
1258
|
@pyqtSlot()
|
@@ -1254,7 +1265,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1254
1265
|
self._timerSel.start()
|
1255
1266
|
else:
|
1256
1267
|
self._timerSel.stop()
|
1257
|
-
self.docFooter.
|
1268
|
+
self.docFooter.updateMainCount(0, False)
|
1258
1269
|
return
|
1259
1270
|
|
1260
1271
|
@pyqtSlot()
|
@@ -1271,8 +1282,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1271
1282
|
def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
|
1272
1283
|
"""Update the counts on the counter's finished signal."""
|
1273
1284
|
if self._docHandle and self._nwItem:
|
1274
|
-
|
1275
|
-
self.docFooter.updateWordCount(wCount, True)
|
1285
|
+
self.docFooter.updateMainCount(cCount if CONFIG.useCharCount else wCount, True)
|
1276
1286
|
self._timerSel.stop()
|
1277
1287
|
return
|
1278
1288
|
|
@@ -2070,13 +2080,13 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2070
2080
|
return
|
2071
2081
|
|
2072
2082
|
|
2073
|
-
class
|
2074
|
-
"""GuiWidget:
|
2083
|
+
class CommandCompleter(QMenu):
|
2084
|
+
"""GuiWidget: Command Completer Menu
|
2075
2085
|
|
2076
2086
|
This is a context menu with options populated from the user's
|
2077
|
-
defined tags. It also helps to type the meta data keyword
|
2078
|
-
line starting with
|
2079
|
-
every keystroke on a line starting with
|
2087
|
+
defined tags and keys. It also helps to type the meta data keyword
|
2088
|
+
on a new line starting with @ or %. The update functions should be
|
2089
|
+
called on every keystroke on a line starting with @ or %.
|
2080
2090
|
"""
|
2081
2091
|
|
2082
2092
|
complete = pyqtSignal(int, int, str)
|
@@ -2085,7 +2095,7 @@ class MetaCompleter(QMenu):
|
|
2085
2095
|
super().__init__(parent=parent)
|
2086
2096
|
return
|
2087
2097
|
|
2088
|
-
def
|
2098
|
+
def updateMetaText(self, text: str, pos: int) -> bool:
|
2089
2099
|
"""Update the menu options based on the line of text."""
|
2090
2100
|
self.clear()
|
2091
2101
|
kw, sep, _ = text.partition(":")
|
@@ -2093,7 +2103,7 @@ class MetaCompleter(QMenu):
|
|
2093
2103
|
offset = 0
|
2094
2104
|
length = len(kw.rstrip())
|
2095
2105
|
suffix = "" if sep else ":"
|
2096
|
-
options =
|
2106
|
+
options = sorted(filter(
|
2097
2107
|
lambda x: x.startswith(kw.rstrip()), nwKeyWords.VALID_KEYS
|
2098
2108
|
))
|
2099
2109
|
else:
|
@@ -2105,22 +2115,64 @@ class MetaCompleter(QMenu):
|
|
2105
2115
|
offset = tPos[index] if lookup else pos
|
2106
2116
|
length = len(lookup)
|
2107
2117
|
suffix = ""
|
2108
|
-
options =
|
2109
|
-
lambda x: lookup in x.lower(), SHARED.project.index.
|
2110
|
-
nwKeyWords.KEY_CLASS.get(kw.strip())
|
2111
|
-
)
|
2118
|
+
options = sorted(filter(
|
2119
|
+
lambda x: lookup in x.lower(), SHARED.project.index.getKeyWordTags(kw.strip())
|
2112
2120
|
))[:15]
|
2113
2121
|
|
2114
2122
|
if not options:
|
2115
2123
|
return False
|
2116
2124
|
|
2117
|
-
for value in
|
2125
|
+
for value in options:
|
2118
2126
|
rep = value + suffix
|
2119
2127
|
action = qtAddAction(self, value)
|
2120
2128
|
action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
|
2121
2129
|
|
2122
2130
|
return True
|
2123
2131
|
|
2132
|
+
def updateCommentText(self, text: str, pos: int) -> bool:
|
2133
|
+
"""Update the menu options based on the line of text."""
|
2134
|
+
self.clear()
|
2135
|
+
cmd, sep, _ = text.partition(":")
|
2136
|
+
if pos <= len(cmd):
|
2137
|
+
clean = text[1:].lstrip()[:6].lower()
|
2138
|
+
if clean[:6] == "story.":
|
2139
|
+
pre, _, key = cmd.partition(".")
|
2140
|
+
offset = len(pre) + 1
|
2141
|
+
length = len(key)
|
2142
|
+
suffix = "" if sep else ": "
|
2143
|
+
options = sorted(filter(
|
2144
|
+
lambda x: x.startswith(key.rstrip()),
|
2145
|
+
SHARED.project.index.getStoryKeys(),
|
2146
|
+
))
|
2147
|
+
elif clean[:5] == "note.":
|
2148
|
+
pre, _, key = cmd.partition(".")
|
2149
|
+
offset = len(pre) + 1
|
2150
|
+
length = len(key)
|
2151
|
+
suffix = "" if sep else ": "
|
2152
|
+
options = sorted(filter(
|
2153
|
+
lambda x: x.startswith(key.rstrip()),
|
2154
|
+
SHARED.project.index.getNoteKeys(),
|
2155
|
+
))
|
2156
|
+
elif pos < 12:
|
2157
|
+
offset = 0
|
2158
|
+
length = len(cmd.rstrip())
|
2159
|
+
suffix = ""
|
2160
|
+
options = list(filter(
|
2161
|
+
lambda x: x.startswith(cmd.rstrip()),
|
2162
|
+
["%Synopsis: ", "%Short: ", "%Story", "%Note"],
|
2163
|
+
))
|
2164
|
+
else:
|
2165
|
+
return False
|
2166
|
+
|
2167
|
+
if options:
|
2168
|
+
for value in options:
|
2169
|
+
rep = value + suffix
|
2170
|
+
action = qtAddAction(self, rep.rstrip(":. "))
|
2171
|
+
action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
|
2172
|
+
return True
|
2173
|
+
|
2174
|
+
return False
|
2175
|
+
|
2124
2176
|
##
|
2125
2177
|
# Events
|
2126
2178
|
##
|
@@ -3045,9 +3097,9 @@ class GuiDocEditFooter(QWidget):
|
|
3045
3097
|
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
3046
3098
|
|
3047
3099
|
# Cached Translations
|
3100
|
+
self.initSettings()
|
3048
3101
|
self._trLineCount = self.tr("Line: {0} ({1})")
|
3049
|
-
self.
|
3050
|
-
self._trSelectCount = self.tr("Words: {0} selected")
|
3102
|
+
self._trSelectCount = self.tr("Selected: {0}")
|
3051
3103
|
|
3052
3104
|
# Main Widget Settings
|
3053
3105
|
self.setContentsMargins(0, 0, 0, 0)
|
@@ -3108,7 +3160,7 @@ class GuiDocEditFooter(QWidget):
|
|
3108
3160
|
self.updateTheme()
|
3109
3161
|
|
3110
3162
|
# Initialise Info
|
3111
|
-
self.
|
3163
|
+
self.updateMainCount(0, False)
|
3112
3164
|
|
3113
3165
|
logger.debug("Ready: GuiDocEditFooter")
|
3114
3166
|
|
@@ -3118,6 +3170,13 @@ class GuiDocEditFooter(QWidget):
|
|
3118
3170
|
# Methods
|
3119
3171
|
##
|
3120
3172
|
|
3173
|
+
def initSettings(self) -> None:
|
3174
|
+
"""Apply user settings."""
|
3175
|
+
self._trMainCount = trStats(nwLabels.STATS_DISPLAY[
|
3176
|
+
nwStats.CHARS if CONFIG.useCharCount else nwStats.WORDS
|
3177
|
+
])
|
3178
|
+
return
|
3179
|
+
|
3121
3180
|
def updateFont(self) -> None:
|
3122
3181
|
"""Update the font settings."""
|
3123
3182
|
self.setFont(SHARED.theme.guiFont)
|
@@ -3162,7 +3221,7 @@ class GuiDocEditFooter(QWidget):
|
|
3162
3221
|
self._tItem = SHARED.project.tree[self._docHandle]
|
3163
3222
|
|
3164
3223
|
self.updateInfo()
|
3165
|
-
self.
|
3224
|
+
self.updateMainCount(0, False)
|
3166
3225
|
|
3167
3226
|
return
|
3168
3227
|
|
@@ -3193,15 +3252,15 @@ class GuiDocEditFooter(QWidget):
|
|
3193
3252
|
)
|
3194
3253
|
return
|
3195
3254
|
|
3196
|
-
def
|
3197
|
-
"""Update
|
3198
|
-
if selection and
|
3199
|
-
|
3255
|
+
def updateMainCount(self, count: int, selection: bool) -> None:
|
3256
|
+
"""Update main counter information."""
|
3257
|
+
if selection and count:
|
3258
|
+
text = self._trSelectCount.format(f"{count:n}")
|
3200
3259
|
elif self._tItem:
|
3201
|
-
|
3202
|
-
|
3203
|
-
|
3260
|
+
count = self._tItem.mainCount
|
3261
|
+
diff = count - self._tItem.initCount
|
3262
|
+
text = self._trMainCount.format(f"{count:n}", f"{diff:+n}")
|
3204
3263
|
else:
|
3205
|
-
|
3206
|
-
self.wordsText.setText(
|
3264
|
+
text = self._trMainCount.format("0", "+0")
|
3265
|
+
self.wordsText.setText(text)
|
3207
3266
|
return
|
novelwriter/gui/noveltree.py
CHANGED
@@ -80,6 +80,7 @@ class GuiNovelView(QWidget):
|
|
80
80
|
# Function Mappings
|
81
81
|
self.setActive = self.novelBar.setActive
|
82
82
|
self.getSelectedHandle = self.novelTree.getSelectedHandle
|
83
|
+
self.refreshCurrentTree = self.novelBar.forceRefreshNovelTree
|
83
84
|
|
84
85
|
return
|
85
86
|
|
@@ -209,7 +210,7 @@ class GuiNovelToolBar(QWidget):
|
|
209
210
|
# Refresh Button
|
210
211
|
self.tbRefresh = NIconToolButton(self, iSz)
|
211
212
|
self.tbRefresh.setToolTip(self.tr("Refresh"))
|
212
|
-
self.tbRefresh.clicked.connect(self.
|
213
|
+
self.tbRefresh.clicked.connect(self.forceRefreshNovelTree)
|
213
214
|
|
214
215
|
# More Options Menu
|
215
216
|
self.mMore = QMenu(self)
|
@@ -274,7 +275,7 @@ class GuiNovelToolBar(QWidget):
|
|
274
275
|
self.novelValue.updateTheme()
|
275
276
|
self.tbNovel.setVisible(self.novelValue.count() > 1)
|
276
277
|
|
277
|
-
self.
|
278
|
+
self.forceRefreshNovelTree()
|
278
279
|
|
279
280
|
return
|
280
281
|
|
@@ -298,6 +299,7 @@ class GuiNovelToolBar(QWidget):
|
|
298
299
|
self.novelValue.setHandle(rootHandle)
|
299
300
|
SHARED.project.data.setLastHandle(rootHandle, "novel")
|
300
301
|
self.novelView.setCurrentNovel(rootHandle)
|
302
|
+
self.novelView.novelTree.setAccessibleName(self.novelValue.currentText())
|
301
303
|
return
|
302
304
|
|
303
305
|
def setLastColType(self, colType: nwNovelExtra, doRefresh: bool = True) -> None:
|
@@ -305,7 +307,7 @@ class GuiNovelToolBar(QWidget):
|
|
305
307
|
self.aLastCol[colType].setChecked(True)
|
306
308
|
self.novelView.novelTree.setLastColType(colType)
|
307
309
|
if doRefresh:
|
308
|
-
self.
|
310
|
+
self.forceRefreshNovelTree()
|
309
311
|
self.novelView.novelTree.resizeColumns()
|
310
312
|
return
|
311
313
|
|
@@ -323,11 +325,11 @@ class GuiNovelToolBar(QWidget):
|
|
323
325
|
return
|
324
326
|
|
325
327
|
##
|
326
|
-
#
|
328
|
+
# Public Slots
|
327
329
|
##
|
328
330
|
|
329
331
|
@pyqtSlot()
|
330
|
-
def
|
332
|
+
def forceRefreshNovelTree(self) -> None:
|
331
333
|
"""Rebuild the current tree."""
|
332
334
|
if tHandle := self.novelValue.handle:
|
333
335
|
self.novelView.setCurrentNovel(tHandle)
|
@@ -335,6 +337,10 @@ class GuiNovelToolBar(QWidget):
|
|
335
337
|
self._refresh[tHandle] = False
|
336
338
|
return
|
337
339
|
|
340
|
+
##
|
341
|
+
# Private Slots
|
342
|
+
##
|
343
|
+
|
338
344
|
@pyqtSlot(str)
|
339
345
|
def _refreshNovelTree(self, tHandle: str) -> None:
|
340
346
|
"""Refresh or schedule refresh of a novel tree."""
|
novelwriter/gui/outline.py
CHANGED
@@ -751,9 +751,13 @@ class GuiOutlineTree(QTreeWidget):
|
|
751
751
|
def _dumpNovelData(self, rootHandle: str | None) -> list[list[str | int]]:
|
752
752
|
"""Dump all novel data into a table."""
|
753
753
|
sLabel = SHARED.project.localLookup("Story Structure")
|
754
|
+
nLabel = SHARED.project.localLookup("Note")
|
754
755
|
sKeys = sorted(SHARED.project.index.getStoryKeys())
|
756
|
+
nKeys = sorted(SHARED.project.index.getNoteKeys())
|
755
757
|
sMatch = [f"story.{k}" for k in sKeys]
|
758
|
+
nMatch = [f"note.{k}" for k in nKeys]
|
756
759
|
sHeaders = [f"{sLabel} ({k})" for k in sKeys]
|
760
|
+
nHeaders = [f"{nLabel} ({k})" for k in nKeys]
|
757
761
|
|
758
762
|
data: list[list[str | int]] = [[
|
759
763
|
"H",
|
@@ -777,6 +781,7 @@ class GuiOutlineTree(QTreeWidget):
|
|
777
781
|
trConst(nwLabels.OUTLINE_COLS[nwOutline.MENTION]),
|
778
782
|
trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP]),
|
779
783
|
*sHeaders,
|
784
|
+
*nHeaders,
|
780
785
|
]]
|
781
786
|
|
782
787
|
for _, tHandle, sTitle, novIdx in SHARED.project.index.novelStructure(
|
@@ -784,7 +789,9 @@ class GuiOutlineTree(QTreeWidget):
|
|
784
789
|
):
|
785
790
|
if novIdx.level != "H0" and (nwItem := SHARED.project.tree[tHandle]):
|
786
791
|
refs = SHARED.project.index.getReferences(tHandle, sTitle)
|
787
|
-
|
792
|
+
comments = dict(novIdx.comments.items())
|
793
|
+
story = [comments.get(k, "") for k in sMatch]
|
794
|
+
notes = [comments.get(k, "") for k in nMatch]
|
788
795
|
data.append([
|
789
796
|
novIdx.level,
|
790
797
|
novIdx.title,
|
@@ -807,6 +814,7 @@ class GuiOutlineTree(QTreeWidget):
|
|
807
814
|
", ".join(refs[nwKeyWords.MENTION_KEY]),
|
808
815
|
novIdx.synopsis,
|
809
816
|
*story,
|
817
|
+
*notes,
|
810
818
|
])
|
811
819
|
|
812
820
|
return data
|
novelwriter/gui/projtree.py
CHANGED
@@ -254,6 +254,7 @@ class GuiProjectToolBar(QWidget):
|
|
254
254
|
self.viewLabel.setFont(SHARED.theme.guiFontB)
|
255
255
|
self.viewLabel.setContentsMargins(0, 0, 0, 0)
|
256
256
|
self.viewLabel.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
|
257
|
+
self.projTree.setAccessibleName(self.viewLabel.text())
|
257
258
|
|
258
259
|
# Quick Links
|
259
260
|
self.mQuick = QMenu(self)
|
novelwriter/gui/search.py
CHANGED
@@ -125,6 +125,7 @@ class GuiProjectSearch(QWidget):
|
|
125
125
|
self.searchResult.setAllColumnsShowFocus(True)
|
126
126
|
self.searchResult.itemDoubleClicked.connect(self._searchResultDoubleClicked)
|
127
127
|
self.searchResult.itemSelectionChanged.connect(self._searchResultSelected)
|
128
|
+
self.searchResult.setAccessibleName(self.viewLabel.text())
|
128
129
|
|
129
130
|
if header := self.searchResult.header():
|
130
131
|
header.setStretchLastSection(False)
|
novelwriter/gui/statusbar.py
CHANGED
@@ -33,7 +33,7 @@ from PyQt6.QtWidgets import QApplication, QLabel, QStatusBar, QWidget
|
|
33
33
|
|
34
34
|
from novelwriter import CONFIG, SHARED
|
35
35
|
from novelwriter.common import formatTime
|
36
|
-
from novelwriter.constants import nwConst
|
36
|
+
from novelwriter.constants import nwConst, nwLabels, nwStats, trStats
|
37
37
|
from novelwriter.extensions.modified import NClickableLabel
|
38
38
|
from novelwriter.extensions.statusled import StatusLED
|
39
39
|
|
@@ -108,11 +108,22 @@ class GuiMainStatus(QStatusBar):
|
|
108
108
|
|
109
109
|
logger.debug("Ready: GuiMainStatus")
|
110
110
|
|
111
|
+
self.initSettings()
|
111
112
|
self.updateTheme()
|
112
113
|
self.clearStatus()
|
113
114
|
|
114
115
|
return
|
115
116
|
|
117
|
+
def initSettings(self) -> None:
|
118
|
+
"""Apply user settings."""
|
119
|
+
if CONFIG.useCharCount:
|
120
|
+
self._trStatsCount = trStats(nwLabels.STATS_DISPLAY[nwStats.CHARS])
|
121
|
+
self._trStatsTip = self.tr("Total character count (session change)")
|
122
|
+
else:
|
123
|
+
self._trStatsCount = trStats(nwLabels.STATS_DISPLAY[nwStats.WORDS])
|
124
|
+
self._trStatsTip = self.tr("Total word count (session change)")
|
125
|
+
return
|
126
|
+
|
116
127
|
def clearStatus(self) -> None:
|
117
128
|
"""Reset all widgets on the status bar to default values."""
|
118
129
|
self.setRefTime(-1.0)
|
@@ -173,11 +184,8 @@ class GuiMainStatus(QStatusBar):
|
|
173
184
|
|
174
185
|
def setProjectStats(self, pWC: int, sWC: int) -> None:
|
175
186
|
"""Update the current project statistics."""
|
176
|
-
self.statsText.setText(self.
|
177
|
-
|
178
|
-
self.statsText.setToolTip(self.tr("Project word count (session change)"))
|
179
|
-
else:
|
180
|
-
self.statsText.setToolTip(self.tr("Novel word count (session change)"))
|
187
|
+
self.statsText.setText(self._trStatsCount.format(f"{pWC:n}", f"{sWC:+n}"))
|
188
|
+
self.statsText.setToolTip(self._trStatsTip)
|
181
189
|
return
|
182
190
|
|
183
191
|
def updateTime(self, idleTime: float = 0.0) -> None:
|
novelwriter/gui/theme.py
CHANGED
@@ -130,14 +130,6 @@ class GuiTheme:
|
|
130
130
|
self._availSyntax: dict[str, Path] = {}
|
131
131
|
self._styleSheets: dict[str, str] = {}
|
132
132
|
|
133
|
-
_listConf(self._availSyntax, CONFIG.assetPath("syntax"), ".conf")
|
134
|
-
_listConf(self._availThemes, CONFIG.assetPath("themes"), ".conf")
|
135
|
-
_listConf(self._availSyntax, CONFIG.dataPath("syntax"), ".conf")
|
136
|
-
_listConf(self._availThemes, CONFIG.dataPath("themes"), ".conf")
|
137
|
-
|
138
|
-
self.loadTheme()
|
139
|
-
self.loadSyntax()
|
140
|
-
|
141
133
|
# Icon Functions
|
142
134
|
self.getIcon = self.iconCache.getIcon
|
143
135
|
self.getPixmap = self.iconCache.getPixmap
|
@@ -186,6 +178,15 @@ class GuiTheme:
|
|
186
178
|
logger.debug("Text 'N' Height: %d", self.textNHeight)
|
187
179
|
logger.debug("Text 'N' Width: %d", self.textNWidth)
|
188
180
|
|
181
|
+
# Process Themes
|
182
|
+
_listConf(self._availSyntax, CONFIG.assetPath("syntax"), ".conf")
|
183
|
+
_listConf(self._availThemes, CONFIG.assetPath("themes"), ".conf")
|
184
|
+
_listConf(self._availSyntax, CONFIG.dataPath("syntax"), ".conf")
|
185
|
+
_listConf(self._availThemes, CONFIG.dataPath("themes"), ".conf")
|
186
|
+
|
187
|
+
self.loadTheme()
|
188
|
+
self.loadSyntax()
|
189
|
+
|
189
190
|
return
|
190
191
|
|
191
192
|
##
|
@@ -218,6 +219,7 @@ class GuiTheme:
|
|
218
219
|
logger.error("Could not load GUI theme")
|
219
220
|
return False
|
220
221
|
|
222
|
+
CONFIG.splashMessage("Loading GUI theme ...")
|
221
223
|
logger.info("Loading GUI theme '%s'", theme)
|
222
224
|
parser = NWConfigParser()
|
223
225
|
try:
|
@@ -357,6 +359,8 @@ class GuiTheme:
|
|
357
359
|
QApplication.setPalette(self._guiPalette)
|
358
360
|
self._buildStyleSheets(self._guiPalette)
|
359
361
|
|
362
|
+
CONFIG.splashMessage(f"Loaded GUI theme: {meta.name}")
|
363
|
+
|
360
364
|
return True
|
361
365
|
|
362
366
|
def loadSyntax(self) -> bool:
|
@@ -371,6 +375,7 @@ class GuiTheme:
|
|
371
375
|
logger.error("Could not load syntax theme")
|
372
376
|
return False
|
373
377
|
|
378
|
+
CONFIG.splashMessage("Loading syntax theme ...")
|
374
379
|
logger.info("Loading syntax theme '%s'", theme)
|
375
380
|
parser = NWConfigParser()
|
376
381
|
try:
|
@@ -418,6 +423,8 @@ class GuiTheme:
|
|
418
423
|
syntax.mod = self._parseColor(parser, sec, "modifier")
|
419
424
|
syntax.mark = self._parseColor(parser, sec, "texthighlight")
|
420
425
|
|
426
|
+
CONFIG.splashMessage(f"Loaded syntax theme: {meta.name}")
|
427
|
+
|
421
428
|
self.syntaxMeta = meta
|
422
429
|
self.syntaxTheme = syntax
|
423
430
|
|
@@ -633,6 +640,7 @@ class GuiIcons:
|
|
633
640
|
logger.error("Could not load icon theme")
|
634
641
|
return False
|
635
642
|
|
643
|
+
CONFIG.splashMessage("Loading icon theme ...")
|
636
644
|
logger.info("Loading icon theme '%s'", theme)
|
637
645
|
try:
|
638
646
|
meta = ThemeMeta()
|
@@ -656,6 +664,9 @@ class GuiIcons:
|
|
656
664
|
logException()
|
657
665
|
return False
|
658
666
|
|
667
|
+
CONFIG.splashMessage(f"Loaded icon theme: {meta.name}")
|
668
|
+
CONFIG.splashMessage("Generating additional icons ...")
|
669
|
+
|
659
670
|
# Set colour overrides for project item icons
|
660
671
|
if (override := CONFIG.iconColTree) != "theme":
|
661
672
|
color = self._svgColors.get(override, b"#000000")
|
@@ -668,6 +679,10 @@ class GuiIcons:
|
|
668
679
|
self._svgColors["scene"] = color
|
669
680
|
self._svgColors["note"] = color
|
670
681
|
|
682
|
+
# Populate generated icons cache
|
683
|
+
self.getHeaderDecoration(0)
|
684
|
+
self.getHeaderDecorationNarrow(0)
|
685
|
+
|
671
686
|
return True
|
672
687
|
|
673
688
|
def setIconColor(self, key: str, color: QColor) -> None:
|
@@ -822,8 +837,8 @@ class GuiIcons:
|
|
822
837
|
##
|
823
838
|
|
824
839
|
def _loadIcon(self, name: str, color: str | None = None, w: int = 24, h: int = 24) -> QIcon:
|
825
|
-
"""Load an icon from the assets themes folder.
|
826
|
-
return a QIcon.
|
840
|
+
"""Load an icon from the assets themes folder. This function is
|
841
|
+
guaranteed to return a QIcon.
|
827
842
|
"""
|
828
843
|
# If we just want the app icons, return right away
|
829
844
|
if name == "novelwriter":
|
novelwriter/guimain.py
CHANGED
@@ -310,11 +310,11 @@ class GuiMain(QMainWindow):
|
|
310
310
|
self.asProjTimer.start()
|
311
311
|
self.asDocTimer.start()
|
312
312
|
self.mainStatus.clearStatus()
|
313
|
-
self.showNormal()
|
314
313
|
|
315
314
|
logger.debug("Ready: GUI")
|
316
315
|
logger.info("novelWriter is ready ...")
|
317
316
|
self.mainStatus.setStatusMessage(self.tr("novelWriter is ready ..."))
|
317
|
+
CONFIG.splashMessage("novelWriter is ready ...")
|
318
318
|
|
319
319
|
return
|
320
320
|
|
@@ -340,6 +340,15 @@ class GuiMain(QMainWindow):
|
|
340
340
|
logger.info("Command line path: %s", cmdOpen)
|
341
341
|
self.openProject(cmdOpen)
|
342
342
|
|
343
|
+
# Add a small delay for the window coordinates to be ready
|
344
|
+
# before showing any dialogs
|
345
|
+
QTimer.singleShot(50, self.showPostLaunchDialogs)
|
346
|
+
|
347
|
+
return
|
348
|
+
|
349
|
+
@pyqtSlot()
|
350
|
+
def showPostLaunchDialogs(self) -> None:
|
351
|
+
"""Show post launch dialogs."""
|
343
352
|
if not SHARED.hasProject:
|
344
353
|
self.showWelcomeDialog()
|
345
354
|
|
@@ -1039,8 +1048,10 @@ class GuiMain(QMainWindow):
|
|
1039
1048
|
self.initMain()
|
1040
1049
|
self.saveDocument()
|
1041
1050
|
|
1042
|
-
if tree:
|
1051
|
+
if tree and not theme:
|
1052
|
+
# These are also updated by a theme refresh
|
1043
1053
|
SHARED.project.tree.refreshAllItems()
|
1054
|
+
self.novelView.refreshCurrentTree()
|
1044
1055
|
|
1045
1056
|
if theme:
|
1046
1057
|
SHARED.theme.loadTheme()
|
@@ -1066,6 +1077,7 @@ class GuiMain(QMainWindow):
|
|
1066
1077
|
self.projView.initSettings()
|
1067
1078
|
self.novelView.initSettings()
|
1068
1079
|
self.outlineView.initSettings()
|
1080
|
+
self.mainStatus.initSettings()
|
1069
1081
|
|
1070
1082
|
# Force update of word count
|
1071
1083
|
self._lastTotalCount = 0
|
@@ -1252,15 +1264,23 @@ class GuiMain(QMainWindow):
|
|
1252
1264
|
if self._lastTotalCount != currentTotalCount:
|
1253
1265
|
self._lastTotalCount = currentTotalCount
|
1254
1266
|
|
1255
|
-
SHARED.project.
|
1267
|
+
SHARED.project.updateCounts()
|
1256
1268
|
if CONFIG.incNotesWCount:
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1269
|
+
if CONFIG.useCharCount:
|
1270
|
+
iTotal = sum(SHARED.project.data.initCounts[2:])
|
1271
|
+
cTotal = sum(SHARED.project.data.currCounts[2:])
|
1272
|
+
else:
|
1273
|
+
iTotal = sum(SHARED.project.data.initCounts[:2])
|
1274
|
+
cTotal = sum(SHARED.project.data.currCounts[:2])
|
1260
1275
|
else:
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1276
|
+
if CONFIG.useCharCount:
|
1277
|
+
iTotal = SHARED.project.data.initCounts[2]
|
1278
|
+
cTotal = SHARED.project.data.currCounts[2]
|
1279
|
+
else:
|
1280
|
+
iTotal = SHARED.project.data.initCounts[0]
|
1281
|
+
cTotal = SHARED.project.data.currCounts[0]
|
1282
|
+
|
1283
|
+
self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
|
1264
1284
|
|
1265
1285
|
return
|
1266
1286
|
|