novelWriter 2.5.1__py3-none-any.whl → 2.6b1__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.5.1.dist-info → novelWriter-2.6b1.dist-info}/METADATA +2 -1
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/RECORD +61 -56
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +3 -3
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +1 -0
- novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +1 -0
- novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +63 -0
- novelwriter/config.py +10 -3
- novelwriter/constants.py +153 -60
- novelwriter/core/buildsettings.py +66 -39
- novelwriter/core/coretools.py +34 -22
- novelwriter/core/docbuild.py +130 -169
- novelwriter/core/index.py +29 -18
- novelwriter/core/item.py +2 -2
- novelwriter/core/options.py +4 -1
- novelwriter/core/spellcheck.py +9 -14
- novelwriter/dialogs/preferences.py +45 -32
- novelwriter/dialogs/projectsettings.py +3 -3
- novelwriter/enum.py +29 -23
- novelwriter/extensions/configlayout.py +24 -11
- novelwriter/extensions/modified.py +13 -1
- novelwriter/extensions/pagedsidebar.py +5 -5
- novelwriter/formats/shared.py +155 -0
- novelwriter/formats/todocx.py +1195 -0
- novelwriter/formats/tohtml.py +452 -0
- novelwriter/{core → formats}/tokenizer.py +483 -485
- novelwriter/formats/tomarkdown.py +217 -0
- novelwriter/{core → formats}/toodt.py +270 -320
- novelwriter/formats/toqdoc.py +436 -0
- novelwriter/formats/toraw.py +91 -0
- novelwriter/gui/doceditor.py +240 -193
- novelwriter/gui/dochighlight.py +96 -84
- novelwriter/gui/docviewer.py +56 -30
- novelwriter/gui/docviewerpanel.py +3 -3
- novelwriter/gui/editordocument.py +17 -2
- novelwriter/gui/itemdetails.py +8 -4
- novelwriter/gui/mainmenu.py +121 -60
- novelwriter/gui/noveltree.py +35 -37
- novelwriter/gui/outline.py +186 -238
- novelwriter/gui/projtree.py +142 -131
- novelwriter/gui/sidebar.py +7 -6
- novelwriter/gui/theme.py +5 -4
- novelwriter/guimain.py +43 -155
- novelwriter/shared.py +14 -4
- novelwriter/text/counting.py +2 -0
- novelwriter/text/patterns.py +155 -59
- novelwriter/tools/manusbuild.py +1 -1
- novelwriter/tools/manuscript.py +121 -78
- novelwriter/tools/manussettings.py +403 -260
- novelwriter/tools/welcome.py +4 -4
- novelwriter/tools/writingstats.py +3 -3
- novelwriter/types.py +16 -6
- novelwriter/core/tohtml.py +0 -530
- novelwriter/core/tomarkdown.py +0 -252
- novelwriter/core/toqdoc.py +0 -419
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/top_level.txt +0 -0
novelwriter/gui/doceditor.py
CHANGED
@@ -36,6 +36,7 @@ import logging
|
|
36
36
|
|
37
37
|
from enum import Enum
|
38
38
|
from time import time
|
39
|
+
from typing import NamedTuple
|
39
40
|
|
40
41
|
from PyQt5.QtCore import (
|
41
42
|
QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal,
|
@@ -51,10 +52,13 @@ from PyQt5.QtWidgets import (
|
|
51
52
|
)
|
52
53
|
|
53
54
|
from novelwriter import CONFIG, SHARED
|
54
|
-
from novelwriter.common import minmax, transferCase
|
55
|
+
from novelwriter.common import minmax, qtLambda, transferCase
|
55
56
|
from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
|
56
57
|
from novelwriter.core.document import NWDocument
|
57
|
-
from novelwriter.enum import
|
58
|
+
from novelwriter.enum import (
|
59
|
+
nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwItemType,
|
60
|
+
nwTrinary
|
61
|
+
)
|
58
62
|
from novelwriter.extensions.configlayout import NColourLabel
|
59
63
|
from novelwriter.extensions.eventfilters import WheelEventFilter
|
60
64
|
from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton
|
@@ -66,7 +70,7 @@ from novelwriter.tools.lipsum import GuiLipsum
|
|
66
70
|
from novelwriter.types import (
|
67
71
|
QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop,
|
68
72
|
QtAlignRight, QtKeepAnchor, QtModCtrl, QtModNone, QtModShift, QtMouseLeft,
|
69
|
-
QtMoveAnchor, QtMoveLeft, QtMoveRight
|
73
|
+
QtMoveAnchor, QtMoveLeft, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded
|
70
74
|
)
|
71
75
|
|
72
76
|
logger = logging.getLogger(__name__)
|
@@ -80,6 +84,21 @@ class _SelectAction(Enum):
|
|
80
84
|
MOVE_AFTER = 3
|
81
85
|
|
82
86
|
|
87
|
+
class AutoReplaceConfig(NamedTuple):
|
88
|
+
|
89
|
+
typPadChar: str
|
90
|
+
typSQuoteO: str
|
91
|
+
typSQuoteC: str
|
92
|
+
typDQuoteO: str
|
93
|
+
typDQuoteC: str
|
94
|
+
typRepDQuote: bool
|
95
|
+
typRepSQuote: bool
|
96
|
+
typRepDash: bool
|
97
|
+
typRepDots: bool
|
98
|
+
typPadBefore: str
|
99
|
+
typPadAfter: str
|
100
|
+
|
101
|
+
|
83
102
|
class GuiDocEditor(QPlainTextEdit):
|
84
103
|
"""Gui Widget: Main Document Editor"""
|
85
104
|
|
@@ -89,20 +108,21 @@ class GuiDocEditor(QPlainTextEdit):
|
|
89
108
|
)
|
90
109
|
|
91
110
|
# Custom Signals
|
92
|
-
|
111
|
+
closeEditorRequest = pyqtSignal()
|
93
112
|
docCountsChanged = pyqtSignal(str, int, int, int)
|
94
113
|
docTextChanged = pyqtSignal(str, float)
|
95
114
|
editedStatusChanged = pyqtSignal(bool)
|
115
|
+
itemHandleChanged = pyqtSignal(str)
|
96
116
|
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
97
|
-
novelStructureChanged = pyqtSignal()
|
98
117
|
novelItemMetaChanged = pyqtSignal(str)
|
99
|
-
|
100
|
-
closeDocumentRequest = pyqtSignal()
|
101
|
-
toggleFocusModeRequest = pyqtSignal()
|
102
|
-
requestProjectItemSelected = pyqtSignal(str, bool)
|
103
|
-
requestProjectItemRenamed = pyqtSignal(str, str)
|
118
|
+
novelStructureChanged = pyqtSignal()
|
104
119
|
requestNewNoteCreation = pyqtSignal(str, nwItemClass)
|
105
120
|
requestNextDocument = pyqtSignal(str, bool)
|
121
|
+
requestProjectItemRenamed = pyqtSignal(str, str)
|
122
|
+
requestProjectItemSelected = pyqtSignal(str, bool)
|
123
|
+
spellCheckStateChanged = pyqtSignal(bool)
|
124
|
+
toggleFocusModeRequest = pyqtSignal()
|
125
|
+
updateStatusMessage = pyqtSignal(str)
|
106
126
|
|
107
127
|
def __init__(self, parent: QWidget) -> None:
|
108
128
|
super().__init__(parent=parent)
|
@@ -124,17 +144,19 @@ class GuiDocEditor(QPlainTextEdit):
|
|
124
144
|
self._doReplace = False # Switch to temporarily disable auto-replace
|
125
145
|
|
126
146
|
# Typography Cache
|
127
|
-
self.
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
147
|
+
self._typConf = AutoReplaceConfig(
|
148
|
+
typPadChar=" ",
|
149
|
+
typSQuoteO="'",
|
150
|
+
typSQuoteC="'",
|
151
|
+
typDQuoteO='"',
|
152
|
+
typDQuoteC='"',
|
153
|
+
typRepSQuote=False,
|
154
|
+
typRepDQuote=False,
|
155
|
+
typRepDash=False,
|
156
|
+
typRepDots=False,
|
157
|
+
typPadBefore="",
|
158
|
+
typPadAfter="",
|
159
|
+
)
|
138
160
|
|
139
161
|
# Completer
|
140
162
|
self._completer = MetaCompleter(self)
|
@@ -177,12 +199,12 @@ class GuiDocEditor(QPlainTextEdit):
|
|
177
199
|
self.keyContext.activated.connect(self._openContextFromCursor)
|
178
200
|
|
179
201
|
self.followTag1 = QShortcut(self)
|
180
|
-
self.followTag1.setKey(
|
202
|
+
self.followTag1.setKey("Ctrl+Return")
|
181
203
|
self.followTag1.setContext(Qt.ShortcutContext.WidgetShortcut)
|
182
204
|
self.followTag1.activated.connect(self._processTag)
|
183
205
|
|
184
206
|
self.followTag2 = QShortcut(self)
|
185
|
-
self.followTag2.setKey(
|
207
|
+
self.followTag2.setKey("Ctrl+Enter")
|
186
208
|
self.followTag2.setContext(Qt.ShortcutContext.WidgetShortcut)
|
187
209
|
self.followTag2.activated.connect(self._processTag)
|
188
210
|
|
@@ -270,6 +292,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
270
292
|
self.docFooter.setHandle(self._docHandle)
|
271
293
|
self.docToolBar.setVisible(False)
|
272
294
|
|
295
|
+
self.itemHandleChanged.emit("")
|
296
|
+
|
273
297
|
return
|
274
298
|
|
275
299
|
def updateTheme(self) -> None:
|
@@ -304,21 +328,19 @@ class GuiDocEditor(QPlainTextEdit):
|
|
304
328
|
created, and when the user changes the main editor preferences.
|
305
329
|
"""
|
306
330
|
# Typography
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
self._typPadBefore = CONFIG.fmtPadBefore
|
321
|
-
self._typPadAfter = CONFIG.fmtPadAfter
|
331
|
+
self._typConf = AutoReplaceConfig(
|
332
|
+
typPadChar=nwUnicode.U_THNBSP if CONFIG.fmtPadThin else nwUnicode.U_NBSP,
|
333
|
+
typSQuoteO=CONFIG.fmtSQuoteOpen,
|
334
|
+
typSQuoteC=CONFIG.fmtSQuoteClose,
|
335
|
+
typDQuoteO=CONFIG.fmtDQuoteOpen,
|
336
|
+
typDQuoteC=CONFIG.fmtDQuoteClose,
|
337
|
+
typRepSQuote=CONFIG.doReplaceSQuote,
|
338
|
+
typRepDQuote=CONFIG.doReplaceDQuote,
|
339
|
+
typRepDash=CONFIG.doReplaceDash,
|
340
|
+
typRepDots=CONFIG.doReplaceDots,
|
341
|
+
typPadBefore=CONFIG.fmtPadBefore,
|
342
|
+
typPadAfter=CONFIG.fmtPadAfter,
|
343
|
+
)
|
322
344
|
|
323
345
|
# Reload spell check and dictionaries
|
324
346
|
SHARED.updateSpellCheckLanguage()
|
@@ -354,14 +376,14 @@ class GuiDocEditor(QPlainTextEdit):
|
|
354
376
|
# Scrolling
|
355
377
|
self.setCenterOnScroll(CONFIG.scrollPastEnd)
|
356
378
|
if CONFIG.hideVScroll:
|
357
|
-
self.setVerticalScrollBarPolicy(
|
379
|
+
self.setVerticalScrollBarPolicy(QtScrollAlwaysOff)
|
358
380
|
else:
|
359
|
-
self.setVerticalScrollBarPolicy(
|
381
|
+
self.setVerticalScrollBarPolicy(QtScrollAsNeeded)
|
360
382
|
|
361
383
|
if CONFIG.hideHScroll:
|
362
|
-
self.setHorizontalScrollBarPolicy(
|
384
|
+
self.setHorizontalScrollBarPolicy(QtScrollAlwaysOff)
|
363
385
|
else:
|
364
|
-
self.setHorizontalScrollBarPolicy(
|
386
|
+
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
|
365
387
|
|
366
388
|
# Refresh the tab stops
|
367
389
|
self.setTabStopDistance(CONFIG.getTabWidth())
|
@@ -388,9 +410,12 @@ class GuiDocEditor(QPlainTextEdit):
|
|
388
410
|
"""
|
389
411
|
self._nwDocument = SHARED.project.storage.getDocument(tHandle)
|
390
412
|
self._nwItem = self._nwDocument.nwItem
|
413
|
+
if not ((nwItem := self._nwItem) and nwItem.itemType == nwItemType.FILE):
|
414
|
+
logger.debug("Requested item '%s' is not a document", tHandle)
|
415
|
+
self.clearEditor()
|
416
|
+
return False
|
391
417
|
|
392
|
-
docText
|
393
|
-
if docText is None:
|
418
|
+
if (docText := self._nwDocument.readDocument()) is None:
|
394
419
|
# There was an I/O error
|
395
420
|
self.clearEditor()
|
396
421
|
return False
|
@@ -411,10 +436,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
411
436
|
self.setReadOnly(False)
|
412
437
|
self.updateDocMargins()
|
413
438
|
|
414
|
-
if tLine
|
415
|
-
self.setCursorPosition(self._nwItem.cursorPos)
|
416
|
-
elif isinstance(tLine, int):
|
439
|
+
if isinstance(tLine, int):
|
417
440
|
self.setCursorLine(tLine)
|
441
|
+
else:
|
442
|
+
self.setCursorPosition(nwItem.cursorPos)
|
418
443
|
|
419
444
|
self.docHeader.setHandle(tHandle)
|
420
445
|
self.docFooter.setHandle(tHandle)
|
@@ -430,11 +455,15 @@ class GuiDocEditor(QPlainTextEdit):
|
|
430
455
|
self._qDocument.clearUndoRedoStacks()
|
431
456
|
self.docToolBar.setVisible(CONFIG.showEditToolBar)
|
432
457
|
|
433
|
-
|
458
|
+
# Process State Changes
|
459
|
+
SHARED.project.data.setLastHandle(tHandle, "editor")
|
460
|
+
self.itemHandleChanged.emit(tHandle)
|
434
461
|
|
435
|
-
#
|
436
|
-
|
437
|
-
|
462
|
+
# Finalise
|
463
|
+
QApplication.restoreOverrideCursor()
|
464
|
+
self.updateStatusMessage.emit(
|
465
|
+
self.tr("Opened Document: {0}").format(nwItem.itemName)
|
466
|
+
)
|
438
467
|
|
439
468
|
return True
|
440
469
|
|
@@ -505,7 +534,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
505
534
|
self.docFooter.updateInfo()
|
506
535
|
|
507
536
|
# Update the status bar
|
508
|
-
self.
|
537
|
+
self.updateStatusMessage.emit(self.tr("Saved Document: {0}").format(self._nwItem.itemName))
|
509
538
|
|
510
539
|
return True
|
511
540
|
|
@@ -700,7 +729,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
700
729
|
self._qDocument.syntaxHighlighter.rehighlight()
|
701
730
|
QApplication.restoreOverrideCursor()
|
702
731
|
logger.debug("Document highlighted in %.3f ms", 1000*(time() - start))
|
703
|
-
self.
|
732
|
+
self.updateStatusMessage.emit(self.tr("Spell check complete"))
|
704
733
|
return
|
705
734
|
|
706
735
|
##
|
@@ -724,6 +753,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
724
753
|
|
725
754
|
logger.debug("Requesting action: %s", action.name)
|
726
755
|
|
756
|
+
tConf = self._typConf
|
727
757
|
self._allowAutoReplace(False)
|
728
758
|
if action == nwDocAction.UNDO:
|
729
759
|
self.undo()
|
@@ -742,9 +772,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
742
772
|
elif action == nwDocAction.MD_STRIKE:
|
743
773
|
self._toggleFormat(2, "~")
|
744
774
|
elif action == nwDocAction.S_QUOTE:
|
745
|
-
self._wrapSelection(
|
775
|
+
self._wrapSelection(tConf.typSQuoteO, tConf.typSQuoteC)
|
746
776
|
elif action == nwDocAction.D_QUOTE:
|
747
|
-
self._wrapSelection(
|
777
|
+
self._wrapSelection(tConf.typDQuoteO, tConf.typDQuoteC)
|
748
778
|
elif action == nwDocAction.SEL_ALL:
|
749
779
|
self._makeSelection(QTextCursor.SelectionType.Document)
|
750
780
|
elif action == nwDocAction.SEL_PARA:
|
@@ -770,9 +800,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
770
800
|
elif action == nwDocAction.BLOCK_HSC:
|
771
801
|
self._formatBlock(nwDocAction.BLOCK_HSC)
|
772
802
|
elif action == nwDocAction.REPL_SNG:
|
773
|
-
self._replaceQuotes("'",
|
803
|
+
self._replaceQuotes("'", tConf.typSQuoteO, tConf.typSQuoteC)
|
774
804
|
elif action == nwDocAction.REPL_DBL:
|
775
|
-
self._replaceQuotes("\"",
|
805
|
+
self._replaceQuotes("\"", tConf.typDQuoteO, tConf.typDQuoteC)
|
776
806
|
elif action == nwDocAction.RM_BREAKS:
|
777
807
|
self._removeInParLineBreaks()
|
778
808
|
elif action == nwDocAction.ALIGN_L:
|
@@ -844,13 +874,13 @@ class GuiDocEditor(QPlainTextEdit):
|
|
844
874
|
text = insert
|
845
875
|
elif isinstance(insert, nwDocInsert):
|
846
876
|
if insert == nwDocInsert.QUOTE_LS:
|
847
|
-
text = self.
|
877
|
+
text = self._typConf.typSQuoteO
|
848
878
|
elif insert == nwDocInsert.QUOTE_RS:
|
849
|
-
text = self.
|
879
|
+
text = self._typConf.typSQuoteC
|
850
880
|
elif insert == nwDocInsert.QUOTE_LD:
|
851
|
-
text = self.
|
881
|
+
text = self._typConf.typDQuoteO
|
852
882
|
elif insert == nwDocInsert.QUOTE_RD:
|
853
|
-
text = self.
|
883
|
+
text = self._typConf.typDQuoteC
|
854
884
|
elif insert == nwDocInsert.SYNOPSIS:
|
855
885
|
text = "%Synopsis: "
|
856
886
|
block = True
|
@@ -877,6 +907,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
877
907
|
after = False
|
878
908
|
elif insert == nwDocInsert.FOOTNOTE:
|
879
909
|
self._insertCommentStructure(nwComment.FOOTNOTE)
|
910
|
+
elif insert == nwDocInsert.LINE_BRK:
|
911
|
+
text = nwShortcode.BREAK
|
880
912
|
|
881
913
|
if text:
|
882
914
|
if block:
|
@@ -982,8 +1014,13 @@ class GuiDocEditor(QPlainTextEdit):
|
|
982
1014
|
pressed, check if we're clicking on a tag, and trigger the
|
983
1015
|
follow tag function.
|
984
1016
|
"""
|
985
|
-
if
|
986
|
-
self.
|
1017
|
+
if event.modifiers() & QtModCtrl == QtModCtrl:
|
1018
|
+
cursor = self.cursorForPosition(event.pos())
|
1019
|
+
mData, mType = self._qDocument.metaDataAtPos(cursor.position())
|
1020
|
+
if mData and mType == "url":
|
1021
|
+
SHARED.openWebsite(mData)
|
1022
|
+
else:
|
1023
|
+
self._processTag(cursor)
|
987
1024
|
super().mouseReleaseEvent(event)
|
988
1025
|
return
|
989
1026
|
|
@@ -1095,6 +1132,12 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1095
1132
|
self._completer.hide()
|
1096
1133
|
return
|
1097
1134
|
|
1135
|
+
@pyqtSlot()
|
1136
|
+
def _openContextFromCursor(self) -> None:
|
1137
|
+
"""Open the spell check context menu at the cursor."""
|
1138
|
+
self._openContextMenu(self.cursorRect().center())
|
1139
|
+
return
|
1140
|
+
|
1098
1141
|
@pyqtSlot("QPoint")
|
1099
1142
|
def _openContextMenu(self, pos: QPoint) -> None:
|
1100
1143
|
"""Open the editor context menu at a given coordinate."""
|
@@ -1106,41 +1149,48 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1106
1149
|
ctxMenu.setObjectName("ContextMenu")
|
1107
1150
|
if pBlock.userState() == BLOCK_TITLE:
|
1108
1151
|
action = ctxMenu.addAction(self.tr("Set as Document Name"))
|
1109
|
-
action.triggered.connect(
|
1152
|
+
action.triggered.connect(qtLambda(self._emitRenameItem, pBlock))
|
1153
|
+
|
1154
|
+
# URL
|
1155
|
+
(mData, mType) = self._qDocument.metaDataAtPos(pCursor.position())
|
1156
|
+
if mData and mType == "url":
|
1157
|
+
action = ctxMenu.addAction(self.tr("Open URL"))
|
1158
|
+
action.triggered.connect(qtLambda(SHARED.openWebsite, mData))
|
1159
|
+
ctxMenu.addSeparator()
|
1110
1160
|
|
1111
1161
|
# Follow
|
1112
1162
|
status = self._processTag(cursor=pCursor, follow=False)
|
1113
1163
|
if status == nwTrinary.POSITIVE:
|
1114
1164
|
action = ctxMenu.addAction(self.tr("Follow Tag"))
|
1115
|
-
action.triggered.connect(
|
1165
|
+
action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, follow=True))
|
1116
1166
|
ctxMenu.addSeparator()
|
1117
1167
|
elif status == nwTrinary.NEGATIVE:
|
1118
1168
|
action = ctxMenu.addAction(self.tr("Create Note for Tag"))
|
1119
|
-
action.triggered.connect(
|
1169
|
+
action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, create=True))
|
1120
1170
|
ctxMenu.addSeparator()
|
1121
1171
|
|
1122
1172
|
# Cut, Copy and Paste
|
1123
1173
|
if uCursor.hasSelection():
|
1124
1174
|
action = ctxMenu.addAction(self.tr("Cut"))
|
1125
|
-
action.triggered.connect(
|
1175
|
+
action.triggered.connect(qtLambda(self.docAction, nwDocAction.CUT))
|
1126
1176
|
action = ctxMenu.addAction(self.tr("Copy"))
|
1127
|
-
action.triggered.connect(
|
1177
|
+
action.triggered.connect(qtLambda(self.docAction, nwDocAction.COPY))
|
1128
1178
|
|
1129
1179
|
action = ctxMenu.addAction(self.tr("Paste"))
|
1130
|
-
action.triggered.connect(
|
1180
|
+
action.triggered.connect(qtLambda(self.docAction, nwDocAction.PASTE))
|
1131
1181
|
ctxMenu.addSeparator()
|
1132
1182
|
|
1133
1183
|
# Selections
|
1134
1184
|
action = ctxMenu.addAction(self.tr("Select All"))
|
1135
|
-
action.triggered.connect(
|
1185
|
+
action.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL))
|
1136
1186
|
action = ctxMenu.addAction(self.tr("Select Word"))
|
1137
|
-
action.triggered.connect(
|
1138
|
-
|
1139
|
-
)
|
1187
|
+
action.triggered.connect(qtLambda(
|
1188
|
+
self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, pos,
|
1189
|
+
))
|
1140
1190
|
action = ctxMenu.addAction(self.tr("Select Paragraph"))
|
1141
|
-
action.triggered.connect(
|
1142
|
-
QTextCursor.SelectionType.BlockUnderCursor, pos
|
1143
|
-
)
|
1191
|
+
action.triggered.connect(qtLambda(
|
1192
|
+
self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, pos
|
1193
|
+
))
|
1144
1194
|
|
1145
1195
|
# Spell Checking
|
1146
1196
|
if SHARED.project.data.spellCheck:
|
@@ -1156,16 +1206,16 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1156
1206
|
ctxMenu.addAction(self.tr("Spelling Suggestion(s)"))
|
1157
1207
|
for option in suggest[:15]:
|
1158
1208
|
action = ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {option}")
|
1159
|
-
action.triggered.connect(
|
1160
|
-
lambda _, option=option: self._correctWord(sCursor, option)
|
1161
|
-
)
|
1209
|
+
action.triggered.connect(qtLambda(self._correctWord, sCursor, option))
|
1162
1210
|
else:
|
1163
1211
|
trNone = self.tr("No Suggestions")
|
1164
1212
|
ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {trNone}")
|
1165
1213
|
|
1166
1214
|
ctxMenu.addSeparator()
|
1215
|
+
action = ctxMenu.addAction(self.tr("Ignore Word"))
|
1216
|
+
action.triggered.connect(qtLambda(self._addWord, word, block, False))
|
1167
1217
|
action = ctxMenu.addAction(self.tr("Add Word to Dictionary"))
|
1168
|
-
action.triggered.connect(
|
1218
|
+
action.triggered.connect(qtLambda(self._addWord, word, block, True))
|
1169
1219
|
|
1170
1220
|
# Execute the context menu
|
1171
1221
|
ctxMenu.exec(self.viewport().mapToGlobal(pos))
|
@@ -1173,30 +1223,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1173
1223
|
|
1174
1224
|
return
|
1175
1225
|
|
1176
|
-
@pyqtSlot("QTextCursor", str)
|
1177
|
-
def _correctWord(self, cursor: QTextCursor, word: str) -> None:
|
1178
|
-
"""Slot for the spell check context menu triggering the
|
1179
|
-
replacement of a word with the word from the dictionary.
|
1180
|
-
"""
|
1181
|
-
pos = cursor.selectionStart()
|
1182
|
-
cursor.beginEditBlock()
|
1183
|
-
cursor.removeSelectedText()
|
1184
|
-
cursor.insertText(word)
|
1185
|
-
cursor.endEditBlock()
|
1186
|
-
cursor.setPosition(pos)
|
1187
|
-
self.setTextCursor(cursor)
|
1188
|
-
return
|
1189
|
-
|
1190
|
-
@pyqtSlot(str, "QTextBlock")
|
1191
|
-
def _addWord(self, word: str, block: QTextBlock) -> None:
|
1192
|
-
"""Slot for the spell check context menu triggered when the user
|
1193
|
-
wants to add a word to the project dictionary.
|
1194
|
-
"""
|
1195
|
-
logger.debug("Added '%s' to project dictionary", word)
|
1196
|
-
SHARED.spelling.addWord(word)
|
1197
|
-
self._qDocument.syntaxHighlighter.rehighlightBlock(block)
|
1198
|
-
return
|
1199
|
-
|
1200
1226
|
@pyqtSlot()
|
1201
1227
|
def _runDocumentTasks(self) -> None:
|
1202
1228
|
"""Run timer document tasks."""
|
@@ -1269,7 +1295,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1269
1295
|
@pyqtSlot()
|
1270
1296
|
def _closeCurrentDocument(self) -> None:
|
1271
1297
|
"""Close the document. Forwarded to the main Gui."""
|
1272
|
-
self.
|
1298
|
+
self.closeEditorRequest.emit()
|
1273
1299
|
self.docToolBar.setVisible(False)
|
1274
1300
|
return
|
1275
1301
|
|
@@ -1875,6 +1901,28 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1875
1901
|
# Internal Functions
|
1876
1902
|
##
|
1877
1903
|
|
1904
|
+
def _correctWord(self, cursor: QTextCursor, word: str) -> None:
|
1905
|
+
"""Slot for the spell check context menu triggering the
|
1906
|
+
replacement of a word with the word from the dictionary.
|
1907
|
+
"""
|
1908
|
+
pos = cursor.selectionStart()
|
1909
|
+
cursor.beginEditBlock()
|
1910
|
+
cursor.removeSelectedText()
|
1911
|
+
cursor.insertText(word)
|
1912
|
+
cursor.endEditBlock()
|
1913
|
+
cursor.setPosition(pos)
|
1914
|
+
self.setTextCursor(cursor)
|
1915
|
+
return
|
1916
|
+
|
1917
|
+
def _addWord(self, word: str, block: QTextBlock, save: bool) -> None:
|
1918
|
+
"""Slot for the spell check context menu triggered when the user
|
1919
|
+
wants to add a word to the project dictionary.
|
1920
|
+
"""
|
1921
|
+
logger.debug("Added '%s' to project dictionary, %s", word, "saved" if save else "unsaved")
|
1922
|
+
SHARED.spelling.addWord(word, save=save)
|
1923
|
+
self._qDocument.syntaxHighlighter.rehighlightBlock(block)
|
1924
|
+
return
|
1925
|
+
|
1878
1926
|
def _processTag(self, cursor: QTextCursor | None = None,
|
1879
1927
|
follow: bool = True, create: bool = False) -> nwTrinary:
|
1880
1928
|
"""Activated by Ctrl+Enter. Checks that we're in a block
|
@@ -1938,11 +1986,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1938
1986
|
self.requestProjectItemRenamed.emit(self._docHandle, text)
|
1939
1987
|
return
|
1940
1988
|
|
1941
|
-
def _openContextFromCursor(self) -> None:
|
1942
|
-
"""Open the spell check context menu at the cursor."""
|
1943
|
-
self._openContextMenu(self.cursorRect().center())
|
1944
|
-
return
|
1945
|
-
|
1946
1989
|
def _docAutoReplace(self, text: str) -> None:
|
1947
1990
|
"""Auto-replace text elements based on main configuration."""
|
1948
1991
|
cursor = self.textCursor()
|
@@ -1952,88 +1995,98 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1952
1995
|
if tLen < 1 or tPos-1 > tLen:
|
1953
1996
|
return
|
1954
1997
|
|
1955
|
-
|
1956
|
-
|
1957
|
-
|
1998
|
+
t1 = text[tPos-1:tPos]
|
1999
|
+
t2 = text[tPos-2:tPos]
|
2000
|
+
t3 = text[tPos-3:tPos]
|
2001
|
+
t4 = text[tPos-4:tPos]
|
1958
2002
|
|
1959
|
-
if not
|
2003
|
+
if not t1:
|
1960
2004
|
return
|
1961
2005
|
|
1962
|
-
|
1963
|
-
|
2006
|
+
delete = 0
|
2007
|
+
insert = t1
|
2008
|
+
tConf = self._typConf
|
1964
2009
|
|
1965
|
-
if
|
1966
|
-
|
1967
|
-
|
2010
|
+
if tConf.typRepDQuote and t2[:1].isspace() and t2.endswith('"'):
|
2011
|
+
delete = 1
|
2012
|
+
insert = tConf.typDQuoteO
|
1968
2013
|
|
1969
|
-
elif
|
1970
|
-
|
2014
|
+
elif tConf.typRepDQuote and t1 == '"':
|
2015
|
+
delete = 1
|
1971
2016
|
if tPos == 1:
|
1972
|
-
|
1973
|
-
elif tPos == 2 and
|
1974
|
-
|
1975
|
-
elif tPos == 3 and
|
1976
|
-
|
2017
|
+
insert = tConf.typDQuoteO
|
2018
|
+
elif tPos == 2 and t2 == '>"':
|
2019
|
+
insert = tConf.typDQuoteO
|
2020
|
+
elif tPos == 3 and t3 == '>>"':
|
2021
|
+
insert = tConf.typDQuoteO
|
1977
2022
|
else:
|
1978
|
-
|
2023
|
+
insert = tConf.typDQuoteC
|
1979
2024
|
|
1980
|
-
elif
|
1981
|
-
|
1982
|
-
|
2025
|
+
elif tConf.typRepSQuote and t2[:1].isspace() and t2.endswith("'"):
|
2026
|
+
delete = 1
|
2027
|
+
insert = tConf.typSQuoteO
|
1983
2028
|
|
1984
|
-
elif
|
1985
|
-
|
2029
|
+
elif tConf.typRepSQuote and t1 == "'":
|
2030
|
+
delete = 1
|
1986
2031
|
if tPos == 1:
|
1987
|
-
|
1988
|
-
elif tPos == 2 and
|
1989
|
-
|
1990
|
-
elif tPos == 3 and
|
1991
|
-
|
2032
|
+
insert = tConf.typSQuoteO
|
2033
|
+
elif tPos == 2 and t2 == ">'":
|
2034
|
+
insert = tConf.typSQuoteO
|
2035
|
+
elif tPos == 3 and t3 == ">>'":
|
2036
|
+
insert = tConf.typSQuoteO
|
1992
2037
|
else:
|
1993
|
-
|
2038
|
+
insert = tConf.typSQuoteC
|
1994
2039
|
|
1995
|
-
elif
|
1996
|
-
|
1997
|
-
|
2040
|
+
elif tConf.typRepDash and t4 == "----":
|
2041
|
+
delete = 4
|
2042
|
+
insert = nwUnicode.U_HBAR
|
1998
2043
|
|
1999
|
-
elif
|
2000
|
-
|
2001
|
-
|
2044
|
+
elif tConf.typRepDash and t3 == "---":
|
2045
|
+
delete = 3
|
2046
|
+
insert = nwUnicode.U_EMDASH
|
2002
2047
|
|
2003
|
-
elif
|
2004
|
-
|
2005
|
-
|
2048
|
+
elif tConf.typRepDash and t2 == "--":
|
2049
|
+
delete = 2
|
2050
|
+
insert = nwUnicode.U_ENDASH
|
2006
2051
|
|
2007
|
-
elif
|
2008
|
-
|
2009
|
-
|
2052
|
+
elif tConf.typRepDash and t2 == nwUnicode.U_ENDASH + "-":
|
2053
|
+
delete = 2
|
2054
|
+
insert = nwUnicode.U_EMDASH
|
2010
2055
|
|
2011
|
-
elif
|
2056
|
+
elif tConf.typRepDash and t2 == nwUnicode.U_EMDASH + "-":
|
2057
|
+
delete = 2
|
2058
|
+
insert = nwUnicode.U_HBAR
|
2059
|
+
|
2060
|
+
elif tConf.typRepDots and t3 == "...":
|
2061
|
+
delete = 3
|
2062
|
+
insert = nwUnicode.U_HELLIP
|
2063
|
+
|
2064
|
+
elif t1 == nwUnicode.U_LSEP:
|
2012
2065
|
# This resolves issue #1150
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2016
|
-
|
2017
|
-
if
|
2018
|
-
if self._allowSpaceBeforeColon(text,
|
2019
|
-
|
2020
|
-
chkPos = tPos -
|
2066
|
+
delete = 1
|
2067
|
+
insert = nwUnicode.U_PSEP
|
2068
|
+
|
2069
|
+
check = insert
|
2070
|
+
if tConf.typPadBefore and check in tConf.typPadBefore:
|
2071
|
+
if self._allowSpaceBeforeColon(text, check):
|
2072
|
+
delete = max(delete, 1)
|
2073
|
+
chkPos = tPos - delete - 1
|
2021
2074
|
if chkPos >= 0 and text[chkPos].isspace():
|
2022
2075
|
# Strip existing space before inserting a new (#1061)
|
2023
|
-
|
2024
|
-
|
2076
|
+
delete += 1
|
2077
|
+
insert = tConf.typPadChar + insert
|
2025
2078
|
|
2026
|
-
if
|
2027
|
-
if self._allowSpaceBeforeColon(text,
|
2028
|
-
|
2029
|
-
|
2079
|
+
if tConf.typPadAfter and check in tConf.typPadAfter:
|
2080
|
+
if self._allowSpaceBeforeColon(text, check):
|
2081
|
+
delete = max(delete, 1)
|
2082
|
+
insert = insert + tConf.typPadChar
|
2030
2083
|
|
2031
|
-
if
|
2032
|
-
cursor.movePosition(QtMoveLeft, QtKeepAnchor,
|
2033
|
-
cursor.insertText(
|
2084
|
+
if delete > 0:
|
2085
|
+
cursor.movePosition(QtMoveLeft, QtKeepAnchor, delete)
|
2086
|
+
cursor.insertText(insert)
|
2034
2087
|
|
2035
|
-
|
2036
|
-
|
2088
|
+
# Re-highlight, since the auto-replace sometimes interferes with it
|
2089
|
+
self._qDocument.syntaxHighlighter.rehighlightBlock(cursor.block())
|
2037
2090
|
|
2038
2091
|
return
|
2039
2092
|
|
@@ -2043,12 +2096,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2043
2096
|
feature for French, Spanish, etc, so it doesn't insert a
|
2044
2097
|
space before colons in meta data lines. See issue #1090.
|
2045
2098
|
"""
|
2046
|
-
if char == ":" and len(text) > 1:
|
2047
|
-
|
2048
|
-
return False
|
2049
|
-
if text[0] == "%":
|
2050
|
-
if text[1:].lstrip()[:9].lower() == "synopsis:":
|
2051
|
-
return False
|
2099
|
+
if char == ":" and len(text) > 1 and text[0] == "@":
|
2100
|
+
return False
|
2052
2101
|
return True
|
2053
2102
|
|
2054
2103
|
def _autoSelect(self) -> QTextCursor:
|
@@ -2173,7 +2222,7 @@ class MetaCompleter(QMenu):
|
|
2173
2222
|
suffix = ""
|
2174
2223
|
options = list(filter(
|
2175
2224
|
lambda x: lookup in x.lower(), SHARED.project.index.getClassTags(
|
2176
|
-
nwKeyWords.KEY_CLASS.get(kw.strip()
|
2225
|
+
nwKeyWords.KEY_CLASS.get(kw.strip())
|
2177
2226
|
)
|
2178
2227
|
))[:15]
|
2179
2228
|
|
@@ -2183,7 +2232,7 @@ class MetaCompleter(QMenu):
|
|
2183
2232
|
for value in sorted(options):
|
2184
2233
|
rep = value + suffix
|
2185
2234
|
action = self.addAction(value)
|
2186
|
-
action.triggered.connect(
|
2235
|
+
action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
|
2187
2236
|
|
2188
2237
|
return True
|
2189
2238
|
|
@@ -2283,61 +2332,61 @@ class GuiDocToolBar(QWidget):
|
|
2283
2332
|
self.tbBoldMD = NIconToolButton(self, iSz)
|
2284
2333
|
self.tbBoldMD.setToolTip(self.tr("Markdown Bold"))
|
2285
2334
|
self.tbBoldMD.clicked.connect(
|
2286
|
-
|
2335
|
+
qtLambda(self.requestDocAction.emit, nwDocAction.MD_BOLD)
|
2287
2336
|
)
|
2288
2337
|
|
2289
2338
|
self.tbItalicMD = NIconToolButton(self, iSz)
|
2290
2339
|
self.tbItalicMD.setToolTip(self.tr("Markdown Italic"))
|
2291
2340
|
self.tbItalicMD.clicked.connect(
|
2292
|
-
|
2341
|
+
qtLambda(self.requestDocAction.emit, nwDocAction.MD_ITALIC)
|
2293
2342
|
)
|
2294
2343
|
|
2295
2344
|
self.tbStrikeMD = NIconToolButton(self, iSz)
|
2296
2345
|
self.tbStrikeMD.setToolTip(self.tr("Markdown Strikethrough"))
|
2297
2346
|
self.tbStrikeMD.clicked.connect(
|
2298
|
-
|
2347
|
+
qtLambda(self.requestDocAction.emit, nwDocAction.MD_STRIKE)
|
2299
2348
|
)
|
2300
2349
|
|
2301
2350
|
self.tbBold = NIconToolButton(self, iSz)
|
2302
2351
|
self.tbBold.setToolTip(self.tr("Shortcode Bold"))
|
2303
2352
|
self.tbBold.clicked.connect(
|
2304
|
-
|
2353
|
+
qtLambda(self.requestDocAction.emit, nwDocAction.SC_BOLD)
|
2305
2354
|
)
|
2306
2355
|
|
2307
2356
|
self.tbItalic = NIconToolButton(self, iSz)
|
2308
2357
|
self.tbItalic.setToolTip(self.tr("Shortcode Italic"))
|
2309
2358
|
self.tbItalic.clicked.connect(
|
2310
|
-
|
2359
|
+
qtLambda(self.requestDocAction.emit, nwDocAction.SC_ITALIC)
|
2311
2360
|
)
|
2312
2361
|
|
2313
2362
|
self.tbStrike = NIconToolButton(self, iSz)
|
2314
2363
|
self.tbStrike.setToolTip(self.tr("Shortcode Strikethrough"))
|
2315
2364
|
self.tbStrike.clicked.connect(
|
2316
|
-
|
2365
|
+
qtLambda(self.requestDocAction.emit, nwDocAction.SC_STRIKE)
|
2317
2366
|
)
|
2318
2367
|
|
2319
2368
|
self.tbUnderline = NIconToolButton(self, iSz)
|
2320
2369
|
self.tbUnderline.setToolTip(self.tr("Shortcode Underline"))
|
2321
2370
|
self.tbUnderline.clicked.connect(
|
2322
|
-
|
2371
|
+
qtLambda(self.requestDocAction.emit, nwDocAction.SC_ULINE)
|
2323
2372
|
)
|
2324
2373
|
|
2325
2374
|
self.tbMark = NIconToolButton(self, iSz)
|
2326
2375
|
self.tbMark.setToolTip(self.tr("Shortcode Highlight"))
|
2327
2376
|
self.tbMark.clicked.connect(
|
2328
|
-
|
2377
|
+
qtLambda(self.requestDocAction.emit, nwDocAction.SC_MARK)
|
2329
2378
|
)
|
2330
2379
|
|
2331
2380
|
self.tbSuperscript = NIconToolButton(self, iSz)
|
2332
2381
|
self.tbSuperscript.setToolTip(self.tr("Shortcode Superscript"))
|
2333
2382
|
self.tbSuperscript.clicked.connect(
|
2334
|
-
|
2383
|
+
qtLambda(self.requestDocAction.emit, nwDocAction.SC_SUP)
|
2335
2384
|
)
|
2336
2385
|
|
2337
2386
|
self.tbSubscript = NIconToolButton(self, iSz)
|
2338
2387
|
self.tbSubscript.setToolTip(self.tr("Shortcode Subscript"))
|
2339
2388
|
self.tbSubscript.clicked.connect(
|
2340
|
-
|
2389
|
+
qtLambda(self.requestDocAction.emit, nwDocAction.SC_SUB)
|
2341
2390
|
)
|
2342
2391
|
|
2343
2392
|
# Assemble
|
@@ -2801,7 +2850,7 @@ class GuiDocEditHeader(QWidget):
|
|
2801
2850
|
self.tbButton = NIconToolButton(self, iSz)
|
2802
2851
|
self.tbButton.setVisible(False)
|
2803
2852
|
self.tbButton.setToolTip(self.tr("Toggle Tool Bar"))
|
2804
|
-
self.tbButton.clicked.connect(
|
2853
|
+
self.tbButton.clicked.connect(qtLambda(self.toggleToolBarRequest.emit))
|
2805
2854
|
|
2806
2855
|
self.outlineButton = NIconToolButton(self, iSz)
|
2807
2856
|
self.outlineButton.setVisible(False)
|
@@ -2816,7 +2865,7 @@ class GuiDocEditHeader(QWidget):
|
|
2816
2865
|
self.minmaxButton = NIconToolButton(self, iSz)
|
2817
2866
|
self.minmaxButton.setVisible(False)
|
2818
2867
|
self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode"))
|
2819
|
-
self.minmaxButton.clicked.connect(
|
2868
|
+
self.minmaxButton.clicked.connect(qtLambda(self.docEditor.toggleFocusModeRequest.emit))
|
2820
2869
|
|
2821
2870
|
self.closeButton = NIconToolButton(self, iSz)
|
2822
2871
|
self.closeButton.setVisible(False)
|
@@ -2879,9 +2928,7 @@ class GuiDocEditHeader(QWidget):
|
|
2879
2928
|
self.outlineMenu.clear()
|
2880
2929
|
for number, text in data.items():
|
2881
2930
|
action = self.outlineMenu.addAction(text)
|
2882
|
-
action.triggered.connect(
|
2883
|
-
lambda _, number=number: self._gotoBlock(number)
|
2884
|
-
)
|
2931
|
+
action.triggered.connect(qtLambda(self._gotoBlock, number))
|
2885
2932
|
self._docOutline = data
|
2886
2933
|
logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart))
|
2887
2934
|
return
|