novelWriter 2.4rc1__py3-none-any.whl → 2.4.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.4rc1.dist-info → novelWriter-2.4.1.dist-info}/METADATA +1 -1
- {novelWriter-2.4rc1.dist-info → novelWriter-2.4.1.dist-info}/RECORD +48 -48
- novelwriter/__init__.py +13 -6
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/common.py +5 -2
- novelwriter/config.py +4 -0
- novelwriter/core/buildsettings.py +7 -7
- novelwriter/core/docbuild.py +2 -2
- novelwriter/core/projectxml.py +1 -1
- novelwriter/core/spellcheck.py +3 -3
- novelwriter/core/tokenizer.py +3 -3
- novelwriter/core/toodt.py +1 -1
- novelwriter/dialogs/preferences.py +1 -1
- novelwriter/dialogs/projectsettings.py +2 -2
- novelwriter/dialogs/wordlist.py +2 -2
- novelwriter/error.py +1 -1
- novelwriter/gui/doceditor.py +27 -25
- novelwriter/gui/dochighlight.py +22 -5
- novelwriter/gui/docviewer.py +3 -3
- novelwriter/gui/mainmenu.py +2 -2
- novelwriter/gui/noveltree.py +1 -1
- novelwriter/gui/outline.py +9 -9
- novelwriter/gui/projtree.py +1 -1
- novelwriter/gui/search.py +2 -2
- novelwriter/gui/theme.py +15 -5
- novelwriter/guimain.py +2 -2
- novelwriter/shared.py +16 -4
- novelwriter/text/counting.py +1 -0
- novelwriter/tools/dictionaries.py +2 -2
- novelwriter/tools/manusbuild.py +12 -11
- novelwriter/tools/manuscript.py +48 -43
- novelwriter/tools/manussettings.py +27 -29
- novelwriter/tools/welcome.py +4 -5
- {novelWriter-2.4rc1.dist-info → novelWriter-2.4.1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.4rc1.dist-info → novelWriter-2.4.1.dist-info}/WHEEL +0 -0
- {novelWriter-2.4rc1.dist-info → novelWriter-2.4.1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.4rc1.dist-info → novelWriter-2.4.1.dist-info}/top_level.txt +0 -0
novelwriter/gui/doceditor.py
CHANGED
@@ -39,8 +39,8 @@ from time import time
|
|
39
39
|
from typing import TYPE_CHECKING
|
40
40
|
|
41
41
|
from PyQt5.QtCore import (
|
42
|
-
|
43
|
-
|
42
|
+
QObject, QPoint, QRegExp, QRegularExpression, QRunnable, Qt, QTimer,
|
43
|
+
pyqtSignal, pyqtSlot
|
44
44
|
)
|
45
45
|
from PyQt5.QtGui import (
|
46
46
|
QColor, QCursor, QFont, QKeyEvent, QKeySequence, QMouseEvent, QPalette,
|
@@ -65,7 +65,7 @@ from novelwriter.text.counting import standardCounter
|
|
65
65
|
from novelwriter.tools.lipsum import GuiLipsum
|
66
66
|
from novelwriter.types import (
|
67
67
|
QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop,
|
68
|
-
QtAlignRight, QtKeepAnchor, QtModCtrl,
|
68
|
+
QtAlignRight, QtKeepAnchor, QtModCtrl, QtModeNone, QtModShift, QtMouseLeft,
|
69
69
|
QtMoveAnchor, QtMoveLeft, QtMoveRight
|
70
70
|
)
|
71
71
|
|
@@ -334,7 +334,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
334
334
|
font = QFont()
|
335
335
|
font.setFamily(CONFIG.textFont)
|
336
336
|
font.setPointSize(CONFIG.textSize)
|
337
|
-
self.
|
337
|
+
self._qDocument.setDefaultFont(font)
|
338
338
|
|
339
339
|
# Set default text margins
|
340
340
|
# Due to cursor visibility, a part of the margin must be
|
@@ -381,7 +381,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
381
381
|
|
382
382
|
return
|
383
383
|
|
384
|
-
def loadText(self, tHandle: str, tLine=None) -> bool:
|
384
|
+
def loadText(self, tHandle: str, tLine: int | None = None) -> bool:
|
385
385
|
"""Load text from a document into the editor. If we have an I/O
|
386
386
|
error, we must handle this and clear the editor so that we don't
|
387
387
|
risk overwriting the file if it exists. This can for instance
|
@@ -497,6 +497,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
497
497
|
return False
|
498
498
|
|
499
499
|
self.setDocumentChanged(False)
|
500
|
+
self.docTextChanged.emit(self._docHandle, self._lastEdit)
|
500
501
|
|
501
502
|
oldHeader = self._nwItem.mainHeading
|
502
503
|
oldCount = SHARED.project.index.getHandleHeaderCount(tHandle)
|
@@ -1080,7 +1081,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1080
1081
|
return
|
1081
1082
|
|
1082
1083
|
@pyqtSlot()
|
1083
|
-
def _cursorMoved(self):
|
1084
|
+
def _cursorMoved(self) -> None:
|
1084
1085
|
"""Triggered when the cursor moved in the editor."""
|
1085
1086
|
self.docFooter.updateLineCount(self.textCursor())
|
1086
1087
|
return
|
@@ -1470,7 +1471,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1470
1471
|
# Internal Functions : Text Manipulation
|
1471
1472
|
##
|
1472
1473
|
|
1473
|
-
def _toggleFormat(self, fLen: int, fChar: str) ->
|
1474
|
+
def _toggleFormat(self, fLen: int, fChar: str) -> None:
|
1474
1475
|
"""Toggle the formatting of a specific type for a piece of text.
|
1475
1476
|
If more than one block is selected, the formatting is applied to
|
1476
1477
|
the first block.
|
@@ -1488,12 +1489,12 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1488
1489
|
|
1489
1490
|
posS = cursor.selectionStart()
|
1490
1491
|
posE = cursor.selectionEnd()
|
1491
|
-
if self._qDocument.characterAt(posO - 1) == fChar:
|
1492
|
+
if posS == posE and self._qDocument.characterAt(posO - 1) == fChar:
|
1492
1493
|
logger.warning("Format repetition, cancelling action")
|
1493
1494
|
cursor.clearSelection()
|
1494
1495
|
cursor.setPosition(posO)
|
1495
1496
|
self.setTextCursor(cursor)
|
1496
|
-
return
|
1497
|
+
return
|
1497
1498
|
|
1498
1499
|
blockS = self._qDocument.findBlock(posS)
|
1499
1500
|
blockE = self._qDocument.findBlock(posE)
|
@@ -1519,7 +1520,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1519
1520
|
break
|
1520
1521
|
|
1521
1522
|
if fLen == min(numA, numB):
|
1522
|
-
cursor.clearSelection()
|
1523
1523
|
cursor.beginEditBlock()
|
1524
1524
|
cursor.setPosition(posS)
|
1525
1525
|
for i in range(fLen):
|
@@ -1528,17 +1528,19 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1528
1528
|
for i in range(fLen):
|
1529
1529
|
cursor.deletePreviousChar()
|
1530
1530
|
cursor.endEditBlock()
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1531
|
+
|
1532
|
+
if select != _SelectAction.KEEP_SELECTION:
|
1533
|
+
cursor.clearSelection()
|
1534
|
+
cursor.setPosition(posO - fLen)
|
1535
|
+
self.setTextCursor(cursor)
|
1534
1536
|
|
1535
1537
|
else:
|
1536
1538
|
self._wrapSelection(fChar*fLen, pos=posO, select=select)
|
1537
1539
|
|
1538
|
-
return
|
1540
|
+
return
|
1539
1541
|
|
1540
1542
|
def _wrapSelection(self, before: str, after: str | None = None, pos: int | None = None,
|
1541
|
-
select: _SelectAction = _SelectAction.NO_DECISION) ->
|
1543
|
+
select: _SelectAction = _SelectAction.NO_DECISION) -> None:
|
1542
1544
|
"""Wrap the selected text in whatever is in tBefore and tAfter.
|
1543
1545
|
If there is no selection, the autoSelect setting decides the
|
1544
1546
|
action. AutoSelect will select the word under the cursor before
|
@@ -1578,21 +1580,21 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1578
1580
|
if select == _SelectAction.MOVE_AFTER:
|
1579
1581
|
cursor.setPosition(posE + len(before + after))
|
1580
1582
|
elif select == _SelectAction.KEEP_SELECTION:
|
1581
|
-
cursor.setPosition(
|
1582
|
-
cursor.setPosition(
|
1583
|
+
cursor.setPosition(posS + len(before), QtMoveAnchor)
|
1584
|
+
cursor.setPosition(posE + len(before), QtKeepAnchor)
|
1583
1585
|
elif select == _SelectAction.KEEP_POSITION:
|
1584
1586
|
cursor.setPosition(posO + len(before))
|
1585
1587
|
|
1586
1588
|
self.setTextCursor(cursor)
|
1587
1589
|
|
1588
|
-
return
|
1590
|
+
return
|
1589
1591
|
|
1590
|
-
def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) ->
|
1592
|
+
def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> None:
|
1591
1593
|
"""Replace all straight quotes in the selected text."""
|
1592
1594
|
cursor = self.textCursor()
|
1593
1595
|
if not cursor.hasSelection():
|
1594
1596
|
SHARED.error(self.tr("Please select some text before calling replace quotes."))
|
1595
|
-
return
|
1597
|
+
return
|
1596
1598
|
|
1597
1599
|
posS = cursor.selectionStart()
|
1598
1600
|
posE = cursor.selectionEnd()
|
@@ -1632,7 +1634,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1632
1634
|
|
1633
1635
|
self._allowAutoReplace(True)
|
1634
1636
|
|
1635
|
-
return
|
1637
|
+
return
|
1636
1638
|
|
1637
1639
|
def _processBlockFormat(
|
1638
1640
|
self, action: nwDocAction, text: str, toggle: bool = True
|
@@ -2185,7 +2187,7 @@ class MetaCompleter(QMenu):
|
|
2185
2187
|
# Internal Functions
|
2186
2188
|
##
|
2187
2189
|
|
2188
|
-
def _emitComplete(self, pos: int, length: int, value: str):
|
2190
|
+
def _emitComplete(self, pos: int, length: int, value: str) -> None:
|
2189
2191
|
"""Emit the signal to indicate a selection has been made."""
|
2190
2192
|
self.complete.emit(pos, length, value)
|
2191
2193
|
return
|
@@ -2408,12 +2410,12 @@ class GuiDocEditSearch(QFrame):
|
|
2408
2410
|
|
2409
2411
|
self.searchBox = QLineEdit(self)
|
2410
2412
|
self.searchBox.setFont(self.boxFont)
|
2411
|
-
self.searchBox.setPlaceholderText(self.tr("Search"))
|
2413
|
+
self.searchBox.setPlaceholderText(self.tr("Search for"))
|
2412
2414
|
self.searchBox.returnPressed.connect(self._doSearch)
|
2413
2415
|
|
2414
2416
|
self.replaceBox = QLineEdit(self)
|
2415
2417
|
self.replaceBox.setFont(self.boxFont)
|
2416
|
-
self.replaceBox.setPlaceholderText(self.tr("Replace"))
|
2418
|
+
self.replaceBox.setPlaceholderText(self.tr("Replace with"))
|
2417
2419
|
self.replaceBox.returnPressed.connect(self._doReplace)
|
2418
2420
|
|
2419
2421
|
self.searchOpt = QToolBar(self)
|
@@ -2966,7 +2968,7 @@ class GuiDocEditHeader(QWidget):
|
|
2966
2968
|
# Events
|
2967
2969
|
##
|
2968
2970
|
|
2969
|
-
def mousePressEvent(self, event: QMouseEvent):
|
2971
|
+
def mousePressEvent(self, event: QMouseEvent) -> None:
|
2970
2972
|
"""Capture a click on the title and ensure that the item is
|
2971
2973
|
selected in the project tree.
|
2972
2974
|
"""
|
novelwriter/gui/dochighlight.py
CHANGED
@@ -44,6 +44,10 @@ logger = logging.getLogger(__name__)
|
|
44
44
|
|
45
45
|
SPELLRX = QRegularExpression(r"\b[^\s\-\+\/–—\[\]:]+\b")
|
46
46
|
SPELLRX.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
|
47
|
+
SPELLSC = QRegularExpression(nwRegEx.FMT_SC)
|
48
|
+
SPELLSC.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
|
49
|
+
SPELLSV = QRegularExpression(nwRegEx.FMT_SV)
|
50
|
+
SPELLSV.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
|
47
51
|
|
48
52
|
BLOCK_NONE = 0
|
49
53
|
BLOCK_TEXT = 1
|
@@ -53,7 +57,8 @@ BLOCK_TITLE = 4
|
|
53
57
|
|
54
58
|
class GuiDocHighlighter(QSyntaxHighlighter):
|
55
59
|
|
56
|
-
__slots__ = ("
|
60
|
+
__slots__ = ("_tHandle", "_isInactive", "_spellCheck", "_spellErr",
|
61
|
+
"_hRules", "_hStyles", "_rxRules")
|
57
62
|
|
58
63
|
def __init__(self, document: QTextDocument) -> None:
|
59
64
|
super().__init__(document)
|
@@ -67,6 +72,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
67
72
|
|
68
73
|
self._hRules: list[tuple[str, dict]] = []
|
69
74
|
self._hStyles: dict[str, QTextCharFormat] = {}
|
75
|
+
self._rxRules: list[tuple[QRegularExpression, dict[str, QTextCharFormat]]] = []
|
70
76
|
|
71
77
|
self.initHighlighter()
|
72
78
|
|
@@ -218,11 +224,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
218
224
|
))
|
219
225
|
|
220
226
|
# Build a QRegExp for each highlight pattern
|
221
|
-
self.
|
227
|
+
self._rxRules = []
|
222
228
|
for regEx, regRules in self._hRules:
|
223
229
|
hReg = QRegularExpression(regEx)
|
224
230
|
hReg.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
|
225
|
-
self.
|
231
|
+
self._rxRules.append((hReg, regRules))
|
226
232
|
|
227
233
|
return
|
228
234
|
|
@@ -327,7 +333,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
327
333
|
self.setFormat(0, 3, self._hStyles["head2h"])
|
328
334
|
self.setFormat(3, len(text), self._hStyles["header2"])
|
329
335
|
|
330
|
-
elif text.startswith("###! "): #
|
336
|
+
elif text.startswith("###! "): # Alternative Scene
|
331
337
|
self.setFormat(0, 4, self._hStyles["head3h"])
|
332
338
|
self.setFormat(4, len(text), self._hStyles["header3"])
|
333
339
|
|
@@ -358,7 +364,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|
358
364
|
|
359
365
|
# Regular Text
|
360
366
|
self.setCurrentBlockState(BLOCK_TEXT)
|
361
|
-
for rX, xFmt in self.
|
367
|
+
for rX, xFmt in self._rxRules:
|
362
368
|
rxItt = rX.globalMatch(text, 0)
|
363
369
|
while rxItt.hasNext():
|
364
370
|
rxMatch = rxItt.next()
|
@@ -439,6 +445,17 @@ class TextBlockData(QTextBlockUserData):
|
|
439
445
|
"""Run the spell checker and cache the result, and return the
|
440
446
|
list of spell check errors.
|
441
447
|
"""
|
448
|
+
if "[" in text:
|
449
|
+
# Strip shortcodes
|
450
|
+
for rX in [SPELLSC, SPELLSV]:
|
451
|
+
rxItt = rX.globalMatch(text, 0)
|
452
|
+
while rxItt.hasNext():
|
453
|
+
rxMatch = rxItt.next()
|
454
|
+
xPos = rxMatch.capturedStart(0)
|
455
|
+
xLen = rxMatch.capturedLength(0)
|
456
|
+
xEnd = rxMatch.capturedEnd(0)
|
457
|
+
text = text[:xPos] + " "*xLen + text[xEnd:]
|
458
|
+
|
442
459
|
self._spellErrors = []
|
443
460
|
rxSpell = SPELLRX.globalMatch(text.replace("_", " "), 0)
|
444
461
|
while rxSpell.hasNext():
|
novelwriter/gui/docviewer.py
CHANGED
@@ -30,7 +30,7 @@ import logging
|
|
30
30
|
|
31
31
|
from enum import Enum
|
32
32
|
|
33
|
-
from PyQt5.QtCore import
|
33
|
+
from PyQt5.QtCore import QPoint, Qt, QUrl, pyqtSignal, pyqtSlot
|
34
34
|
from PyQt5.QtGui import (
|
35
35
|
QCursor, QFont, QMouseEvent, QPalette, QResizeEvent, QTextCursor,
|
36
36
|
QTextOption
|
@@ -44,7 +44,7 @@ from novelwriter import CONFIG, SHARED
|
|
44
44
|
from novelwriter.common import cssCol
|
45
45
|
from novelwriter.constants import nwHeaders, nwUnicode
|
46
46
|
from novelwriter.core.tohtml import ToHtml
|
47
|
-
from novelwriter.enum import
|
47
|
+
from novelwriter.enum import nwDocAction, nwDocMode, nwItemType
|
48
48
|
from novelwriter.error import logException
|
49
49
|
from novelwriter.extensions.eventfilters import WheelEventFilter
|
50
50
|
from novelwriter.extensions.modified import NIconToolButton
|
@@ -146,7 +146,7 @@ class GuiDocViewer(QTextBrowser):
|
|
146
146
|
font = QFont()
|
147
147
|
font.setFamily(CONFIG.textFont)
|
148
148
|
font.setPointSize(CONFIG.textSize)
|
149
|
-
self.
|
149
|
+
self.document().setDefaultFont(font)
|
150
150
|
|
151
151
|
# Set the widget colours to match syntax theme
|
152
152
|
mainPalette = self.palette()
|
novelwriter/gui/mainmenu.py
CHANGED
@@ -736,8 +736,8 @@ class GuiMainMenu(QMenuBar):
|
|
736
736
|
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_UNN)
|
737
737
|
)
|
738
738
|
|
739
|
-
# Format >
|
740
|
-
self.aFmtHardSc = self.fmtMenu.addAction(self.tr("
|
739
|
+
# Format > Alternative Scene
|
740
|
+
self.aFmtHardSc = self.fmtMenu.addAction(self.tr("Alternative Scene"))
|
741
741
|
self.aFmtHardSc.triggered.connect(
|
742
742
|
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_HSC)
|
743
743
|
)
|
novelwriter/gui/noveltree.py
CHANGED
@@ -344,7 +344,7 @@ class GuiNovelToolBar(QWidget):
|
|
344
344
|
# Internal Functions
|
345
345
|
##
|
346
346
|
|
347
|
-
def _addLastColAction(self, colType, actionLabel) -> None:
|
347
|
+
def _addLastColAction(self, colType: NovelTreeColumn, actionLabel: str) -> None:
|
348
348
|
"""Add a column selection entry to the last column menu."""
|
349
349
|
aLast = self.mLastCol.addAction(actionLabel)
|
350
350
|
aLast.setCheckable(True)
|
novelwriter/gui/outline.py
CHANGED
@@ -47,6 +47,7 @@ from novelwriter.enum import (
|
|
47
47
|
from novelwriter.error import logException
|
48
48
|
from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe
|
49
49
|
from novelwriter.constants import nwHeaders, trConst, nwKeyWords, nwLabels
|
50
|
+
from novelwriter.extensions.configlayout import NColourLabel
|
50
51
|
from novelwriter.extensions.novelselector import NovelSelector
|
51
52
|
from novelwriter.types import (
|
52
53
|
QtAlignLeftTop, QtAlignRight, QtAlignRightTop, QtDecoration, QtUserRole
|
@@ -190,7 +191,7 @@ class GuiOutlineView(QWidget):
|
|
190
191
|
return
|
191
192
|
|
192
193
|
@pyqtSlot(str)
|
193
|
-
def _rootItemChanged(self, tHandle) -> None:
|
194
|
+
def _rootItemChanged(self, tHandle: str) -> None:
|
194
195
|
"""Handle root novel changed or needs to be refreshed."""
|
195
196
|
self.outlineTree.refreshTree(rootHandle=(tHandle or None), overRide=True)
|
196
197
|
return
|
@@ -209,19 +210,18 @@ class GuiOutlineToolBar(QToolBar):
|
|
209
210
|
|
210
211
|
logger.debug("Create: GuiOutlineToolBar")
|
211
212
|
|
212
|
-
iSz = SHARED.theme.baseIconSize
|
213
|
-
mPx = CONFIG.pxInt(12)
|
214
|
-
|
215
213
|
self.setMovable(False)
|
216
|
-
self.setIconSize(
|
214
|
+
self.setIconSize(1.4*SHARED.theme.baseIconSize)
|
217
215
|
self.setContentsMargins(0, 0, 0, 0)
|
218
216
|
|
219
217
|
stretch = QWidget(self)
|
220
218
|
stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
221
219
|
|
222
220
|
# Novel Selector
|
223
|
-
self.novelLabel =
|
224
|
-
|
221
|
+
self.novelLabel = NColourLabel(
|
222
|
+
self.tr("Outline of"), parent=self, scale=NColourLabel.HEADER_SCALE, bold=True
|
223
|
+
)
|
224
|
+
self.novelLabel.setContentsMargins(0, 0, CONFIG.pxInt(12), 0)
|
225
225
|
|
226
226
|
self.novelValue = NovelSelector(self)
|
227
227
|
self.novelValue.setIncludeAll(True)
|
@@ -428,7 +428,7 @@ class GuiOutlineTree(QTreeWidget):
|
|
428
428
|
##
|
429
429
|
|
430
430
|
@property
|
431
|
-
def hiddenColumns(self):
|
431
|
+
def hiddenColumns(self) -> dict[nwOutline, bool]:
|
432
432
|
return self._colHidden
|
433
433
|
|
434
434
|
##
|
@@ -586,7 +586,7 @@ class GuiOutlineTree(QTreeWidget):
|
|
586
586
|
# Internal Functions
|
587
587
|
##
|
588
588
|
|
589
|
-
def _loadHeaderState(self):
|
589
|
+
def _loadHeaderState(self) -> None:
|
590
590
|
"""Load the state of the main tree header, that is, column order
|
591
591
|
and column width.
|
592
592
|
"""
|
novelwriter/gui/projtree.py
CHANGED
@@ -453,7 +453,7 @@ class GuiProjectToolBar(QWidget):
|
|
453
453
|
|
454
454
|
def _buildRootMenu(self) -> None:
|
455
455
|
"""Build the rood folder menu."""
|
456
|
-
def addClass(itemClass):
|
456
|
+
def addClass(itemClass: nwItemClass) -> None:
|
457
457
|
aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass]))
|
458
458
|
aNew.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass]))
|
459
459
|
aNew.triggered.connect(lambda: self.projTree.newTreeItem(nwItemType.ROOT, itemClass))
|
novelwriter/gui/search.py
CHANGED
@@ -98,7 +98,7 @@ class GuiProjectSearch(QWidget):
|
|
98
98
|
|
99
99
|
# Search Box
|
100
100
|
self.searchText = QLineEdit(self)
|
101
|
-
self.searchText.setPlaceholderText(self.tr("Search"))
|
101
|
+
self.searchText.setPlaceholderText(self.tr("Search for"))
|
102
102
|
self.searchText.setClearButtonEnabled(True)
|
103
103
|
|
104
104
|
self.searchAction = self.searchText.addAction(
|
@@ -161,7 +161,7 @@ class GuiProjectSearch(QWidget):
|
|
161
161
|
colBase = cssCol(qPalette.base().color())
|
162
162
|
colFocus = cssCol(qPalette.highlight().color())
|
163
163
|
|
164
|
-
self.headerWidget.setStyleSheet(f"background: {colBase};")
|
164
|
+
self.headerWidget.setStyleSheet(f"QWidget {{background: {colBase};}}")
|
165
165
|
self.headerWidget.setAutoFillBackground(True)
|
166
166
|
|
167
167
|
self.setStyleSheet(
|
novelwriter/gui/theme.py
CHANGED
@@ -74,9 +74,9 @@ class GuiTheme:
|
|
74
74
|
self.isLightTheme = True
|
75
75
|
|
76
76
|
# GUI
|
77
|
-
self.statNone = QColor(
|
78
|
-
self.statUnsaved = QColor(
|
79
|
-
self.statSaved = QColor(
|
77
|
+
self.statNone = QColor(0, 0, 0)
|
78
|
+
self.statUnsaved = QColor(0, 0, 0)
|
79
|
+
self.statSaved = QColor(0, 0, 0)
|
80
80
|
self.helpText = QColor(0, 0, 0)
|
81
81
|
|
82
82
|
# Loaded Syntax Settings
|
@@ -227,6 +227,10 @@ class GuiTheme:
|
|
227
227
|
logException()
|
228
228
|
return False
|
229
229
|
|
230
|
+
# Reset Palette
|
231
|
+
self._guiPalette = QApplication.style().standardPalette()
|
232
|
+
self._resetGuiColors()
|
233
|
+
|
230
234
|
# Main
|
231
235
|
sec = "Main"
|
232
236
|
if parser.has_section(sec):
|
@@ -256,8 +260,6 @@ class GuiTheme:
|
|
256
260
|
self._setPalette(parser, sec, "highlightedtext", QPalette.ColorRole.HighlightedText)
|
257
261
|
self._setPalette(parser, sec, "link", QPalette.ColorRole.Link)
|
258
262
|
self._setPalette(parser, sec, "linkvisited", QPalette.ColorRole.LinkVisited)
|
259
|
-
else:
|
260
|
-
self._guiPalette = QApplication.style().standardPalette()
|
261
263
|
|
262
264
|
# GUI
|
263
265
|
sec = "GUI"
|
@@ -393,6 +395,14 @@ class GuiTheme:
|
|
393
395
|
# Internal Functions
|
394
396
|
##
|
395
397
|
|
398
|
+
def _resetGuiColors(self) -> None:
|
399
|
+
"""Reset GUI colours to default values."""
|
400
|
+
self.statNone = QColor(120, 120, 120)
|
401
|
+
self.statUnsaved = QColor(200, 15, 39)
|
402
|
+
self.statSaved = QColor(2, 133, 37)
|
403
|
+
self.helpText = QColor(0, 0, 0)
|
404
|
+
return
|
405
|
+
|
396
406
|
def _setGuiFont(self) -> None:
|
397
407
|
"""Update the GUI's font style from settings."""
|
398
408
|
font = QFont()
|
novelwriter/guimain.py
CHANGED
@@ -944,7 +944,7 @@ class GuiMain(QMainWindow):
|
|
944
944
|
# Events
|
945
945
|
##
|
946
946
|
|
947
|
-
def closeEvent(self, event: QCloseEvent):
|
947
|
+
def closeEvent(self, event: QCloseEvent) -> None:
|
948
948
|
"""Capture the closing event of the GUI and call the close
|
949
949
|
function to handle all the close process steps.
|
950
950
|
"""
|
@@ -1201,7 +1201,7 @@ class GuiMain(QMainWindow):
|
|
1201
1201
|
return
|
1202
1202
|
|
1203
1203
|
@pyqtSlot()
|
1204
|
-
def _toggleViewerPanelVisibility(self):
|
1204
|
+
def _toggleViewerPanelVisibility(self) -> None:
|
1205
1205
|
"""Toggle the visibility of the document viewer panel."""
|
1206
1206
|
CONFIG.showViewerPanel = not CONFIG.showViewerPanel
|
1207
1207
|
self.docViewerPanel.setVisible(CONFIG.showViewerPanel)
|
novelwriter/shared.py
CHANGED
@@ -26,21 +26,21 @@ from __future__ import annotations
|
|
26
26
|
|
27
27
|
import logging
|
28
28
|
|
29
|
+
from pathlib import Path
|
29
30
|
from time import time
|
30
31
|
from typing import TYPE_CHECKING, TypeVar
|
31
|
-
from pathlib import Path
|
32
32
|
|
33
33
|
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal
|
34
34
|
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget
|
35
|
-
from novelwriter.common import formatFileFilter
|
36
35
|
|
36
|
+
from novelwriter.common import formatFileFilter
|
37
37
|
from novelwriter.constants import nwFiles
|
38
38
|
from novelwriter.core.spellcheck import NWSpellEnchant
|
39
39
|
|
40
40
|
if TYPE_CHECKING: # pragma: no cover
|
41
|
-
from novelwriter.guimain import GuiMain
|
42
|
-
from novelwriter.gui.theme import GuiTheme
|
43
41
|
from novelwriter.core.project import NWProject
|
42
|
+
from novelwriter.gui.theme import GuiTheme
|
43
|
+
from novelwriter.guimain import GuiMain
|
44
44
|
|
45
45
|
logger = logging.getLogger(__name__)
|
46
46
|
|
@@ -198,6 +198,7 @@ class SharedData(QObject):
|
|
198
198
|
|
199
199
|
def closeProject(self) -> None:
|
200
200
|
"""Close the current project."""
|
201
|
+
self._closeDialogs()
|
201
202
|
self.project.closeProject(self._idleTime)
|
202
203
|
self._resetProject()
|
203
204
|
self._resetIdleTimer()
|
@@ -356,6 +357,17 @@ class SharedData(QObject):
|
|
356
357
|
self._idleTime = 0.0
|
357
358
|
return
|
358
359
|
|
360
|
+
def _closeDialogs(self) -> None:
|
361
|
+
"""Close non-modal dialogs."""
|
362
|
+
from novelwriter.tools.manuscript import GuiManuscript
|
363
|
+
from novelwriter.tools.writingstats import GuiWritingStats
|
364
|
+
|
365
|
+
for widget in self.mainGui.children():
|
366
|
+
if isinstance(widget, (GuiManuscript, GuiWritingStats)):
|
367
|
+
widget.close()
|
368
|
+
|
369
|
+
return
|
370
|
+
|
359
371
|
# END Class SharedData
|
360
372
|
|
361
373
|
|
novelwriter/text/counting.py
CHANGED
@@ -57,6 +57,7 @@ def preProcessText(text: str, keepHeaders: bool = True) -> list[str]:
|
|
57
57
|
continue
|
58
58
|
if line[0] == ">":
|
59
59
|
line = line.lstrip(">").lstrip(" ")
|
60
|
+
if line: # Above block can return empty line (Issue #1816)
|
60
61
|
if line[-1] == "<":
|
61
62
|
line = line.rstrip("<").rstrip(" ")
|
62
63
|
if "[" in line:
|
@@ -179,7 +179,7 @@ class GuiDictionaries(QDialog):
|
|
179
179
|
##
|
180
180
|
|
181
181
|
@pyqtSlot()
|
182
|
-
def _doBrowseHunspell(self):
|
182
|
+
def _doBrowseHunspell(self) -> None:
|
183
183
|
"""Browse for a Free/Libre Office dictionary."""
|
184
184
|
ffilter = formatFileFilter([
|
185
185
|
(self.tr("Free or Libre Office extension"), "*.sox *.oxt"), "*"
|
@@ -193,7 +193,7 @@ class GuiDictionaries(QDialog):
|
|
193
193
|
return
|
194
194
|
|
195
195
|
@pyqtSlot()
|
196
|
-
def _doImportHunspell(self):
|
196
|
+
def _doImportHunspell(self) -> None:
|
197
197
|
"""Import a hunspell dictionary from .sox or .oxt file."""
|
198
198
|
procErr = self.tr("Could not process dictionary file")
|
199
199
|
if self._installPath:
|
novelwriter/tools/manusbuild.py
CHANGED
@@ -44,9 +44,7 @@ from novelwriter.core.item import NWItem
|
|
44
44
|
from novelwriter.enum import nwBuildFmt
|
45
45
|
from novelwriter.extensions.modified import NIconToolButton
|
46
46
|
from novelwriter.extensions.simpleprogress import NProgressSimple
|
47
|
-
from novelwriter.types import
|
48
|
-
QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole
|
49
|
-
)
|
47
|
+
from novelwriter.types import QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole
|
50
48
|
|
51
49
|
logger = logging.getLogger(__name__)
|
52
50
|
|
@@ -60,7 +58,7 @@ class GuiManuscriptBuild(QDialog):
|
|
60
58
|
|
61
59
|
D_KEY = QtUserRole
|
62
60
|
|
63
|
-
def __init__(self, parent: QWidget, build: BuildSettings):
|
61
|
+
def __init__(self, parent: QWidget, build: BuildSettings) -> None:
|
64
62
|
super().__init__(parent=parent)
|
65
63
|
|
66
64
|
logger.debug("Create: GuiManuscriptBuild")
|
@@ -260,7 +258,7 @@ class GuiManuscriptBuild(QDialog):
|
|
260
258
|
##
|
261
259
|
|
262
260
|
@pyqtSlot("QAbstractButton*")
|
263
|
-
def _dialogButtonClicked(self, button: QAbstractButton):
|
261
|
+
def _dialogButtonClicked(self, button: QAbstractButton) -> None:
|
264
262
|
"""Handle button clicks from the dialog button box."""
|
265
263
|
role = self.dlgButtons.buttonRole(button)
|
266
264
|
if role == QtRoleAction:
|
@@ -273,7 +271,7 @@ class GuiManuscriptBuild(QDialog):
|
|
273
271
|
return
|
274
272
|
|
275
273
|
@pyqtSlot()
|
276
|
-
def _doSelectPath(self):
|
274
|
+
def _doSelectPath(self) -> None:
|
277
275
|
"""Select a folder for output."""
|
278
276
|
bPath = Path(self.buildPath.text())
|
279
277
|
bPath = bPath if bPath.is_dir() else self._build.lastPath
|
@@ -285,7 +283,7 @@ class GuiManuscriptBuild(QDialog):
|
|
285
283
|
return
|
286
284
|
|
287
285
|
@pyqtSlot()
|
288
|
-
def _doResetBuildName(self):
|
286
|
+
def _doResetBuildName(self) -> None:
|
289
287
|
"""Generate a default build name."""
|
290
288
|
bName = f"{SHARED.project.data.name} - {self._build.name}"
|
291
289
|
self.buildName.setText(bName)
|
@@ -293,7 +291,7 @@ class GuiManuscriptBuild(QDialog):
|
|
293
291
|
return
|
294
292
|
|
295
293
|
@pyqtSlot()
|
296
|
-
def _resetProgress(self):
|
294
|
+
def _resetProgress(self) -> None:
|
297
295
|
"""Set the progress bar back to 0."""
|
298
296
|
self.buildProgress.setValue(0)
|
299
297
|
return
|
@@ -328,6 +326,9 @@ class GuiManuscriptBuild(QDialog):
|
|
328
326
|
):
|
329
327
|
return False
|
330
328
|
|
329
|
+
# Make sure editor content is saved before we start
|
330
|
+
SHARED.mainGui.saveDocument()
|
331
|
+
|
331
332
|
docBuild = NWBuildDocument(SHARED.project, self._build)
|
332
333
|
docBuild.queueAll()
|
333
334
|
|
@@ -350,7 +351,7 @@ class GuiManuscriptBuild(QDialog):
|
|
350
351
|
return items[0].data(self.D_KEY)
|
351
352
|
return None
|
352
353
|
|
353
|
-
def _saveSettings(self):
|
354
|
+
def _saveSettings(self) -> None:
|
354
355
|
"""Save the user GUI settings."""
|
355
356
|
winWidth = CONFIG.rpxInt(self.width())
|
356
357
|
winHeight = CONFIG.rpxInt(self.height())
|
@@ -369,7 +370,7 @@ class GuiManuscriptBuild(QDialog):
|
|
369
370
|
|
370
371
|
return
|
371
372
|
|
372
|
-
def _populateContentList(self):
|
373
|
+
def _populateContentList(self) -> None:
|
373
374
|
"""Build the content list."""
|
374
375
|
rootMap = {}
|
375
376
|
filtered = self._build.buildItemFilter(SHARED.project)
|
@@ -398,7 +399,7 @@ class GuiManuscriptBuild(QDialog):
|
|
398
399
|
|
399
400
|
return
|
400
401
|
|
401
|
-
def _openOutputFolder(self):
|
402
|
+
def _openOutputFolder(self) -> None:
|
402
403
|
"""Open the build folder in the system's file explorer."""
|
403
404
|
openExternalPath(Path(self.buildPath.text()))
|
404
405
|
return
|