novelWriter 2.7b1__py3-none-any.whl → 2.7rc1__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/project_en_GB.json +1 -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/themes/snazzy.conf +48 -0
- novelwriter/common.py +10 -1
- novelwriter/config.py +96 -25
- novelwriter/constants.py +17 -0
- novelwriter/core/buildsettings.py +2 -0
- novelwriter/core/coretools.py +41 -34
- novelwriter/core/docbuild.py +1 -0
- novelwriter/core/index.py +25 -1
- 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 +98 -40
- 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 +38 -13
- 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.7rc1.dist-info}/METADATA +1 -1
- {novelwriter-2.7b1.dist-info → novelwriter-2.7rc1.dist-info}/RECORD +56 -53
- {novelwriter-2.7b1.dist-info → novelwriter-2.7rc1.dist-info}/WHEEL +1 -1
- {novelwriter-2.7b1.dist-info → novelwriter-2.7rc1.dist-info}/entry_points.txt +0 -0
- {novelwriter-2.7b1.dist-info → novelwriter-2.7rc1.dist-info}/licenses/LICENSE.md +0 -0
- {novelwriter-2.7b1.dist-info → novelwriter-2.7rc1.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,13 +1080,16 @@ 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
|
-
|
1089
|
+
if text[0] == "@":
|
1090
|
+
show = self._completer.updateMetaText(text, bPos)
|
1091
|
+
else:
|
1092
|
+
show = self._completer.updateCommentText(text, bPos)
|
1086
1093
|
point = self.cursorRect().bottomRight()
|
1087
1094
|
self._completer.move(viewport.mapToGlobal(point))
|
1088
1095
|
self._completer.setVisible(show)
|
@@ -1233,7 +1240,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1233
1240
|
"""Process the word counter's finished signal."""
|
1234
1241
|
if self._docHandle and self._nwItem:
|
1235
1242
|
logger.debug("Updating word count")
|
1236
|
-
|
1243
|
+
mCount = cCount if CONFIG.useCharCount else wCount
|
1244
|
+
needsRefresh = mCount != self._nwItem.mainCount
|
1237
1245
|
self._nwItem.setCharCount(cCount)
|
1238
1246
|
self._nwItem.setWordCount(wCount)
|
1239
1247
|
self._nwItem.setParaCount(pCount)
|
@@ -1241,7 +1249,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1241
1249
|
self._nwItem.notifyToRefresh()
|
1242
1250
|
if not self.textCursor().hasSelection():
|
1243
1251
|
# Selection counter should take precedence (#2155)
|
1244
|
-
self.docFooter.
|
1252
|
+
self.docFooter.updateMainCount(mCount, False)
|
1245
1253
|
return
|
1246
1254
|
|
1247
1255
|
@pyqtSlot()
|
@@ -1254,7 +1262,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1254
1262
|
self._timerSel.start()
|
1255
1263
|
else:
|
1256
1264
|
self._timerSel.stop()
|
1257
|
-
self.docFooter.
|
1265
|
+
self.docFooter.updateMainCount(0, False)
|
1258
1266
|
return
|
1259
1267
|
|
1260
1268
|
@pyqtSlot()
|
@@ -1271,8 +1279,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1271
1279
|
def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
|
1272
1280
|
"""Update the counts on the counter's finished signal."""
|
1273
1281
|
if self._docHandle and self._nwItem:
|
1274
|
-
|
1275
|
-
self.docFooter.updateWordCount(wCount, True)
|
1282
|
+
self.docFooter.updateMainCount(cCount if CONFIG.useCharCount else wCount, True)
|
1276
1283
|
self._timerSel.stop()
|
1277
1284
|
return
|
1278
1285
|
|
@@ -2070,13 +2077,13 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2070
2077
|
return
|
2071
2078
|
|
2072
2079
|
|
2073
|
-
class
|
2074
|
-
"""GuiWidget:
|
2080
|
+
class CommandCompleter(QMenu):
|
2081
|
+
"""GuiWidget: Command Completer Menu
|
2075
2082
|
|
2076
2083
|
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
|
2084
|
+
defined tags and keys. It also helps to type the meta data keyword
|
2085
|
+
on a new line starting with @ or %. The update functions should be
|
2086
|
+
called on every keystroke on a line starting with @ or %.
|
2080
2087
|
"""
|
2081
2088
|
|
2082
2089
|
complete = pyqtSignal(int, int, str)
|
@@ -2085,7 +2092,7 @@ class MetaCompleter(QMenu):
|
|
2085
2092
|
super().__init__(parent=parent)
|
2086
2093
|
return
|
2087
2094
|
|
2088
|
-
def
|
2095
|
+
def updateMetaText(self, text: str, pos: int) -> bool:
|
2089
2096
|
"""Update the menu options based on the line of text."""
|
2090
2097
|
self.clear()
|
2091
2098
|
kw, sep, _ = text.partition(":")
|
@@ -2093,7 +2100,7 @@ class MetaCompleter(QMenu):
|
|
2093
2100
|
offset = 0
|
2094
2101
|
length = len(kw.rstrip())
|
2095
2102
|
suffix = "" if sep else ":"
|
2096
|
-
options =
|
2103
|
+
options = sorted(filter(
|
2097
2104
|
lambda x: x.startswith(kw.rstrip()), nwKeyWords.VALID_KEYS
|
2098
2105
|
))
|
2099
2106
|
else:
|
@@ -2105,7 +2112,7 @@ class MetaCompleter(QMenu):
|
|
2105
2112
|
offset = tPos[index] if lookup else pos
|
2106
2113
|
length = len(lookup)
|
2107
2114
|
suffix = ""
|
2108
|
-
options =
|
2115
|
+
options = sorted(filter(
|
2109
2116
|
lambda x: lookup in x.lower(), SHARED.project.index.getClassTags(
|
2110
2117
|
nwKeyWords.KEY_CLASS.get(kw.strip())
|
2111
2118
|
)
|
@@ -2114,13 +2121,57 @@ class MetaCompleter(QMenu):
|
|
2114
2121
|
if not options:
|
2115
2122
|
return False
|
2116
2123
|
|
2117
|
-
for value in
|
2124
|
+
for value in options:
|
2118
2125
|
rep = value + suffix
|
2119
2126
|
action = qtAddAction(self, value)
|
2120
2127
|
action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
|
2121
2128
|
|
2122
2129
|
return True
|
2123
2130
|
|
2131
|
+
def updateCommentText(self, text: str, pos: int) -> bool:
|
2132
|
+
"""Update the menu options based on the line of text."""
|
2133
|
+
self.clear()
|
2134
|
+
cmd, sep, _ = text.partition(":")
|
2135
|
+
if pos <= len(cmd):
|
2136
|
+
clean = text[1:].lstrip()[:6].lower()
|
2137
|
+
if clean[:6] == "story.":
|
2138
|
+
pre, _, key = cmd.partition(".")
|
2139
|
+
offset = len(pre) + 1
|
2140
|
+
length = len(key)
|
2141
|
+
suffix = "" if sep else ": "
|
2142
|
+
options = sorted(filter(
|
2143
|
+
lambda x: x.startswith(key.rstrip()),
|
2144
|
+
SHARED.project.index.getStoryKeys(),
|
2145
|
+
))
|
2146
|
+
elif clean[:5] == "note.":
|
2147
|
+
pre, _, key = cmd.partition(".")
|
2148
|
+
offset = len(pre) + 1
|
2149
|
+
length = len(key)
|
2150
|
+
suffix = "" if sep else ": "
|
2151
|
+
options = sorted(filter(
|
2152
|
+
lambda x: x.startswith(key.rstrip()),
|
2153
|
+
SHARED.project.index.getNoteKeys(),
|
2154
|
+
))
|
2155
|
+
elif pos < 12:
|
2156
|
+
offset = 0
|
2157
|
+
length = len(cmd.rstrip())
|
2158
|
+
suffix = ""
|
2159
|
+
options = list(filter(
|
2160
|
+
lambda x: x.startswith(cmd.rstrip()),
|
2161
|
+
["%Synopsis: ", "%Short: ", "%Story", "%Note"],
|
2162
|
+
))
|
2163
|
+
else:
|
2164
|
+
return False
|
2165
|
+
|
2166
|
+
if options:
|
2167
|
+
for value in options:
|
2168
|
+
rep = value + suffix
|
2169
|
+
action = qtAddAction(self, rep.rstrip(":. "))
|
2170
|
+
action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
|
2171
|
+
return True
|
2172
|
+
|
2173
|
+
return False
|
2174
|
+
|
2124
2175
|
##
|
2125
2176
|
# Events
|
2126
2177
|
##
|
@@ -3045,9 +3096,9 @@ class GuiDocEditFooter(QWidget):
|
|
3045
3096
|
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
3046
3097
|
|
3047
3098
|
# Cached Translations
|
3099
|
+
self.initSettings()
|
3048
3100
|
self._trLineCount = self.tr("Line: {0} ({1})")
|
3049
|
-
self.
|
3050
|
-
self._trSelectCount = self.tr("Words: {0} selected")
|
3101
|
+
self._trSelectCount = self.tr("Selected: {0}")
|
3051
3102
|
|
3052
3103
|
# Main Widget Settings
|
3053
3104
|
self.setContentsMargins(0, 0, 0, 0)
|
@@ -3108,7 +3159,7 @@ class GuiDocEditFooter(QWidget):
|
|
3108
3159
|
self.updateTheme()
|
3109
3160
|
|
3110
3161
|
# Initialise Info
|
3111
|
-
self.
|
3162
|
+
self.updateMainCount(0, False)
|
3112
3163
|
|
3113
3164
|
logger.debug("Ready: GuiDocEditFooter")
|
3114
3165
|
|
@@ -3118,6 +3169,13 @@ class GuiDocEditFooter(QWidget):
|
|
3118
3169
|
# Methods
|
3119
3170
|
##
|
3120
3171
|
|
3172
|
+
def initSettings(self) -> None:
|
3173
|
+
"""Apply user settings."""
|
3174
|
+
self._trMainCount = trStats(nwLabels.STATS_DISPLAY[
|
3175
|
+
nwStats.CHARS if CONFIG.useCharCount else nwStats.WORDS
|
3176
|
+
])
|
3177
|
+
return
|
3178
|
+
|
3121
3179
|
def updateFont(self) -> None:
|
3122
3180
|
"""Update the font settings."""
|
3123
3181
|
self.setFont(SHARED.theme.guiFont)
|
@@ -3162,7 +3220,7 @@ class GuiDocEditFooter(QWidget):
|
|
3162
3220
|
self._tItem = SHARED.project.tree[self._docHandle]
|
3163
3221
|
|
3164
3222
|
self.updateInfo()
|
3165
|
-
self.
|
3223
|
+
self.updateMainCount(0, False)
|
3166
3224
|
|
3167
3225
|
return
|
3168
3226
|
|
@@ -3193,15 +3251,15 @@ class GuiDocEditFooter(QWidget):
|
|
3193
3251
|
)
|
3194
3252
|
return
|
3195
3253
|
|
3196
|
-
def
|
3197
|
-
"""Update
|
3198
|
-
if selection and
|
3199
|
-
|
3254
|
+
def updateMainCount(self, count: int, selection: bool) -> None:
|
3255
|
+
"""Update main counter information."""
|
3256
|
+
if selection and count:
|
3257
|
+
text = self._trSelectCount.format(f"{count:n}")
|
3200
3258
|
elif self._tItem:
|
3201
|
-
|
3202
|
-
|
3203
|
-
|
3259
|
+
count = self._tItem.mainCount
|
3260
|
+
diff = count - self._tItem.initCount
|
3261
|
+
text = self._trMainCount.format(f"{count:n}", f"{diff:+n}")
|
3204
3262
|
else:
|
3205
|
-
|
3206
|
-
self.wordsText.setText(
|
3263
|
+
text = self._trMainCount.format("0", "+0")
|
3264
|
+
self.wordsText.setText(text)
|
3207
3265
|
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
|
|