novelWriter 2.3rc1__py3-none-any.whl → 2.4__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.3rc1.dist-info → novelWriter-2.4.dist-info}/METADATA +5 -6
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/RECORD +119 -109
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/WHEEL +1 -1
- novelWriter-2.4.dist-info/entry_points.txt +2 -0
- novelwriter/__init__.py +17 -10
- 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/i18n/project_nl_NL.json +11 -0
- novelwriter/assets/i18n/project_pt_BR.json +11 -0
- novelwriter/assets/icons/none.svg +4 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tango.conf +23 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +25 -23
- novelwriter/assets/themes/cyberpunk_night.conf +29 -0
- novelwriter/common.py +12 -4
- novelwriter/config.py +47 -16
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +64 -44
- novelwriter/core/coretools.py +97 -13
- novelwriter/core/docbuild.py +74 -7
- novelwriter/core/document.py +24 -3
- novelwriter/core/index.py +31 -112
- novelwriter/core/project.py +11 -15
- novelwriter/core/projectxml.py +3 -2
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/spellcheck.py +3 -3
- novelwriter/core/status.py +6 -5
- novelwriter/core/storage.py +16 -6
- novelwriter/core/tohtml.py +22 -25
- novelwriter/core/tokenizer.py +417 -237
- novelwriter/core/tomd.py +17 -8
- novelwriter/core/toodt.py +386 -351
- novelwriter/core/tree.py +8 -8
- novelwriter/dialogs/about.py +10 -12
- novelwriter/dialogs/docmerge.py +17 -14
- novelwriter/dialogs/docsplit.py +20 -19
- novelwriter/dialogs/editlabel.py +5 -4
- novelwriter/dialogs/preferences.py +32 -40
- novelwriter/dialogs/projectsettings.py +31 -28
- novelwriter/dialogs/quotes.py +10 -9
- novelwriter/dialogs/wordlist.py +18 -15
- novelwriter/enum.py +17 -14
- novelwriter/error.py +14 -12
- novelwriter/extensions/circularprogress.py +12 -8
- novelwriter/extensions/configlayout.py +23 -3
- novelwriter/extensions/modified.py +51 -2
- novelwriter/extensions/pagedsidebar.py +16 -14
- novelwriter/extensions/simpleprogress.py +3 -1
- novelwriter/extensions/statusled.py +3 -1
- novelwriter/extensions/switch.py +10 -9
- novelwriter/extensions/switchbox.py +14 -13
- novelwriter/extensions/versioninfo.py +1 -1
- novelwriter/gui/doceditor.py +433 -496
- novelwriter/gui/dochighlight.py +54 -33
- novelwriter/gui/docviewer.py +162 -175
- novelwriter/gui/docviewerpanel.py +20 -37
- novelwriter/gui/editordocument.py +15 -4
- novelwriter/gui/itemdetails.py +51 -54
- novelwriter/gui/mainmenu.py +37 -17
- novelwriter/gui/noveltree.py +31 -37
- novelwriter/gui/outline.py +120 -98
- novelwriter/gui/projtree.py +114 -112
- novelwriter/gui/search.py +362 -0
- novelwriter/gui/sidebar.py +36 -45
- novelwriter/gui/statusbar.py +14 -14
- novelwriter/gui/theme.py +116 -34
- novelwriter/guimain.py +216 -207
- novelwriter/shared.py +31 -6
- novelwriter/text/counting.py +138 -0
- novelwriter/tools/dictionaries.py +15 -14
- novelwriter/tools/lipsum.py +20 -17
- novelwriter/tools/manusbuild.py +43 -35
- novelwriter/tools/manuscript.py +381 -104
- novelwriter/tools/manussettings.py +263 -125
- novelwriter/tools/noveldetails.py +21 -19
- novelwriter/tools/welcome.py +59 -57
- novelwriter/tools/writingstats.py +61 -55
- novelwriter/types.py +90 -0
- novelWriter-2.3rc1.dist-info/entry_points.txt +0 -5
- novelwriter/core/__init__.py +0 -3
- novelwriter/dialogs/__init__.py +0 -3
- novelwriter/extensions/__init__.py +0 -3
- novelwriter/gui/__init__.py +0 -3
- novelwriter/tools/__init__.py +0 -3
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/top_level.txt +0 -0
novelwriter/gui/doceditor.py
CHANGED
@@ -40,28 +40,34 @@ from typing import TYPE_CHECKING
|
|
40
40
|
|
41
41
|
from PyQt5.QtCore import (
|
42
42
|
pyqtSignal, pyqtSlot, QObject, QPoint, QRegExp, QRegularExpression,
|
43
|
-
QRunnable,
|
43
|
+
QRunnable, Qt, QTimer
|
44
44
|
)
|
45
45
|
from PyQt5.QtGui import (
|
46
46
|
QColor, QCursor, QFont, QKeyEvent, QKeySequence, QMouseEvent, QPalette,
|
47
47
|
QPixmap, QResizeEvent, QTextBlock, QTextCursor, QTextDocument, QTextOption
|
48
48
|
)
|
49
49
|
from PyQt5.QtWidgets import (
|
50
|
-
QAction, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit,
|
51
|
-
|
52
|
-
QWidget, qApp
|
50
|
+
QAction, QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit,
|
51
|
+
QMenu, QPlainTextEdit, QShortcut, QToolBar, QVBoxLayout, QWidget
|
53
52
|
)
|
54
53
|
|
55
54
|
from novelwriter import CONFIG, SHARED
|
56
|
-
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary
|
57
55
|
from novelwriter.common import minmax, transferCase
|
58
|
-
from novelwriter.constants import
|
59
|
-
from novelwriter.core.index import countWords
|
60
|
-
from novelwriter.tools.lipsum import GuiLipsum
|
56
|
+
from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
|
61
57
|
from novelwriter.core.document import NWDocument
|
62
|
-
from novelwriter.
|
63
|
-
from novelwriter.gui.editordocument import GuiTextDocument
|
58
|
+
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary
|
64
59
|
from novelwriter.extensions.eventfilters import WheelEventFilter
|
60
|
+
from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton
|
61
|
+
from novelwriter.gui.dochighlight import BLOCK_META, BLOCK_TITLE
|
62
|
+
from novelwriter.gui.editordocument import GuiTextDocument
|
63
|
+
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
64
|
+
from novelwriter.text.counting import standardCounter
|
65
|
+
from novelwriter.tools.lipsum import GuiLipsum
|
66
|
+
from novelwriter.types import (
|
67
|
+
QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop,
|
68
|
+
QtAlignRight, QtKeepAnchor, QtModCtrl, QtMouseLeft, QtModeNone, QtModShift,
|
69
|
+
QtMoveAnchor, QtMoveLeft, QtMoveRight
|
70
|
+
)
|
65
71
|
|
66
72
|
if TYPE_CHECKING: # pragma: no cover
|
67
73
|
from novelwriter.guimain import GuiMain
|
@@ -90,6 +96,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
90
96
|
# Custom Signals
|
91
97
|
statusMessage = pyqtSignal(str)
|
92
98
|
docCountsChanged = pyqtSignal(str, int, int, int)
|
99
|
+
docTextChanged = pyqtSignal(str, float)
|
93
100
|
editedStatusChanged = pyqtSignal(bool)
|
94
101
|
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
95
102
|
novelStructureChanged = pyqtSignal()
|
@@ -99,6 +106,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
99
106
|
toggleFocusModeRequest = pyqtSignal()
|
100
107
|
requestProjectItemSelected = pyqtSignal(str, bool)
|
101
108
|
requestProjectItemRenamed = pyqtSignal(str, str)
|
109
|
+
requestNewNoteCreation = pyqtSignal(str, nwItemClass)
|
102
110
|
|
103
111
|
def __init__(self, mainGui: GuiMain) -> None:
|
104
112
|
super().__init__(parent=mainGui)
|
@@ -142,9 +150,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
142
150
|
self._qDocument = GuiTextDocument(self)
|
143
151
|
self.setDocument(self._qDocument)
|
144
152
|
|
145
|
-
# Connect Signals
|
153
|
+
# Connect Editor and Document Signals
|
146
154
|
self._qDocument.contentsChange.connect(self._docChange)
|
147
155
|
self.selectionChanged.connect(self._updateSelectedStatus)
|
156
|
+
self.cursorPositionChanged.connect(self._cursorMoved)
|
148
157
|
self.spellCheckStateChanged.connect(self._qDocument.setSpellCheckState)
|
149
158
|
|
150
159
|
# Document Title
|
@@ -153,7 +162,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
153
162
|
self.docSearch = GuiDocEditSearch(self)
|
154
163
|
self.docToolBar = GuiDocToolBar(self)
|
155
164
|
|
156
|
-
# Connect Signals
|
165
|
+
# Connect Widget Signals
|
157
166
|
self.docHeader.closeDocumentRequest.connect(self._closeCurrentDocument)
|
158
167
|
self.docHeader.toggleToolBarRequest.connect(self._toggleToolBarVisibility)
|
159
168
|
self.docToolBar.requestDocAction.connect(self.docAction)
|
@@ -174,28 +183,28 @@ class GuiDocEditor(QPlainTextEdit):
|
|
174
183
|
self.keyContext.activated.connect(self._openContextFromCursor)
|
175
184
|
|
176
185
|
self.followTag1 = QShortcut(self)
|
177
|
-
self.followTag1.setKey(Qt.Key.Key_Return |
|
186
|
+
self.followTag1.setKey(Qt.Key.Key_Return | QtModCtrl)
|
178
187
|
self.followTag1.setContext(Qt.ShortcutContext.WidgetShortcut)
|
179
188
|
self.followTag1.activated.connect(self._processTag)
|
180
189
|
|
181
190
|
self.followTag2 = QShortcut(self)
|
182
|
-
self.followTag2.setKey(Qt.Key.Key_Enter |
|
191
|
+
self.followTag2.setKey(Qt.Key.Key_Enter | QtModCtrl)
|
183
192
|
self.followTag2.setContext(Qt.ShortcutContext.WidgetShortcut)
|
184
193
|
self.followTag2.activated.connect(self._processTag)
|
185
194
|
|
186
195
|
# Set Up Document Word Counter
|
187
|
-
self.
|
188
|
-
self.
|
189
|
-
self.
|
196
|
+
self.timerDoc = QTimer(self)
|
197
|
+
self.timerDoc.timeout.connect(self._runDocumentTasks)
|
198
|
+
self.timerDoc.setInterval(5000)
|
190
199
|
|
191
200
|
self.wCounterDoc = BackgroundWordCounter(self)
|
192
201
|
self.wCounterDoc.setAutoDelete(False)
|
193
202
|
self.wCounterDoc.signals.countsReady.connect(self._updateDocCounts)
|
194
203
|
|
195
204
|
# Set Up Selection Word Counter
|
196
|
-
self.
|
197
|
-
self.
|
198
|
-
self.
|
205
|
+
self.timerSel = QTimer(self)
|
206
|
+
self.timerSel.timeout.connect(self._runSelCounter)
|
207
|
+
self.timerSel.setInterval(500)
|
199
208
|
|
200
209
|
self.wCounterSel = BackgroundWordCounter(self, forSelection=True)
|
201
210
|
self.wCounterSel.setAutoDelete(False)
|
@@ -205,6 +214,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
205
214
|
self.wheelEventFilter = WheelEventFilter(self)
|
206
215
|
self.installEventFilter(self.wheelEventFilter)
|
207
216
|
|
217
|
+
# Function Mapping
|
218
|
+
self.closeSearch = self.docSearch.closeSearch
|
219
|
+
self.searchVisible = self.docSearch.isVisible
|
220
|
+
|
208
221
|
# Finalise
|
209
222
|
self.updateSyntaxColours()
|
210
223
|
self.initEditor()
|
@@ -248,8 +261,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
248
261
|
self._nwDocument = None
|
249
262
|
self.setReadOnly(True)
|
250
263
|
self.clear()
|
251
|
-
self.
|
252
|
-
self.
|
264
|
+
self.timerDoc.stop()
|
265
|
+
self.timerSel.stop()
|
253
266
|
|
254
267
|
self._docHandle = None
|
255
268
|
self._lastEdit = 0.0
|
@@ -258,7 +271,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
258
271
|
self._doReplace = False
|
259
272
|
|
260
273
|
self.setDocumentChanged(False)
|
261
|
-
self.docHeader.
|
274
|
+
self.docHeader.clearHeader()
|
262
275
|
self.docFooter.setHandle(self._docHandle)
|
263
276
|
self.docToolBar.setVisible(False)
|
264
277
|
|
@@ -334,7 +347,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
334
347
|
options = QTextOption()
|
335
348
|
|
336
349
|
if CONFIG.doJustify:
|
337
|
-
options.setAlignment(
|
350
|
+
options.setAlignment(QtAlignJustify)
|
338
351
|
if CONFIG.showTabsNSpaces:
|
339
352
|
options.setFlags(options.flags() | QTextOption.Flag.ShowTabsAndSpaces)
|
340
353
|
if CONFIG.showLineEndings:
|
@@ -362,13 +375,13 @@ class GuiDocEditor(QPlainTextEdit):
|
|
362
375
|
# which makes it read only.
|
363
376
|
if self._docHandle:
|
364
377
|
self._qDocument.syntaxHighlighter.rehighlight()
|
365
|
-
self.docHeader.
|
378
|
+
self.docHeader.setHandle(self._docHandle)
|
366
379
|
else:
|
367
380
|
self.clearEditor()
|
368
381
|
|
369
382
|
return
|
370
383
|
|
371
|
-
def loadText(self, tHandle: str, tLine=None) -> bool:
|
384
|
+
def loadText(self, tHandle: str, tLine: int | None = None) -> bool:
|
372
385
|
"""Load text from a document into the editor. If we have an I/O
|
373
386
|
error, we must handle this and clear the editor so that we don't
|
374
387
|
risk overwriting the file if it exists. This can for instance
|
@@ -386,22 +399,20 @@ class GuiDocEditor(QPlainTextEdit):
|
|
386
399
|
self.clearEditor()
|
387
400
|
return False
|
388
401
|
|
389
|
-
|
402
|
+
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
390
403
|
self._docHandle = tHandle
|
391
404
|
|
392
405
|
self._allowAutoReplace(False)
|
393
406
|
self._qDocument.setTextContent(docText, tHandle)
|
394
407
|
self._allowAutoReplace(True)
|
395
|
-
|
408
|
+
QApplication.processEvents()
|
396
409
|
|
397
410
|
self._lastEdit = time()
|
398
411
|
self._lastActive = time()
|
399
|
-
self.
|
400
|
-
self.
|
412
|
+
self._runDocumentTasks()
|
413
|
+
self.timerDoc.start()
|
401
414
|
|
402
415
|
self.setReadOnly(False)
|
403
|
-
self.docHeader.setTitleFromHandle(self._docHandle)
|
404
|
-
self.docFooter.setHandle(self._docHandle)
|
405
416
|
self.updateDocMargins()
|
406
417
|
|
407
418
|
if tLine is None and self._nwItem is not None:
|
@@ -409,7 +420,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
409
420
|
elif isinstance(tLine, int):
|
410
421
|
self.setCursorLine(tLine)
|
411
422
|
|
412
|
-
self.
|
423
|
+
self.docHeader.setHandle(tHandle)
|
424
|
+
self.docFooter.setHandle(tHandle)
|
413
425
|
|
414
426
|
# This is a hack to fix invisible cursor on an empty document
|
415
427
|
if self._qDocument.characterCount() <= 1:
|
@@ -417,12 +429,12 @@ class GuiDocEditor(QPlainTextEdit):
|
|
417
429
|
self.setPlainText("")
|
418
430
|
self.setCursorPosition(0)
|
419
431
|
|
420
|
-
|
432
|
+
QApplication.processEvents()
|
421
433
|
self.setDocumentChanged(False)
|
422
434
|
self._qDocument.clearUndoRedoStacks()
|
423
435
|
self.docToolBar.setVisible(CONFIG.showEditToolBar)
|
424
436
|
|
425
|
-
|
437
|
+
QApplication.restoreOverrideCursor()
|
426
438
|
|
427
439
|
# Update the status bar
|
428
440
|
if self._nwItem is not None:
|
@@ -432,18 +444,18 @@ class GuiDocEditor(QPlainTextEdit):
|
|
432
444
|
|
433
445
|
def updateTagHighLighting(self) -> None:
|
434
446
|
"""Rerun the syntax highlighter on all meta data lines."""
|
435
|
-
self._qDocument.syntaxHighlighter.rehighlightByType(
|
447
|
+
self._qDocument.syntaxHighlighter.rehighlightByType(BLOCK_META)
|
436
448
|
return
|
437
449
|
|
438
450
|
def replaceText(self, text: str) -> None:
|
439
451
|
"""Replace the text of the current document with the provided
|
440
452
|
text. This also clears undo history.
|
441
453
|
"""
|
442
|
-
|
454
|
+
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
443
455
|
self.setPlainText(text)
|
444
456
|
self.updateDocMargins()
|
445
457
|
self.setDocumentChanged(True)
|
446
|
-
|
458
|
+
QApplication.restoreOverrideCursor()
|
447
459
|
return
|
448
460
|
|
449
461
|
def saveText(self) -> bool:
|
@@ -462,7 +474,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
462
474
|
return False
|
463
475
|
|
464
476
|
docText = self.getText()
|
465
|
-
cC, wC, pC =
|
477
|
+
cC, wC, pC = standardCounter(docText)
|
466
478
|
self._updateDocCounts(cC, wC, pC)
|
467
479
|
|
468
480
|
self.saveCursorPosition()
|
@@ -485,6 +497,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
485
497
|
return False
|
486
498
|
|
487
499
|
self.setDocumentChanged(False)
|
500
|
+
self.docTextChanged.emit(self._docHandle, self._lastEdit)
|
488
501
|
|
489
502
|
oldHeader = self._nwItem.mainHeading
|
490
503
|
oldCount = SHARED.project.index.getHandleHeaderCount(tHandle)
|
@@ -530,7 +543,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
530
543
|
while self.cursorRect().bottom() > vH and count < 100000:
|
531
544
|
vBar.setValue(vBar.value() + 1)
|
532
545
|
count += 1
|
533
|
-
|
546
|
+
QApplication.processEvents()
|
534
547
|
return
|
535
548
|
|
536
549
|
def updateDocMargins(self) -> None:
|
@@ -548,8 +561,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
548
561
|
sH = hBar.height() if hBar.isVisible() else 0
|
549
562
|
|
550
563
|
tM = self._vpMargin
|
551
|
-
if CONFIG.textWidth > 0 or
|
552
|
-
tW = CONFIG.getTextWidth(
|
564
|
+
if CONFIG.textWidth > 0 or SHARED.focusMode:
|
565
|
+
tW = CONFIG.getTextWidth(SHARED.focusMode)
|
553
566
|
tM = max((wW - sW - tW)//2, self._vpMargin)
|
554
567
|
|
555
568
|
tB = self.frameWidth()
|
@@ -590,6 +603,15 @@ class GuiDocEditor(QPlainTextEdit):
|
|
590
603
|
text = text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
|
591
604
|
return text
|
592
605
|
|
606
|
+
def getSelectedText(self) -> str:
|
607
|
+
"""Get currently selected text."""
|
608
|
+
if (cursor := self.textCursor()).hasSelection():
|
609
|
+
text = cursor.selectedText()
|
610
|
+
text = text.replace(nwUnicode.U_LSEP, "\n") # Line separators
|
611
|
+
text = text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
|
612
|
+
return text
|
613
|
+
return ""
|
614
|
+
|
593
615
|
def getCursorPosition(self) -> int:
|
594
616
|
"""Find the cursor position in the document. If the editor has a
|
595
617
|
selection, return the position of the end of the selection.
|
@@ -600,13 +622,15 @@ class GuiDocEditor(QPlainTextEdit):
|
|
600
622
|
# Setters
|
601
623
|
##
|
602
624
|
|
603
|
-
def setDocumentChanged(self, state: bool) ->
|
625
|
+
def setDocumentChanged(self, state: bool) -> None:
|
604
626
|
"""Keep track of the document changed variable, and emit the
|
605
627
|
document change signal.
|
606
628
|
"""
|
607
|
-
self._docChanged
|
608
|
-
|
609
|
-
|
629
|
+
if self._docChanged != state:
|
630
|
+
logger.debug("Document changed status is '%s'", state)
|
631
|
+
self._docChanged = state
|
632
|
+
self.editedStatusChanged.emit(self._docChanged)
|
633
|
+
return
|
610
634
|
|
611
635
|
def setCursorPosition(self, position: int) -> None:
|
612
636
|
"""Move the cursor to a given position in the document."""
|
@@ -616,7 +640,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
616
640
|
cursor.setPosition(minmax(position, 0, nChars-1))
|
617
641
|
self.setTextCursor(cursor)
|
618
642
|
self.centerCursor()
|
619
|
-
self.docFooter.updateLineCount()
|
620
643
|
return
|
621
644
|
|
622
645
|
def saveCursorPosition(self) -> None:
|
@@ -635,6 +658,15 @@ class GuiDocEditor(QPlainTextEdit):
|
|
635
658
|
logger.debug("Cursor moved to line %d", line)
|
636
659
|
return
|
637
660
|
|
661
|
+
def setCursorSelection(self, start: int, length: int) -> None:
|
662
|
+
"""Make a text selection."""
|
663
|
+
if start >= 0 and length > 0:
|
664
|
+
cursor = self.textCursor()
|
665
|
+
cursor.setPosition(start, QtMoveAnchor)
|
666
|
+
cursor.setPosition(start + length, QtKeepAnchor)
|
667
|
+
self.setTextCursor(cursor)
|
668
|
+
return
|
669
|
+
|
638
670
|
##
|
639
671
|
# Spell Checking
|
640
672
|
##
|
@@ -674,9 +706,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
674
706
|
"""
|
675
707
|
logger.debug("Running spell checker")
|
676
708
|
start = time()
|
677
|
-
|
709
|
+
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
678
710
|
self._qDocument.syntaxHighlighter.rehighlight()
|
679
|
-
|
711
|
+
QApplication.restoreOverrideCursor()
|
680
712
|
logger.debug("Document highlighted in %.3f ms", 1000*(time() - start))
|
681
713
|
self.statusMessage.emit(self.tr("Spell check complete"))
|
682
714
|
return
|
@@ -745,6 +777,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
745
777
|
self._formatBlock(nwDocAction.BLOCK_TTL)
|
746
778
|
elif action == nwDocAction.BLOCK_UNN:
|
747
779
|
self._formatBlock(nwDocAction.BLOCK_UNN)
|
780
|
+
elif action == nwDocAction.BLOCK_HSC:
|
781
|
+
self._formatBlock(nwDocAction.BLOCK_HSC)
|
748
782
|
elif action == nwDocAction.REPL_SNG:
|
749
783
|
self._replaceQuotes("'", self._typSQuoteO, self._typSQuoteC)
|
750
784
|
elif action == nwDocAction.REPL_DBL:
|
@@ -769,6 +803,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
769
803
|
self._wrapSelection(nwShortcode.STRIKE_O, nwShortcode.STRIKE_C)
|
770
804
|
elif action == nwDocAction.SC_ULINE:
|
771
805
|
self._wrapSelection(nwShortcode.ULINE_O, nwShortcode.ULINE_C)
|
806
|
+
elif action == nwDocAction.SC_MARK:
|
807
|
+
self._wrapSelection(nwShortcode.MARK_O, nwShortcode.MARK_C)
|
772
808
|
elif action == nwDocAction.SC_SUP:
|
773
809
|
self._wrapSelection(nwShortcode.SUP_O, nwShortcode.SUP_C)
|
774
810
|
elif action == nwDocAction.SC_SUB:
|
@@ -785,11 +821,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
785
821
|
|
786
822
|
def anyFocus(self) -> bool:
|
787
823
|
"""Check if any widget or child widget has focus."""
|
788
|
-
|
789
|
-
return True
|
790
|
-
if self.isAncestorOf(qApp.focusWidget()):
|
791
|
-
return True
|
792
|
-
return False
|
824
|
+
return self.hasFocus() or self.isAncestorOf(QApplication.focusWidget())
|
793
825
|
|
794
826
|
def revealLocation(self) -> None:
|
795
827
|
"""Tell the user where on the file system the file in the editor
|
@@ -898,11 +930,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
898
930
|
|
899
931
|
return True
|
900
932
|
|
901
|
-
def closeSearch(self) -> bool:
|
902
|
-
"""Close the search box."""
|
903
|
-
self.docSearch.closeSearch()
|
904
|
-
return self.docSearch.isVisible()
|
905
|
-
|
906
933
|
##
|
907
934
|
# Document Events and Maintenance
|
908
935
|
##
|
@@ -937,7 +964,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
937
964
|
super().keyPressEvent(event)
|
938
965
|
nPos = self.cursorRect().topLeft().y()
|
939
966
|
kMod = event.modifiers()
|
940
|
-
okMod = kMod in (
|
967
|
+
okMod = kMod in (QtModeNone, QtModShift)
|
941
968
|
okKey = event.key() not in self.MOVE_KEYS
|
942
969
|
if nPos != cPos and okMod and okKey:
|
943
970
|
mPos = CONFIG.autoScrollPos*0.01 * self.viewport().height()
|
@@ -947,8 +974,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
947
974
|
else:
|
948
975
|
super().keyPressEvent(event)
|
949
976
|
|
950
|
-
self.docFooter.updateLineCount()
|
951
|
-
|
952
977
|
return
|
953
978
|
|
954
979
|
def focusNextPrevChild(self, next: bool) -> bool:
|
@@ -960,7 +985,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
960
985
|
if self.hasFocus():
|
961
986
|
return False
|
962
987
|
elif self.docSearch.isVisible():
|
963
|
-
return self.docSearch.cycleFocus(
|
988
|
+
return self.docSearch.cycleFocus()
|
964
989
|
return True
|
965
990
|
|
966
991
|
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
@@ -968,10 +993,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
968
993
|
pressed, check if we're clicking on a tag, and trigger the
|
969
994
|
follow tag function.
|
970
995
|
"""
|
971
|
-
if
|
996
|
+
if QApplication.keyboardModifiers() == QtModCtrl:
|
972
997
|
self._processTag(self.cursorForPosition(event.pos()))
|
973
998
|
super().mouseReleaseEvent(event)
|
974
|
-
self.docFooter.updateLineCount()
|
975
999
|
return
|
976
1000
|
|
977
1001
|
def resizeEvent(self, event: QResizeEvent) -> None:
|
@@ -991,8 +1015,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
991
1015
|
"""Called when an item label is changed to check if the document
|
992
1016
|
title bar needs updating,
|
993
1017
|
"""
|
994
|
-
if tHandle == self._docHandle:
|
995
|
-
self.docHeader.
|
1018
|
+
if tHandle and tHandle == self._docHandle:
|
1019
|
+
self.docHeader.setHandle(tHandle)
|
996
1020
|
self.docFooter.updateInfo()
|
997
1021
|
self.updateDocMargins()
|
998
1022
|
return
|
@@ -1012,8 +1036,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1012
1036
|
@pyqtSlot()
|
1013
1037
|
def toggleSearch(self) -> None:
|
1014
1038
|
"""Toggle the visibility of the search box."""
|
1015
|
-
if self.
|
1016
|
-
self.
|
1039
|
+
if self.searchVisible():
|
1040
|
+
self.closeSearch()
|
1017
1041
|
else:
|
1018
1042
|
self.beginSearch()
|
1019
1043
|
return
|
@@ -1033,32 +1057,35 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1033
1057
|
if not self._docChanged:
|
1034
1058
|
self.setDocumentChanged(removed != 0 or added != 0)
|
1035
1059
|
|
1036
|
-
if not self.
|
1037
|
-
self.
|
1038
|
-
|
1039
|
-
block = self._qDocument.findBlock(pos)
|
1040
|
-
if not block.isValid():
|
1041
|
-
return
|
1060
|
+
if not self.timerDoc.isActive():
|
1061
|
+
self.timerDoc.start()
|
1042
1062
|
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1063
|
+
if (block := self._qDocument.findBlock(pos)).isValid():
|
1064
|
+
text = block.text()
|
1065
|
+
if text.startswith("@") and added + removed == 1:
|
1066
|
+
# Only run on single keypresses, otherwise it will trigger
|
1067
|
+
# at unwanted times when other changes are made to the document
|
1068
|
+
cursor = self.textCursor()
|
1069
|
+
bPos = cursor.positionInBlock()
|
1070
|
+
if bPos > 0:
|
1071
|
+
show = self._completer.updateText(text, bPos)
|
1072
|
+
point = self.cursorRect().bottomRight()
|
1073
|
+
self._completer.move(self.viewport().mapToGlobal(point))
|
1074
|
+
self._completer.setVisible(show)
|
1075
|
+
else:
|
1076
|
+
self._completer.setVisible(False)
|
1056
1077
|
|
1057
1078
|
if self._doReplace and added == 1:
|
1058
1079
|
self._docAutoReplace(text)
|
1059
1080
|
|
1060
1081
|
return
|
1061
1082
|
|
1083
|
+
@pyqtSlot()
|
1084
|
+
def _cursorMoved(self) -> None:
|
1085
|
+
"""Triggered when the cursor moved in the editor."""
|
1086
|
+
self.docFooter.updateLineCount(self.textCursor())
|
1087
|
+
return
|
1088
|
+
|
1062
1089
|
@pyqtSlot(int, int, str)
|
1063
1090
|
def _insertCompletion(self, pos: int, length: int, text: str) -> None:
|
1064
1091
|
"""Insert choice from the completer menu."""
|
@@ -1066,8 +1093,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1066
1093
|
block = cursor.block()
|
1067
1094
|
if block.isValid():
|
1068
1095
|
pos += block.position()
|
1069
|
-
cursor.setPosition(pos,
|
1070
|
-
cursor.setPosition(pos + length,
|
1096
|
+
cursor.setPosition(pos, QtMoveAnchor)
|
1097
|
+
cursor.setPosition(pos + length, QtKeepAnchor)
|
1071
1098
|
cursor.insertText(text)
|
1072
1099
|
self._completer.hide()
|
1073
1100
|
return
|
@@ -1081,7 +1108,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1081
1108
|
|
1082
1109
|
ctxMenu = QMenu(self)
|
1083
1110
|
ctxMenu.setObjectName("ContextMenu")
|
1084
|
-
if pBlock.userState() ==
|
1111
|
+
if pBlock.userState() == BLOCK_TITLE:
|
1085
1112
|
action = ctxMenu.addAction(self.tr("Set as Document Name"))
|
1086
1113
|
action.triggered.connect(lambda: self._emitRenameItem(pBlock))
|
1087
1114
|
|
@@ -1127,9 +1154,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1127
1154
|
block = pCursor.block()
|
1128
1155
|
sCursor = self.textCursor()
|
1129
1156
|
sCursor.setPosition(block.position() + cPos)
|
1130
|
-
sCursor.movePosition(
|
1131
|
-
QTextCursor.MoveOperation.Right, QTextCursor.MoveMode.KeepAnchor, cLen
|
1132
|
-
)
|
1157
|
+
sCursor.movePosition(QtMoveRight, QtKeepAnchor, cLen)
|
1133
1158
|
if suggest:
|
1134
1159
|
ctxMenu.addSeparator()
|
1135
1160
|
ctxMenu.addAction(self.tr("Spelling Suggestion(s)"))
|
@@ -1146,7 +1171,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1146
1171
|
action.triggered.connect(lambda: self._addWord(word, block))
|
1147
1172
|
|
1148
1173
|
# Execute the context menu
|
1149
|
-
ctxMenu.
|
1174
|
+
ctxMenu.exec(self.viewport().mapToGlobal(pos))
|
1150
1175
|
ctxMenu.deleteLater()
|
1151
1176
|
|
1152
1177
|
return
|
@@ -1176,39 +1201,36 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1176
1201
|
return
|
1177
1202
|
|
1178
1203
|
@pyqtSlot()
|
1179
|
-
def
|
1180
|
-
"""
|
1181
|
-
inactivity.
|
1182
|
-
"""
|
1204
|
+
def _runDocumentTasks(self) -> None:
|
1205
|
+
"""Run timer document tasks."""
|
1183
1206
|
if self._docHandle is None:
|
1184
1207
|
return
|
1185
1208
|
|
1186
|
-
if self.wCounterDoc.isRunning():
|
1187
|
-
logger.debug("Word counter is busy")
|
1188
|
-
return
|
1189
|
-
|
1190
1209
|
if time() - self._lastEdit < 25.0:
|
1191
|
-
logger.debug("Running
|
1192
|
-
|
1210
|
+
logger.debug("Running document tasks")
|
1211
|
+
if not self.wCounterDoc.isRunning():
|
1212
|
+
SHARED.runInThreadPool(self.wCounterDoc)
|
1213
|
+
|
1214
|
+
self.docHeader.setOutline({
|
1215
|
+
block.blockNumber(): block.text()
|
1216
|
+
for block in self._qDocument.iterBlockByType(BLOCK_TITLE, maxCount=30)
|
1217
|
+
})
|
1218
|
+
|
1219
|
+
if self._docChanged:
|
1220
|
+
self.docTextChanged.emit(self._docHandle, self._lastEdit)
|
1193
1221
|
|
1194
1222
|
return
|
1195
1223
|
|
1196
1224
|
@pyqtSlot(int, int, int)
|
1197
1225
|
def _updateDocCounts(self, cCount: int, wCount: int, pCount: int) -> None:
|
1198
1226
|
"""Process the word counter's finished signal."""
|
1199
|
-
if self._docHandle
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
self._nwItem.setParaCount(pCount)
|
1207
|
-
|
1208
|
-
# Must not be emitted if docHandle is None!
|
1209
|
-
self.docCountsChanged.emit(self._docHandle, cCount, wCount, pCount)
|
1210
|
-
self.docFooter.updateCounts()
|
1211
|
-
|
1227
|
+
if self._docHandle and self._nwItem:
|
1228
|
+
logger.debug("Updating word count")
|
1229
|
+
self._nwItem.setCharCount(cCount)
|
1230
|
+
self._nwItem.setWordCount(wCount)
|
1231
|
+
self._nwItem.setParaCount(pCount)
|
1232
|
+
self.docCountsChanged.emit(self._docHandle, cCount, wCount, pCount)
|
1233
|
+
self.docFooter.updateWordCount(wCount, False)
|
1212
1234
|
return
|
1213
1235
|
|
1214
1236
|
@pyqtSlot()
|
@@ -1217,13 +1239,11 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1217
1239
|
information to the footer, and start the selection word counter.
|
1218
1240
|
"""
|
1219
1241
|
if self.textCursor().hasSelection():
|
1220
|
-
if not self.
|
1221
|
-
self.
|
1222
|
-
self.docFooter.setHasSelection(True)
|
1242
|
+
if not self.timerSel.isActive():
|
1243
|
+
self.timerSel.start()
|
1223
1244
|
else:
|
1224
|
-
self.
|
1225
|
-
self.docFooter.
|
1226
|
-
self.docFooter.updateCounts()
|
1245
|
+
self.timerSel.stop()
|
1246
|
+
self.docFooter.updateWordCount(0, False)
|
1227
1247
|
return
|
1228
1248
|
|
1229
1249
|
@pyqtSlot()
|
@@ -1243,13 +1263,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1243
1263
|
@pyqtSlot(int, int, int)
|
1244
1264
|
def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
|
1245
1265
|
"""Update the counts on the counter's finished signal."""
|
1246
|
-
if self._docHandle
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
self.docFooter.updateCounts(wCount=wCount, cCount=cCount)
|
1251
|
-
self.wcTimerSel.stop()
|
1252
|
-
|
1266
|
+
if self._docHandle and self._nwItem:
|
1267
|
+
logger.debug("User selected %d words", wCount)
|
1268
|
+
self.docFooter.updateWordCount(wCount, True)
|
1269
|
+
self.timerSel.stop()
|
1253
1270
|
return
|
1254
1271
|
|
1255
1272
|
@pyqtSlot()
|
@@ -1273,11 +1290,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1273
1290
|
|
1274
1291
|
def beginSearch(self) -> None:
|
1275
1292
|
"""Set the selected text as the search text."""
|
1276
|
-
|
1277
|
-
if cursor.hasSelection():
|
1278
|
-
self.docSearch.setSearchText(cursor.selectedText())
|
1279
|
-
else:
|
1280
|
-
self.docSearch.setSearchText(None)
|
1293
|
+
self.docSearch.setSearchText(self.getSelectedText() or None)
|
1281
1294
|
resS, _ = self.findAllOccurences()
|
1282
1295
|
self.docSearch.setResultCount(None, len(resS))
|
1283
1296
|
return
|
@@ -1306,17 +1319,18 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1306
1319
|
if len(resS) == 0 and self._docHandle:
|
1307
1320
|
self.docSearch.setResultCount(0, 0)
|
1308
1321
|
self._lastFind = None
|
1309
|
-
if
|
1322
|
+
if CONFIG.searchNextFile and not goBack:
|
1310
1323
|
self.mainGui.openNextDocument(
|
1311
|
-
self._docHandle, wrapAround=
|
1324
|
+
self._docHandle, wrapAround=CONFIG.searchLoop
|
1312
1325
|
)
|
1313
1326
|
self.beginSearch()
|
1327
|
+
self.setFocus()
|
1314
1328
|
return
|
1315
1329
|
|
1316
1330
|
cursor = self.textCursor()
|
1317
1331
|
resIdx = bisect.bisect_left(resS, cursor.position())
|
1318
1332
|
|
1319
|
-
doLoop =
|
1333
|
+
doLoop = CONFIG.searchLoop
|
1320
1334
|
maxIdx = len(resS) - 1
|
1321
1335
|
|
1322
1336
|
if goBack:
|
@@ -1326,20 +1340,20 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1326
1340
|
resIdx = maxIdx if doLoop else 0
|
1327
1341
|
|
1328
1342
|
if resIdx > maxIdx and self._docHandle:
|
1329
|
-
if
|
1343
|
+
if CONFIG.searchNextFile and not goBack:
|
1330
1344
|
self.mainGui.openNextDocument(
|
1331
|
-
self._docHandle, wrapAround=
|
1345
|
+
self._docHandle, wrapAround=CONFIG.searchLoop
|
1332
1346
|
)
|
1333
1347
|
self.beginSearch()
|
1348
|
+
self.setFocus()
|
1334
1349
|
return
|
1335
1350
|
else:
|
1336
1351
|
resIdx = 0 if doLoop else maxIdx
|
1337
1352
|
|
1338
|
-
cursor.setPosition(resS[resIdx],
|
1339
|
-
cursor.setPosition(resE[resIdx],
|
1353
|
+
cursor.setPosition(resS[resIdx], QtMoveAnchor)
|
1354
|
+
cursor.setPosition(resE[resIdx], QtKeepAnchor)
|
1340
1355
|
self.setTextCursor(cursor)
|
1341
1356
|
|
1342
|
-
self.docFooter.updateLineCount()
|
1343
1357
|
self.docSearch.setResultCount(resIdx + 1, len(resS))
|
1344
1358
|
self._lastFind = (resS[resIdx], resE[resIdx])
|
1345
1359
|
|
@@ -1361,18 +1375,19 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1361
1375
|
origB = cursor.position()
|
1362
1376
|
|
1363
1377
|
findOpt = QTextDocument.FindFlag(0)
|
1364
|
-
if
|
1378
|
+
if CONFIG.searchCase:
|
1365
1379
|
findOpt |= QTextDocument.FindFlag.FindCaseSensitively
|
1366
|
-
if
|
1380
|
+
if CONFIG.searchWord:
|
1367
1381
|
findOpt |= QTextDocument.FindFlag.FindWholeWords
|
1368
1382
|
|
1369
1383
|
searchFor = self.docSearch.getSearchObject()
|
1370
1384
|
cursor.setPosition(0)
|
1371
1385
|
self.setTextCursor(cursor)
|
1372
1386
|
|
1373
|
-
# Search up to a maximum of
|
1374
|
-
# searches like a regex search for .* don't loop
|
1375
|
-
|
1387
|
+
# Search up to a maximum of MAX_SEARCH_RESULT, and make sure
|
1388
|
+
# certain special searches like a regex search for .* don't loop
|
1389
|
+
# infinitely
|
1390
|
+
while self.find(searchFor, findOpt) and len(resE) <= nwConst.MAX_SEARCH_RESULT:
|
1376
1391
|
cursor = self.textCursor()
|
1377
1392
|
if cursor.hasSelection():
|
1378
1393
|
resS.append(cursor.selectionStart())
|
@@ -1382,8 +1397,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1382
1397
|
break
|
1383
1398
|
|
1384
1399
|
if hasSelection:
|
1385
|
-
cursor.setPosition(origA,
|
1386
|
-
cursor.setPosition(origB,
|
1400
|
+
cursor.setPosition(origA, QtMoveAnchor)
|
1401
|
+
cursor.setPosition(origB, QtKeepAnchor)
|
1387
1402
|
else:
|
1388
1403
|
cursor.setPosition(origA)
|
1389
1404
|
|
@@ -1431,18 +1446,12 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1431
1446
|
searchFor = self.docSearch.searchText
|
1432
1447
|
replWith = self.docSearch.replaceText
|
1433
1448
|
|
1434
|
-
if
|
1449
|
+
if CONFIG.searchMatchCap:
|
1435
1450
|
replWith = transferCase(cursor.selectedText(), replWith)
|
1436
1451
|
|
1437
1452
|
# Make sure the selected text was selected by an actual find
|
1438
1453
|
# call, and not the user.
|
1439
|
-
|
1440
|
-
isFind = self._lastFind[0] == cursor.selectionStart()
|
1441
|
-
isFind &= self._lastFind[1] == cursor.selectionEnd()
|
1442
|
-
except Exception:
|
1443
|
-
isFind = False
|
1444
|
-
|
1445
|
-
if isFind:
|
1454
|
+
if self._lastFind == (cursor.selectionStart(), cursor.selectionEnd()):
|
1446
1455
|
cursor.beginEditBlock()
|
1447
1456
|
cursor.removeSelectedText()
|
1448
1457
|
cursor.insertText(replWith)
|
@@ -1451,10 +1460,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1451
1460
|
self.setTextCursor(cursor)
|
1452
1461
|
logger.debug(
|
1453
1462
|
"Replaced occurrence of '%s' with '%s' on line %d",
|
1454
|
-
searchFor, replWith, cursor.blockNumber()
|
1463
|
+
searchFor, replWith, cursor.blockNumber() + 1
|
1455
1464
|
)
|
1456
|
-
else:
|
1457
|
-
logger.error("The selected text is not a search result, skipping replace")
|
1458
1465
|
|
1459
1466
|
self.findNext()
|
1460
1467
|
|
@@ -1464,7 +1471,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1464
1471
|
# Internal Functions : Text Manipulation
|
1465
1472
|
##
|
1466
1473
|
|
1467
|
-
def _toggleFormat(self, fLen: int, fChar: str) ->
|
1474
|
+
def _toggleFormat(self, fLen: int, fChar: str) -> None:
|
1468
1475
|
"""Toggle the formatting of a specific type for a piece of text.
|
1469
1476
|
If more than one block is selected, the formatting is applied to
|
1470
1477
|
the first block.
|
@@ -1482,20 +1489,20 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1482
1489
|
|
1483
1490
|
posS = cursor.selectionStart()
|
1484
1491
|
posE = cursor.selectionEnd()
|
1485
|
-
if self._qDocument.characterAt(posO - 1) == fChar:
|
1492
|
+
if posS == posE and self._qDocument.characterAt(posO - 1) == fChar:
|
1486
1493
|
logger.warning("Format repetition, cancelling action")
|
1487
1494
|
cursor.clearSelection()
|
1488
1495
|
cursor.setPosition(posO)
|
1489
1496
|
self.setTextCursor(cursor)
|
1490
|
-
return
|
1497
|
+
return
|
1491
1498
|
|
1492
1499
|
blockS = self._qDocument.findBlock(posS)
|
1493
1500
|
blockE = self._qDocument.findBlock(posE)
|
1494
1501
|
if blockS != blockE:
|
1495
1502
|
posE = blockS.position() + blockS.length() - 1
|
1496
1503
|
cursor.clearSelection()
|
1497
|
-
cursor.setPosition(posS,
|
1498
|
-
cursor.setPosition(posE,
|
1504
|
+
cursor.setPosition(posS, QtMoveAnchor)
|
1505
|
+
cursor.setPosition(posE, QtKeepAnchor)
|
1499
1506
|
self.setTextCursor(cursor)
|
1500
1507
|
|
1501
1508
|
numB = 0
|
@@ -1513,7 +1520,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1513
1520
|
break
|
1514
1521
|
|
1515
1522
|
if fLen == min(numA, numB):
|
1516
|
-
cursor.clearSelection()
|
1517
1523
|
cursor.beginEditBlock()
|
1518
1524
|
cursor.setPosition(posS)
|
1519
1525
|
for i in range(fLen):
|
@@ -1522,17 +1528,19 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1522
1528
|
for i in range(fLen):
|
1523
1529
|
cursor.deletePreviousChar()
|
1524
1530
|
cursor.endEditBlock()
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1531
|
+
|
1532
|
+
if select != _SelectAction.KEEP_SELECTION:
|
1533
|
+
cursor.clearSelection()
|
1534
|
+
cursor.setPosition(posO - fLen)
|
1535
|
+
self.setTextCursor(cursor)
|
1528
1536
|
|
1529
1537
|
else:
|
1530
1538
|
self._wrapSelection(fChar*fLen, pos=posO, select=select)
|
1531
1539
|
|
1532
|
-
return
|
1540
|
+
return
|
1533
1541
|
|
1534
1542
|
def _wrapSelection(self, before: str, after: str | None = None, pos: int | None = None,
|
1535
|
-
select: _SelectAction = _SelectAction.NO_DECISION) ->
|
1543
|
+
select: _SelectAction = _SelectAction.NO_DECISION) -> None:
|
1536
1544
|
"""Wrap the selected text in whatever is in tBefore and tAfter.
|
1537
1545
|
If there is no selection, the autoSelect setting decides the
|
1538
1546
|
action. AutoSelect will select the word under the cursor before
|
@@ -1572,21 +1580,21 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1572
1580
|
if select == _SelectAction.MOVE_AFTER:
|
1573
1581
|
cursor.setPosition(posE + len(before + after))
|
1574
1582
|
elif select == _SelectAction.KEEP_SELECTION:
|
1575
|
-
cursor.setPosition(
|
1576
|
-
cursor.setPosition(
|
1583
|
+
cursor.setPosition(posS + len(before), QtMoveAnchor)
|
1584
|
+
cursor.setPosition(posE + len(before), QtKeepAnchor)
|
1577
1585
|
elif select == _SelectAction.KEEP_POSITION:
|
1578
1586
|
cursor.setPosition(posO + len(before))
|
1579
1587
|
|
1580
1588
|
self.setTextCursor(cursor)
|
1581
1589
|
|
1582
|
-
return
|
1590
|
+
return
|
1583
1591
|
|
1584
|
-
def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) ->
|
1592
|
+
def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> None:
|
1585
1593
|
"""Replace all straight quotes in the selected text."""
|
1586
1594
|
cursor = self.textCursor()
|
1587
1595
|
if not cursor.hasSelection():
|
1588
1596
|
SHARED.error(self.tr("Please select some text before calling replace quotes."))
|
1589
|
-
return
|
1597
|
+
return
|
1590
1598
|
|
1591
1599
|
posS = cursor.selectionStart()
|
1592
1600
|
posE = cursor.selectionEnd()
|
@@ -1595,9 +1603,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1595
1603
|
self._allowAutoReplace(False)
|
1596
1604
|
for posC in range(posS, posE+1):
|
1597
1605
|
cursor.setPosition(posC)
|
1598
|
-
cursor.movePosition(
|
1599
|
-
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.KeepAnchor, 2
|
1600
|
-
)
|
1606
|
+
cursor.movePosition(QtMoveLeft, QtKeepAnchor, 2)
|
1601
1607
|
selText = cursor.selectedText()
|
1602
1608
|
|
1603
1609
|
nS = len(selText)
|
@@ -1617,22 +1623,18 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1617
1623
|
cursor.setPosition(posC)
|
1618
1624
|
if pC in closeCheck:
|
1619
1625
|
cursor.beginEditBlock()
|
1620
|
-
cursor.movePosition(
|
1621
|
-
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.KeepAnchor, 1
|
1622
|
-
)
|
1626
|
+
cursor.movePosition(QtMoveLeft, QtKeepAnchor, 1)
|
1623
1627
|
cursor.insertText(oQuote)
|
1624
1628
|
cursor.endEditBlock()
|
1625
1629
|
else:
|
1626
1630
|
cursor.beginEditBlock()
|
1627
|
-
cursor.movePosition(
|
1628
|
-
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.KeepAnchor, 1
|
1629
|
-
)
|
1631
|
+
cursor.movePosition(QtMoveLeft, QtKeepAnchor, 1)
|
1630
1632
|
cursor.insertText(cQuote)
|
1631
1633
|
cursor.endEditBlock()
|
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
|
@@ -1670,6 +1672,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1670
1672
|
elif text.startswith("##! "):
|
1671
1673
|
temp = text[4:]
|
1672
1674
|
offset = 4
|
1675
|
+
elif text.startswith("###! "):
|
1676
|
+
temp = text[5:]
|
1677
|
+
offset = 5
|
1673
1678
|
elif text.startswith(">> "):
|
1674
1679
|
temp = text[3:]
|
1675
1680
|
offset = 3
|
@@ -1721,6 +1726,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1721
1726
|
elif action == nwDocAction.BLOCK_UNN:
|
1722
1727
|
text = f"##! {temp}"
|
1723
1728
|
offset -= 4
|
1729
|
+
elif action == nwDocAction.BLOCK_HSC:
|
1730
|
+
text = f"###! {temp}"
|
1731
|
+
offset -= 5
|
1724
1732
|
elif action == nwDocAction.ALIGN_L:
|
1725
1733
|
text = f"{temp} <<"
|
1726
1734
|
elif action == nwDocAction.ALIGN_C:
|
@@ -1836,9 +1844,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1836
1844
|
cursor.beginEditBlock()
|
1837
1845
|
cursor.clearSelection()
|
1838
1846
|
cursor.setPosition(rS)
|
1839
|
-
cursor.movePosition(
|
1840
|
-
QTextCursor.MoveOperation.Right, QTextCursor.MoveMode.KeepAnchor, rE-rS
|
1841
|
-
)
|
1847
|
+
cursor.movePosition(QtMoveRight, QtKeepAnchor, rE-rS)
|
1842
1848
|
cursor.insertText(cleanText.rstrip() + "\n")
|
1843
1849
|
cursor.endEditBlock()
|
1844
1850
|
|
@@ -1867,7 +1873,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1867
1873
|
if text.startswith("@") and self._docHandle:
|
1868
1874
|
|
1869
1875
|
isGood, tBits, tPos = SHARED.project.index.scanThis(text)
|
1870
|
-
if
|
1876
|
+
if (
|
1877
|
+
not isGood or not tBits or tBits[0] == nwKeyWords.TAG_KEY
|
1878
|
+
or tBits[0] not in nwKeyWords.VALID_KEYS
|
1879
|
+
):
|
1871
1880
|
return nwTrinary.NEUTRAL
|
1872
1881
|
|
1873
1882
|
tag = ""
|
@@ -1895,13 +1904,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1895
1904
|
"Do you want to create a new project note for the tag '{0}'?"
|
1896
1905
|
).format(tag)):
|
1897
1906
|
itemClass = nwKeyWords.KEY_CLASS.get(tBits[0], nwItemClass.NO_CLASS)
|
1898
|
-
|
1899
|
-
|
1900
|
-
|
1901
|
-
SHARED.error(self.tr(
|
1902
|
-
"Could not create note in a root folder for '{0}'. "
|
1903
|
-
"If one doesn't exist, you must create one first."
|
1904
|
-
).format(trConst(nwLabels.CLASS_NAME[itemClass])))
|
1907
|
+
self.requestNewNoteCreation.emit(tag, itemClass)
|
1908
|
+
QApplication.processEvents()
|
1909
|
+
self._qDocument.syntaxHighlighter.rehighlightBlock(block)
|
1905
1910
|
|
1906
1911
|
return nwTrinary.POSITIVE if exist else nwTrinary.NEGATIVE
|
1907
1912
|
|
@@ -2005,9 +2010,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2005
2010
|
tInsert = tInsert + self._typPadChar
|
2006
2011
|
|
2007
2012
|
if nDelete > 0:
|
2008
|
-
cursor.movePosition(
|
2009
|
-
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.KeepAnchor, nDelete
|
2010
|
-
)
|
2013
|
+
cursor.movePosition(QtMoveLeft, QtKeepAnchor, nDelete)
|
2011
2014
|
cursor.insertText(tInsert)
|
2012
2015
|
|
2013
2016
|
return
|
@@ -2063,8 +2066,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2063
2066
|
return cursor
|
2064
2067
|
|
2065
2068
|
cursor.clearSelection()
|
2066
|
-
cursor.setPosition(sPos,
|
2067
|
-
cursor.setPosition(ePos,
|
2069
|
+
cursor.setPosition(sPos, QtMoveAnchor)
|
2070
|
+
cursor.setPosition(ePos, QtKeepAnchor)
|
2068
2071
|
|
2069
2072
|
self.setTextCursor(cursor)
|
2070
2073
|
|
@@ -2088,8 +2091,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2088
2091
|
posE = cursor.selectionEnd()
|
2089
2092
|
selTxt = cursor.selectedText()
|
2090
2093
|
if selTxt.startswith(nwUnicode.U_PSEP):
|
2091
|
-
cursor.setPosition(posS+1,
|
2092
|
-
cursor.setPosition(posE,
|
2094
|
+
cursor.setPosition(posS+1, QtMoveAnchor)
|
2095
|
+
cursor.setPosition(posE, QtKeepAnchor)
|
2093
2096
|
|
2094
2097
|
self.setTextCursor(cursor)
|
2095
2098
|
|
@@ -2184,7 +2187,7 @@ class MetaCompleter(QMenu):
|
|
2184
2187
|
# Internal Functions
|
2185
2188
|
##
|
2186
2189
|
|
2187
|
-
def _emitComplete(self, pos: int, length: int, value: str):
|
2190
|
+
def _emitComplete(self, pos: int, length: int, value: str) -> None:
|
2188
2191
|
"""Emit the signal to indicate a selection has been made."""
|
2189
2192
|
self.complete.emit(pos, length, value)
|
2190
2193
|
return
|
@@ -2220,11 +2223,11 @@ class BackgroundWordCounter(QRunnable):
|
|
2220
2223
|
"""
|
2221
2224
|
self._isRunning = True
|
2222
2225
|
if self._forSelection:
|
2223
|
-
text = self._docEditor.
|
2226
|
+
text = self._docEditor.getSelectedText()
|
2224
2227
|
else:
|
2225
2228
|
text = self._docEditor.getText()
|
2226
2229
|
|
2227
|
-
cC, wC, pC =
|
2230
|
+
cC, wC, pC = standardCounter(text)
|
2228
2231
|
self.signals.countsReady.emit(cC, wC, pC)
|
2229
2232
|
self._isRunning = False
|
2230
2233
|
|
@@ -2256,72 +2259,68 @@ class GuiDocToolBar(QWidget):
|
|
2256
2259
|
|
2257
2260
|
logger.debug("Create: GuiDocToolBar")
|
2258
2261
|
|
2262
|
+
iSz = SHARED.theme.baseIconSize
|
2259
2263
|
cM = CONFIG.pxInt(4)
|
2260
|
-
tPx = int(0.8*SHARED.theme.fontPixelSize)
|
2261
|
-
iconSize = QSize(tPx, tPx)
|
2262
2264
|
self.setContentsMargins(0, 0, 0, 0)
|
2263
2265
|
|
2264
2266
|
# General Buttons
|
2265
2267
|
# ===============
|
2266
2268
|
|
2267
|
-
self.tbBoldMD =
|
2268
|
-
self.tbBoldMD.setIconSize(iconSize)
|
2269
|
+
self.tbBoldMD = NIconToolButton(self, iSz)
|
2269
2270
|
self.tbBoldMD.setToolTip(self.tr("Markdown Bold"))
|
2270
2271
|
self.tbBoldMD.clicked.connect(
|
2271
2272
|
lambda: self.requestDocAction.emit(nwDocAction.MD_BOLD)
|
2272
2273
|
)
|
2273
2274
|
|
2274
|
-
self.tbItalicMD =
|
2275
|
-
self.tbItalicMD.setIconSize(iconSize)
|
2275
|
+
self.tbItalicMD = NIconToolButton(self, iSz)
|
2276
2276
|
self.tbItalicMD.setToolTip(self.tr("Markdown Italic"))
|
2277
2277
|
self.tbItalicMD.clicked.connect(
|
2278
2278
|
lambda: self.requestDocAction.emit(nwDocAction.MD_ITALIC)
|
2279
2279
|
)
|
2280
2280
|
|
2281
|
-
self.tbStrikeMD =
|
2282
|
-
self.tbStrikeMD.setIconSize(iconSize)
|
2281
|
+
self.tbStrikeMD = NIconToolButton(self, iSz)
|
2283
2282
|
self.tbStrikeMD.setToolTip(self.tr("Markdown Strikethrough"))
|
2284
2283
|
self.tbStrikeMD.clicked.connect(
|
2285
2284
|
lambda: self.requestDocAction.emit(nwDocAction.MD_STRIKE)
|
2286
2285
|
)
|
2287
2286
|
|
2288
|
-
self.tbBold =
|
2289
|
-
self.tbBold.setIconSize(iconSize)
|
2287
|
+
self.tbBold = NIconToolButton(self, iSz)
|
2290
2288
|
self.tbBold.setToolTip(self.tr("Shortcode Bold"))
|
2291
2289
|
self.tbBold.clicked.connect(
|
2292
2290
|
lambda: self.requestDocAction.emit(nwDocAction.SC_BOLD)
|
2293
2291
|
)
|
2294
2292
|
|
2295
|
-
self.tbItalic =
|
2296
|
-
self.tbItalic.setIconSize(iconSize)
|
2293
|
+
self.tbItalic = NIconToolButton(self, iSz)
|
2297
2294
|
self.tbItalic.setToolTip(self.tr("Shortcode Italic"))
|
2298
2295
|
self.tbItalic.clicked.connect(
|
2299
2296
|
lambda: self.requestDocAction.emit(nwDocAction.SC_ITALIC)
|
2300
2297
|
)
|
2301
2298
|
|
2302
|
-
self.tbStrike =
|
2303
|
-
self.tbStrike.setIconSize(iconSize)
|
2299
|
+
self.tbStrike = NIconToolButton(self, iSz)
|
2304
2300
|
self.tbStrike.setToolTip(self.tr("Shortcode Strikethrough"))
|
2305
2301
|
self.tbStrike.clicked.connect(
|
2306
2302
|
lambda: self.requestDocAction.emit(nwDocAction.SC_STRIKE)
|
2307
2303
|
)
|
2308
2304
|
|
2309
|
-
self.tbUnderline =
|
2310
|
-
self.tbUnderline.setIconSize(iconSize)
|
2305
|
+
self.tbUnderline = NIconToolButton(self, iSz)
|
2311
2306
|
self.tbUnderline.setToolTip(self.tr("Shortcode Underline"))
|
2312
2307
|
self.tbUnderline.clicked.connect(
|
2313
2308
|
lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE)
|
2314
2309
|
)
|
2315
2310
|
|
2316
|
-
self.
|
2317
|
-
self.
|
2311
|
+
self.tbMark = NIconToolButton(self, iSz)
|
2312
|
+
self.tbMark.setToolTip(self.tr("Shortcode Highlight"))
|
2313
|
+
self.tbMark.clicked.connect(
|
2314
|
+
lambda: self.requestDocAction.emit(nwDocAction.SC_MARK)
|
2315
|
+
)
|
2316
|
+
|
2317
|
+
self.tbSuperscript = NIconToolButton(self, iSz)
|
2318
2318
|
self.tbSuperscript.setToolTip(self.tr("Shortcode Superscript"))
|
2319
2319
|
self.tbSuperscript.clicked.connect(
|
2320
2320
|
lambda: self.requestDocAction.emit(nwDocAction.SC_SUP)
|
2321
2321
|
)
|
2322
2322
|
|
2323
|
-
self.tbSubscript =
|
2324
|
-
self.tbSubscript.setIconSize(iconSize)
|
2323
|
+
self.tbSubscript = NIconToolButton(self, iSz)
|
2325
2324
|
self.tbSubscript.setToolTip(self.tr("Shortcode Subscript"))
|
2326
2325
|
self.tbSubscript.clicked.connect(
|
2327
2326
|
lambda: self.requestDocAction.emit(nwDocAction.SC_SUB)
|
@@ -2339,6 +2338,7 @@ class GuiDocToolBar(QWidget):
|
|
2339
2338
|
self.outerBox.addWidget(self.tbItalic)
|
2340
2339
|
self.outerBox.addWidget(self.tbStrike)
|
2341
2340
|
self.outerBox.addWidget(self.tbUnderline)
|
2341
|
+
self.outerBox.addWidget(self.tbMark)
|
2342
2342
|
self.outerBox.addWidget(self.tbSuperscript)
|
2343
2343
|
self.outerBox.addWidget(self.tbSubscript)
|
2344
2344
|
self.outerBox.setContentsMargins(cM, cM, cM, cM)
|
@@ -2362,15 +2362,16 @@ class GuiDocToolBar(QWidget):
|
|
2362
2362
|
palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
|
2363
2363
|
self.setPalette(palette)
|
2364
2364
|
|
2365
|
-
self.tbBoldMD.
|
2366
|
-
self.tbItalicMD.
|
2367
|
-
self.tbStrikeMD.
|
2368
|
-
self.tbBold.
|
2369
|
-
self.tbItalic.
|
2370
|
-
self.tbStrike.
|
2371
|
-
self.tbUnderline.
|
2372
|
-
self.
|
2373
|
-
self.
|
2365
|
+
self.tbBoldMD.setThemeIcon("fmt_bold-md")
|
2366
|
+
self.tbItalicMD.setThemeIcon("fmt_italic-md")
|
2367
|
+
self.tbStrikeMD.setThemeIcon("fmt_strike-md")
|
2368
|
+
self.tbBold.setThemeIcon("fmt_bold")
|
2369
|
+
self.tbItalic.setThemeIcon("fmt_italic")
|
2370
|
+
self.tbStrike.setThemeIcon("fmt_strike")
|
2371
|
+
self.tbUnderline.setThemeIcon("fmt_underline")
|
2372
|
+
self.tbMark.setThemeIcon("fmt_mark")
|
2373
|
+
self.tbSuperscript.setThemeIcon("fmt_superscript")
|
2374
|
+
self.tbSubscript.setThemeIcon("fmt_subscript")
|
2374
2375
|
|
2375
2376
|
return
|
2376
2377
|
|
@@ -2391,16 +2392,9 @@ class GuiDocEditSearch(QFrame):
|
|
2391
2392
|
|
2392
2393
|
self.docEditor = docEditor
|
2393
2394
|
|
2394
|
-
|
2395
|
-
self.isCaseSense = CONFIG.searchCase
|
2396
|
-
self.isWholeWord = CONFIG.searchWord
|
2397
|
-
self.isRegEx = CONFIG.searchRegEx
|
2398
|
-
self.doLoop = CONFIG.searchLoop
|
2399
|
-
self.doNextFile = CONFIG.searchNextFile
|
2400
|
-
self.doMatchCap = CONFIG.searchMatchCap
|
2401
|
-
|
2395
|
+
iSz = SHARED.theme.baseIconSize
|
2402
2396
|
mPx = CONFIG.pxInt(6)
|
2403
|
-
|
2397
|
+
|
2404
2398
|
self.boxFont = SHARED.theme.guiFont
|
2405
2399
|
self.boxFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
2406
2400
|
|
@@ -2416,54 +2410,54 @@ class GuiDocEditSearch(QFrame):
|
|
2416
2410
|
|
2417
2411
|
self.searchBox = QLineEdit(self)
|
2418
2412
|
self.searchBox.setFont(self.boxFont)
|
2419
|
-
self.searchBox.setPlaceholderText(self.tr("Search"))
|
2413
|
+
self.searchBox.setPlaceholderText(self.tr("Search for"))
|
2420
2414
|
self.searchBox.returnPressed.connect(self._doSearch)
|
2421
2415
|
|
2422
2416
|
self.replaceBox = QLineEdit(self)
|
2423
2417
|
self.replaceBox.setFont(self.boxFont)
|
2424
|
-
self.replaceBox.setPlaceholderText(self.tr("Replace"))
|
2418
|
+
self.replaceBox.setPlaceholderText(self.tr("Replace with"))
|
2425
2419
|
self.replaceBox.returnPressed.connect(self._doReplace)
|
2426
2420
|
|
2427
2421
|
self.searchOpt = QToolBar(self)
|
2428
2422
|
self.searchOpt.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
2429
|
-
self.searchOpt.setIconSize(
|
2423
|
+
self.searchOpt.setIconSize(iSz)
|
2430
2424
|
self.searchOpt.setContentsMargins(0, 0, 0, 0)
|
2431
2425
|
|
2432
|
-
self.searchLabel = QLabel(self.tr("Search"))
|
2426
|
+
self.searchLabel = QLabel(self.tr("Search"), self)
|
2433
2427
|
self.searchLabel.setFont(self.boxFont)
|
2434
2428
|
self.searchLabel.setIndent(CONFIG.pxInt(6))
|
2435
2429
|
|
2436
|
-
self.resultLabel = QLabel("?/?")
|
2430
|
+
self.resultLabel = QLabel("?/?", self)
|
2437
2431
|
self.resultLabel.setFont(self.boxFont)
|
2438
2432
|
self.resultLabel.setMinimumWidth(SHARED.theme.getTextWidth("?/?", self.boxFont))
|
2439
2433
|
|
2440
2434
|
self.toggleCase = QAction(self.tr("Case Sensitive"), self)
|
2441
2435
|
self.toggleCase.setCheckable(True)
|
2442
|
-
self.toggleCase.setChecked(
|
2436
|
+
self.toggleCase.setChecked(CONFIG.searchCase)
|
2443
2437
|
self.toggleCase.toggled.connect(self._doToggleCase)
|
2444
2438
|
self.searchOpt.addAction(self.toggleCase)
|
2445
2439
|
|
2446
2440
|
self.toggleWord = QAction(self.tr("Whole Words Only"), self)
|
2447
2441
|
self.toggleWord.setCheckable(True)
|
2448
|
-
self.toggleWord.setChecked(
|
2442
|
+
self.toggleWord.setChecked(CONFIG.searchWord)
|
2449
2443
|
self.toggleWord.toggled.connect(self._doToggleWord)
|
2450
2444
|
self.searchOpt.addAction(self.toggleWord)
|
2451
2445
|
|
2452
2446
|
self.toggleRegEx = QAction(self.tr("RegEx Mode"), self)
|
2453
2447
|
self.toggleRegEx.setCheckable(True)
|
2454
|
-
self.toggleRegEx.setChecked(
|
2448
|
+
self.toggleRegEx.setChecked(CONFIG.searchRegEx)
|
2455
2449
|
self.toggleRegEx.toggled.connect(self._doToggleRegEx)
|
2456
2450
|
self.searchOpt.addAction(self.toggleRegEx)
|
2457
2451
|
|
2458
2452
|
self.toggleLoop = QAction(self.tr("Loop Search"), self)
|
2459
2453
|
self.toggleLoop.setCheckable(True)
|
2460
|
-
self.toggleLoop.setChecked(
|
2454
|
+
self.toggleLoop.setChecked(CONFIG.searchLoop)
|
2461
2455
|
self.toggleLoop.toggled.connect(self._doToggleLoop)
|
2462
2456
|
self.searchOpt.addAction(self.toggleLoop)
|
2463
2457
|
|
2464
2458
|
self.toggleProject = QAction(self.tr("Search Next File"), self)
|
2465
2459
|
self.toggleProject.setCheckable(True)
|
2466
|
-
self.toggleProject.setChecked(
|
2460
|
+
self.toggleProject.setChecked(CONFIG.searchNextFile)
|
2467
2461
|
self.toggleProject.toggled.connect(self._doToggleProject)
|
2468
2462
|
self.searchOpt.addAction(self.toggleProject)
|
2469
2463
|
|
@@ -2471,38 +2465,32 @@ class GuiDocEditSearch(QFrame):
|
|
2471
2465
|
|
2472
2466
|
self.toggleMatchCap = QAction(self.tr("Preserve Case"), self)
|
2473
2467
|
self.toggleMatchCap.setCheckable(True)
|
2474
|
-
self.toggleMatchCap.setChecked(
|
2468
|
+
self.toggleMatchCap.setChecked(CONFIG.searchMatchCap)
|
2475
2469
|
self.toggleMatchCap.toggled.connect(self._doToggleMatchCap)
|
2476
2470
|
self.searchOpt.addAction(self.toggleMatchCap)
|
2477
2471
|
|
2478
2472
|
self.searchOpt.addSeparator()
|
2479
2473
|
|
2480
2474
|
self.cancelSearch = QAction(self.tr("Close Search"), self)
|
2481
|
-
self.cancelSearch.triggered.connect(self.
|
2475
|
+
self.cancelSearch.triggered.connect(self.closeSearch)
|
2482
2476
|
self.searchOpt.addAction(self.cancelSearch)
|
2483
2477
|
|
2484
2478
|
# Buttons
|
2485
2479
|
# =======
|
2486
2480
|
|
2487
|
-
|
2488
|
-
|
2489
|
-
self.showReplace = QToolButton(self)
|
2490
|
-
self.showReplace.setArrowType(Qt.ArrowType.RightArrow)
|
2491
|
-
self.showReplace.setCheckable(True)
|
2481
|
+
self.showReplace = NIconToggleButton(self, iSz, "unfold")
|
2492
2482
|
self.showReplace.toggled.connect(self._doToggleReplace)
|
2493
2483
|
|
2494
|
-
self.searchButton =
|
2495
|
-
self.searchButton.setFixedSize(QSize(bPx, bPx))
|
2484
|
+
self.searchButton = NIconToolButton(self, iSz)
|
2496
2485
|
self.searchButton.setToolTip(self.tr("Find in current document"))
|
2497
2486
|
self.searchButton.clicked.connect(self._doSearch)
|
2498
2487
|
|
2499
|
-
self.replaceButton =
|
2500
|
-
self.replaceButton.setFixedSize(QSize(bPx, bPx))
|
2488
|
+
self.replaceButton = NIconToolButton(self, iSz)
|
2501
2489
|
self.replaceButton.setToolTip(self.tr("Find and replace in current document"))
|
2502
2490
|
self.replaceButton.clicked.connect(self._doReplace)
|
2503
2491
|
|
2504
|
-
self.mainBox.addWidget(self.searchLabel, 0, 0, 1, 2,
|
2505
|
-
self.mainBox.addWidget(self.searchOpt, 0, 2, 1, 3,
|
2492
|
+
self.mainBox.addWidget(self.searchLabel, 0, 0, 1, 2, QtAlignLeft)
|
2493
|
+
self.mainBox.addWidget(self.searchOpt, 0, 2, 1, 3, QtAlignRight)
|
2506
2494
|
self.mainBox.addWidget(self.showReplace, 1, 0, 1, 1)
|
2507
2495
|
self.mainBox.addWidget(self.searchBox, 1, 1, 1, 2)
|
2508
2496
|
self.mainBox.addWidget(self.searchButton, 1, 3, 1, 1)
|
@@ -2555,20 +2543,20 @@ class GuiDocEditSearch(QFrame):
|
|
2555
2543
|
expression object.
|
2556
2544
|
"""
|
2557
2545
|
text = self.searchBox.text()
|
2558
|
-
if
|
2546
|
+
if CONFIG.searchRegEx:
|
2559
2547
|
# Using the Unicode-capable QRegularExpression class was
|
2560
2548
|
# only added in Qt 5.13. Otherwise, 5.3 and up supports
|
2561
2549
|
# only the QRegExp class.
|
2562
2550
|
if CONFIG.verQtValue >= 0x050d00:
|
2563
2551
|
rxOpt = QRegularExpression.PatternOption.UseUnicodePropertiesOption
|
2564
|
-
if not
|
2552
|
+
if not CONFIG.searchCase:
|
2565
2553
|
rxOpt |= QRegularExpression.PatternOption.CaseInsensitiveOption
|
2566
2554
|
regEx = QRegularExpression(text, rxOpt)
|
2567
2555
|
self._alertSearchValid(regEx.isValid())
|
2568
2556
|
return regEx
|
2569
2557
|
else: # pragma: no cover
|
2570
2558
|
# >= 50300 to < 51300
|
2571
|
-
if
|
2559
|
+
if CONFIG.searchCase:
|
2572
2560
|
rxOpt = Qt.CaseSensitivity.CaseSensitive
|
2573
2561
|
else:
|
2574
2562
|
rxOpt = Qt.CaseSensitivity.CaseInsensitive
|
@@ -2592,7 +2580,7 @@ class GuiDocEditSearch(QFrame):
|
|
2592
2580
|
self.searchBox.setText(text)
|
2593
2581
|
self.searchBox.setFocus()
|
2594
2582
|
self.searchBox.selectAll()
|
2595
|
-
if
|
2583
|
+
if CONFIG.searchRegEx:
|
2596
2584
|
self._alertSearchValid(True)
|
2597
2585
|
return
|
2598
2586
|
|
@@ -2605,8 +2593,10 @@ class GuiDocEditSearch(QFrame):
|
|
2605
2593
|
|
2606
2594
|
def setResultCount(self, currRes: int | None, resCount: int | None) -> None:
|
2607
2595
|
"""Set the count values for the current search."""
|
2596
|
+
lim = nwConst.MAX_SEARCH_RESULT
|
2597
|
+
numCount = f"{lim:n}+" if (resCount or 0) > lim else f"{resCount:n}"
|
2608
2598
|
sCurrRes = "?" if currRes is None else str(currRes)
|
2609
|
-
sResCount = "?" if resCount is None else
|
2599
|
+
sResCount = "?" if resCount is None else numCount
|
2610
2600
|
minWidth = SHARED.theme.getTextWidth(f"{sResCount}//{sResCount}", self.boxFont)
|
2611
2601
|
self.resultLabel.setText(f"{sCurrRes}/{sResCount}")
|
2612
2602
|
self.resultLabel.setMinimumWidth(minWidth)
|
@@ -2620,7 +2610,7 @@ class GuiDocEditSearch(QFrame):
|
|
2620
2610
|
|
2621
2611
|
def updateTheme(self) -> None:
|
2622
2612
|
"""Update theme elements."""
|
2623
|
-
qPalette =
|
2613
|
+
qPalette = QApplication.palette()
|
2624
2614
|
self.setPalette(qPalette)
|
2625
2615
|
self.searchBox.setPalette(qPalette)
|
2626
2616
|
self.replaceBox.setPalette(qPalette)
|
@@ -2633,8 +2623,8 @@ class GuiDocEditSearch(QFrame):
|
|
2633
2623
|
self.toggleProject.setIcon(SHARED.theme.getIcon("search_project"))
|
2634
2624
|
self.toggleMatchCap.setIcon(SHARED.theme.getIcon("search_preserve"))
|
2635
2625
|
self.cancelSearch.setIcon(SHARED.theme.getIcon("search_cancel"))
|
2636
|
-
self.searchButton.
|
2637
|
-
self.replaceButton.
|
2626
|
+
self.searchButton.setThemeIcon("search")
|
2627
|
+
self.replaceButton.setThemeIcon("search_replace")
|
2638
2628
|
|
2639
2629
|
# Set stylesheets
|
2640
2630
|
self.searchOpt.setStyleSheet("QToolBar {padding: 0;}")
|
@@ -2660,57 +2650,43 @@ class GuiDocEditSearch(QFrame):
|
|
2660
2650
|
|
2661
2651
|
return
|
2662
2652
|
|
2663
|
-
def
|
2664
|
-
"""Close the search box."""
|
2665
|
-
CONFIG.searchCase = self.isCaseSense
|
2666
|
-
CONFIG.searchWord = self.isWholeWord
|
2667
|
-
CONFIG.searchRegEx = self.isRegEx
|
2668
|
-
CONFIG.searchLoop = self.doLoop
|
2669
|
-
CONFIG.searchNextFile = self.doNextFile
|
2670
|
-
CONFIG.searchMatchCap = self.doMatchCap
|
2671
|
-
|
2672
|
-
self.showReplace.setChecked(False)
|
2673
|
-
self.setVisible(False)
|
2674
|
-
self.docEditor.updateDocMargins()
|
2675
|
-
self.docEditor.setFocus()
|
2676
|
-
|
2677
|
-
return
|
2678
|
-
|
2679
|
-
def cycleFocus(self, next: bool) -> bool:
|
2653
|
+
def cycleFocus(self) -> bool:
|
2680
2654
|
"""The tab key just alternates focus between the two input
|
2681
2655
|
boxes, if the replace box is visible.
|
2682
2656
|
"""
|
2683
|
-
if self.
|
2684
|
-
|
2685
|
-
|
2686
|
-
|
2687
|
-
|
2688
|
-
|
2689
|
-
return True
|
2657
|
+
if self.searchBox.hasFocus():
|
2658
|
+
self.replaceBox.setFocus()
|
2659
|
+
return True
|
2660
|
+
elif self.replaceBox.hasFocus():
|
2661
|
+
self.searchBox.setFocus()
|
2662
|
+
return True
|
2690
2663
|
return False
|
2691
2664
|
|
2692
2665
|
def anyFocus(self) -> bool:
|
2693
2666
|
"""Return True if any of the input boxes have focus."""
|
2694
|
-
return self.searchBox.hasFocus()
|
2667
|
+
return self.searchBox.hasFocus() or self.replaceBox.hasFocus()
|
2695
2668
|
|
2696
2669
|
##
|
2697
|
-
#
|
2670
|
+
# Public Slots
|
2698
2671
|
##
|
2699
2672
|
|
2700
2673
|
@pyqtSlot()
|
2701
|
-
def
|
2702
|
-
"""
|
2703
|
-
self.
|
2674
|
+
def closeSearch(self) -> None:
|
2675
|
+
"""Close the search box."""
|
2676
|
+
self.showReplace.setChecked(False)
|
2677
|
+
self.setVisible(False)
|
2678
|
+
self.docEditor.updateDocMargins()
|
2679
|
+
self.docEditor.setFocus()
|
2704
2680
|
return
|
2705
2681
|
|
2682
|
+
##
|
2683
|
+
# Private Slots
|
2684
|
+
##
|
2685
|
+
|
2706
2686
|
@pyqtSlot()
|
2707
2687
|
def _doSearch(self) -> None:
|
2708
2688
|
"""Call the search action function for the document editor."""
|
2709
|
-
|
2710
|
-
if modKey == Qt.KeyboardModifier.ShiftModifier:
|
2711
|
-
self.docEditor.findNext(goBack=True)
|
2712
|
-
else:
|
2713
|
-
self.docEditor.findNext()
|
2689
|
+
self.docEditor.findNext(goBack=(QApplication.keyboardModifiers() == QtModShift))
|
2714
2690
|
return
|
2715
2691
|
|
2716
2692
|
@pyqtSlot()
|
@@ -2722,13 +2698,8 @@ class GuiDocEditSearch(QFrame):
|
|
2722
2698
|
@pyqtSlot(bool)
|
2723
2699
|
def _doToggleReplace(self, state: bool) -> None:
|
2724
2700
|
"""Toggle the show/hide of the replace box."""
|
2725
|
-
if state:
|
2726
|
-
self.showReplace.setArrowType(Qt.ArrowType.DownArrow)
|
2727
|
-
else:
|
2728
|
-
self.showReplace.setArrowType(Qt.ArrowType.RightArrow)
|
2729
2701
|
self.replaceBox.setVisible(state)
|
2730
2702
|
self.replaceButton.setVisible(state)
|
2731
|
-
self.repVisible = state
|
2732
2703
|
self.adjustSize()
|
2733
2704
|
self.docEditor.updateDocMargins()
|
2734
2705
|
return
|
@@ -2736,37 +2707,37 @@ class GuiDocEditSearch(QFrame):
|
|
2736
2707
|
@pyqtSlot(bool)
|
2737
2708
|
def _doToggleCase(self, state: bool) -> None:
|
2738
2709
|
"""Enable/disable case sensitive mode."""
|
2739
|
-
|
2710
|
+
CONFIG.searchCase = state
|
2740
2711
|
return
|
2741
2712
|
|
2742
2713
|
@pyqtSlot(bool)
|
2743
2714
|
def _doToggleWord(self, state: bool) -> None:
|
2744
2715
|
"""Enable/disable whole word search mode."""
|
2745
|
-
|
2716
|
+
CONFIG.searchWord = state
|
2746
2717
|
return
|
2747
2718
|
|
2748
2719
|
@pyqtSlot(bool)
|
2749
2720
|
def _doToggleRegEx(self, state: bool) -> None:
|
2750
2721
|
"""Enable/disable regular expression search mode."""
|
2751
|
-
|
2722
|
+
CONFIG.searchRegEx = state
|
2752
2723
|
return
|
2753
2724
|
|
2754
2725
|
@pyqtSlot(bool)
|
2755
2726
|
def _doToggleLoop(self, state: bool) -> None:
|
2756
2727
|
"""Enable/disable looping the search."""
|
2757
|
-
|
2728
|
+
CONFIG.searchLoop = state
|
2758
2729
|
return
|
2759
2730
|
|
2760
2731
|
@pyqtSlot(bool)
|
2761
2732
|
def _doToggleProject(self, state: bool) -> None:
|
2762
2733
|
"""Enable/disable continuing search in next project file."""
|
2763
|
-
|
2734
|
+
CONFIG.searchNextFile = state
|
2764
2735
|
return
|
2765
2736
|
|
2766
2737
|
@pyqtSlot(bool)
|
2767
2738
|
def _doToggleMatchCap(self, state: bool) -> None:
|
2768
2739
|
"""Enable/disable preserving capitalisation when replacing."""
|
2769
|
-
|
2740
|
+
CONFIG.searchMatchCap = state
|
2770
2741
|
return
|
2771
2742
|
|
2772
2743
|
##
|
@@ -2801,84 +2772,82 @@ class GuiDocEditHeader(QWidget):
|
|
2801
2772
|
logger.debug("Create: GuiDocEditHeader")
|
2802
2773
|
|
2803
2774
|
self.docEditor = docEditor
|
2804
|
-
self.mainGui = docEditor.mainGui
|
2805
2775
|
|
2806
2776
|
self._docHandle = None
|
2777
|
+
self._docOutline: dict[int, str] = {}
|
2807
2778
|
|
2808
|
-
|
2809
|
-
|
2810
|
-
|
2779
|
+
iPx = SHARED.theme.baseIconHeight
|
2780
|
+
iSz = SHARED.theme.baseIconSize
|
2781
|
+
mPx = CONFIG.pxInt(4)
|
2811
2782
|
|
2812
2783
|
# Main Widget Settings
|
2813
2784
|
self.setAutoFillBackground(True)
|
2814
2785
|
|
2815
2786
|
# Title Label
|
2816
|
-
self.itemTitle = QLabel()
|
2817
|
-
self.itemTitle.setText("")
|
2787
|
+
self.itemTitle = QLabel("", self)
|
2818
2788
|
self.itemTitle.setIndent(0)
|
2819
2789
|
self.itemTitle.setMargin(0)
|
2820
2790
|
self.itemTitle.setContentsMargins(0, 0, 0, 0)
|
2821
2791
|
self.itemTitle.setAutoFillBackground(True)
|
2822
|
-
self.itemTitle.setAlignment(
|
2823
|
-
self.itemTitle.setFixedHeight(
|
2792
|
+
self.itemTitle.setAlignment(QtAlignCenterTop)
|
2793
|
+
self.itemTitle.setFixedHeight(iPx)
|
2824
2794
|
|
2825
2795
|
lblFont = self.itemTitle.font()
|
2826
2796
|
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
2827
2797
|
self.itemTitle.setFont(lblFont)
|
2828
2798
|
|
2799
|
+
# Other Widgets
|
2800
|
+
self.outlineMenu = QMenu(self)
|
2801
|
+
|
2829
2802
|
# Buttons
|
2830
|
-
self.tbButton =
|
2831
|
-
self.tbButton.setContentsMargins(0, 0, 0, 0)
|
2832
|
-
self.tbButton.setIconSize(iconSize)
|
2833
|
-
self.tbButton.setFixedSize(fPx, fPx)
|
2834
|
-
self.tbButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
2803
|
+
self.tbButton = NIconToolButton(self, iSz)
|
2835
2804
|
self.tbButton.setVisible(False)
|
2836
2805
|
self.tbButton.setToolTip(self.tr("Toggle Tool Bar"))
|
2837
2806
|
self.tbButton.clicked.connect(lambda: self.toggleToolBarRequest.emit())
|
2838
2807
|
|
2839
|
-
self.
|
2840
|
-
self.
|
2841
|
-
self.
|
2842
|
-
self.
|
2843
|
-
|
2808
|
+
self.outlineButton = NIconToolButton(self, iSz)
|
2809
|
+
self.outlineButton.setVisible(False)
|
2810
|
+
self.outlineButton.setToolTip(self.tr("Outline"))
|
2811
|
+
self.outlineButton.setMenu(self.outlineMenu)
|
2812
|
+
|
2813
|
+
self.searchButton = NIconToolButton(self, iSz)
|
2844
2814
|
self.searchButton.setVisible(False)
|
2845
2815
|
self.searchButton.setToolTip(self.tr("Search"))
|
2846
2816
|
self.searchButton.clicked.connect(self.docEditor.toggleSearch)
|
2847
2817
|
|
2848
|
-
self.minmaxButton =
|
2849
|
-
self.minmaxButton.setContentsMargins(0, 0, 0, 0)
|
2850
|
-
self.minmaxButton.setIconSize(iconSize)
|
2851
|
-
self.minmaxButton.setFixedSize(fPx, fPx)
|
2852
|
-
self.minmaxButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
2818
|
+
self.minmaxButton = NIconToolButton(self, iSz)
|
2853
2819
|
self.minmaxButton.setVisible(False)
|
2854
2820
|
self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode"))
|
2855
2821
|
self.minmaxButton.clicked.connect(lambda: self.docEditor.toggleFocusModeRequest.emit())
|
2856
2822
|
|
2857
|
-
self.closeButton =
|
2858
|
-
self.closeButton.setContentsMargins(0, 0, 0, 0)
|
2859
|
-
self.closeButton.setIconSize(iconSize)
|
2860
|
-
self.closeButton.setFixedSize(fPx, fPx)
|
2861
|
-
self.closeButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
2823
|
+
self.closeButton = NIconToolButton(self, iSz)
|
2862
2824
|
self.closeButton.setVisible(False)
|
2863
2825
|
self.closeButton.setToolTip(self.tr("Close"))
|
2864
2826
|
self.closeButton.clicked.connect(self._closeDocument)
|
2865
2827
|
|
2866
2828
|
# Assemble Layout
|
2867
2829
|
self.outerBox = QHBoxLayout()
|
2868
|
-
self.outerBox.setSpacing(hSp)
|
2869
2830
|
self.outerBox.addWidget(self.tbButton, 0)
|
2831
|
+
self.outerBox.addWidget(self.outlineButton, 0)
|
2870
2832
|
self.outerBox.addWidget(self.searchButton, 0)
|
2833
|
+
self.outerBox.addSpacing(mPx)
|
2871
2834
|
self.outerBox.addWidget(self.itemTitle, 1)
|
2835
|
+
self.outerBox.addSpacing(mPx)
|
2836
|
+
self.outerBox.addSpacing(iPx)
|
2872
2837
|
self.outerBox.addWidget(self.minmaxButton, 0)
|
2873
2838
|
self.outerBox.addWidget(self.closeButton, 0)
|
2839
|
+
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
|
2840
|
+
self.outerBox.setSpacing(0)
|
2841
|
+
|
2874
2842
|
self.setLayout(self.outerBox)
|
2875
2843
|
|
2844
|
+
# Other Signals
|
2845
|
+
SHARED.focusModeChanged.connect(self._focusModeChanged)
|
2846
|
+
|
2876
2847
|
# Fix Margins and Size
|
2877
2848
|
# This is needed for high DPI systems. See issue #499.
|
2878
|
-
cM = CONFIG.pxInt(8)
|
2879
2849
|
self.setContentsMargins(0, 0, 0, 0)
|
2880
|
-
self.
|
2881
|
-
self.setMinimumHeight(fPx + 2*cM)
|
2850
|
+
self.setMinimumHeight(iPx + 2*mPx)
|
2882
2851
|
|
2883
2852
|
self.updateTheme()
|
2884
2853
|
|
@@ -2890,20 +2859,45 @@ class GuiDocEditHeader(QWidget):
|
|
2890
2859
|
# Methods
|
2891
2860
|
##
|
2892
2861
|
|
2862
|
+
def clearHeader(self) -> None:
|
2863
|
+
"""Clear the header."""
|
2864
|
+
self._docHandle = None
|
2865
|
+
self._docOutline = {}
|
2866
|
+
|
2867
|
+
self.itemTitle.setText("")
|
2868
|
+
self.outlineMenu.clear()
|
2869
|
+
self.tbButton.setVisible(False)
|
2870
|
+
self.outlineButton.setVisible(False)
|
2871
|
+
self.searchButton.setVisible(False)
|
2872
|
+
self.closeButton.setVisible(False)
|
2873
|
+
self.minmaxButton.setVisible(False)
|
2874
|
+
return
|
2875
|
+
|
2876
|
+
def setOutline(self, data: dict[int, str]) -> None:
|
2877
|
+
"""Set the document outline dataset."""
|
2878
|
+
if data != self._docOutline:
|
2879
|
+
tStart = time()
|
2880
|
+
self.outlineMenu.clear()
|
2881
|
+
for number, text in data.items():
|
2882
|
+
action = self.outlineMenu.addAction(text)
|
2883
|
+
action.triggered.connect(
|
2884
|
+
lambda _, number=number: self._gotoBlock(number)
|
2885
|
+
)
|
2886
|
+
self._docOutline = data
|
2887
|
+
logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart))
|
2888
|
+
return
|
2889
|
+
|
2893
2890
|
def updateTheme(self) -> None:
|
2894
2891
|
"""Update theme elements."""
|
2895
|
-
self.tbButton.
|
2896
|
-
self.
|
2897
|
-
self.
|
2898
|
-
self.
|
2899
|
-
|
2900
|
-
colText = SHARED.theme.colText
|
2901
|
-
buttonStyle = (
|
2902
|
-
"QToolButton {{border: none; background: transparent;}} "
|
2903
|
-
"QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
|
2904
|
-
).format(colText.red(), colText.green(), colText.blue())
|
2892
|
+
self.tbButton.setThemeIcon("menu")
|
2893
|
+
self.outlineButton.setThemeIcon("list")
|
2894
|
+
self.searchButton.setThemeIcon("search")
|
2895
|
+
self.minmaxButton.setThemeIcon("maximise")
|
2896
|
+
self.closeButton.setThemeIcon("close")
|
2905
2897
|
|
2898
|
+
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
2906
2899
|
self.tbButton.setStyleSheet(buttonStyle)
|
2900
|
+
self.outlineButton.setStyleSheet(buttonStyle)
|
2907
2901
|
self.searchButton.setStyleSheet(buttonStyle)
|
2908
2902
|
self.minmaxButton.setStyleSheet(buttonStyle)
|
2909
2903
|
self.closeButton.setStyleSheet(buttonStyle)
|
@@ -2926,51 +2920,25 @@ class GuiDocEditHeader(QWidget):
|
|
2926
2920
|
|
2927
2921
|
return
|
2928
2922
|
|
2929
|
-
def
|
2923
|
+
def setHandle(self, tHandle: str) -> None:
|
2930
2924
|
"""Set the document title from the handle, or alternatively, set
|
2931
2925
|
the whole document path within the project.
|
2932
2926
|
"""
|
2933
2927
|
self._docHandle = tHandle
|
2934
|
-
if tHandle is None:
|
2935
|
-
self.itemTitle.setText("")
|
2936
|
-
self.tbButton.setVisible(False)
|
2937
|
-
self.searchButton.setVisible(False)
|
2938
|
-
self.closeButton.setVisible(False)
|
2939
|
-
self.minmaxButton.setVisible(False)
|
2940
|
-
return True
|
2941
2928
|
|
2942
|
-
pTree = SHARED.project.tree
|
2943
2929
|
if CONFIG.showFullPath:
|
2944
|
-
|
2945
|
-
|
2946
|
-
|
2947
|
-
nwItem = pTree[aHandle]
|
2948
|
-
if nwItem is not None:
|
2949
|
-
tTitle.append(nwItem.itemName)
|
2950
|
-
sSep = " %s " % nwUnicode.U_RSAQUO
|
2951
|
-
self.itemTitle.setText(sSep.join(tTitle))
|
2930
|
+
self.itemTitle.setText(f" {nwUnicode.U_RSAQUO} ".join(reversed(
|
2931
|
+
[name for name in SHARED.project.tree.getItemPath(tHandle, asName=True)]
|
2932
|
+
)))
|
2952
2933
|
else:
|
2953
|
-
|
2954
|
-
if nwItem is None:
|
2955
|
-
return False
|
2956
|
-
self.itemTitle.setText(nwItem.itemName)
|
2934
|
+
self.itemTitle.setText(i.itemName if (i := SHARED.project.tree[tHandle]) else "")
|
2957
2935
|
|
2958
2936
|
self.tbButton.setVisible(True)
|
2959
2937
|
self.searchButton.setVisible(True)
|
2938
|
+
self.outlineButton.setVisible(True)
|
2960
2939
|
self.closeButton.setVisible(True)
|
2961
2940
|
self.minmaxButton.setVisible(True)
|
2962
2941
|
|
2963
|
-
return True
|
2964
|
-
|
2965
|
-
def updateFocusMode(self) -> None:
|
2966
|
-
"""Update the minimise/maximise icon of the Focus Mode button.
|
2967
|
-
This function is called by the GuiMain class via the
|
2968
|
-
toggleFocusMode function and should not be activated directly.
|
2969
|
-
"""
|
2970
|
-
if self.mainGui.isFocusMode:
|
2971
|
-
self.minmaxButton.setIcon(SHARED.theme.getIcon("minimise"))
|
2972
|
-
else:
|
2973
|
-
self.minmaxButton.setIcon(SHARED.theme.getIcon("maximise"))
|
2974
2942
|
return
|
2975
2943
|
|
2976
2944
|
##
|
@@ -2980,23 +2948,32 @@ class GuiDocEditHeader(QWidget):
|
|
2980
2948
|
@pyqtSlot()
|
2981
2949
|
def _closeDocument(self) -> None:
|
2982
2950
|
"""Trigger the close editor on the main window."""
|
2951
|
+
self.clearHeader()
|
2983
2952
|
self.closeDocumentRequest.emit()
|
2984
|
-
|
2985
|
-
|
2986
|
-
|
2987
|
-
|
2953
|
+
return
|
2954
|
+
|
2955
|
+
@pyqtSlot(int)
|
2956
|
+
def _gotoBlock(self, blockNumber: int) -> None:
|
2957
|
+
"""Move cursor to a specific heading."""
|
2958
|
+
self.docEditor.setCursorLine(blockNumber + 1)
|
2959
|
+
return
|
2960
|
+
|
2961
|
+
@pyqtSlot(bool)
|
2962
|
+
def _focusModeChanged(self, focusMode: bool) -> None:
|
2963
|
+
"""Update minimise/maximise icon of the Focus Mode button."""
|
2964
|
+
self.minmaxButton.setThemeIcon("minimise" if focusMode else "maximise")
|
2988
2965
|
return
|
2989
2966
|
|
2990
2967
|
##
|
2991
2968
|
# Events
|
2992
2969
|
##
|
2993
2970
|
|
2994
|
-
def mousePressEvent(self, event: QMouseEvent):
|
2971
|
+
def mousePressEvent(self, event: QMouseEvent) -> None:
|
2995
2972
|
"""Capture a click on the title and ensure that the item is
|
2996
2973
|
selected in the project tree.
|
2997
2974
|
"""
|
2998
|
-
if event.button() ==
|
2999
|
-
self.docEditor.requestProjectItemSelected.emit(self._docHandle, True)
|
2975
|
+
if event.button() == QtMouseLeft:
|
2976
|
+
self.docEditor.requestProjectItemSelected.emit(self._docHandle or "", True)
|
3000
2977
|
return
|
3001
2978
|
|
3002
2979
|
# END Class GuiDocEditHeader
|
@@ -3009,75 +2986,75 @@ class GuiDocEditHeader(QWidget):
|
|
3009
2986
|
|
3010
2987
|
class GuiDocEditFooter(QWidget):
|
3011
2988
|
|
3012
|
-
def __init__(self,
|
3013
|
-
super().__init__(parent=
|
2989
|
+
def __init__(self, parent: QWidget) -> None:
|
2990
|
+
super().__init__(parent=parent)
|
3014
2991
|
|
3015
2992
|
logger.debug("Create: GuiDocEditFooter")
|
3016
2993
|
|
3017
|
-
self.
|
3018
|
-
|
3019
|
-
self._tItem = None
|
2994
|
+
self._tItem = None
|
3020
2995
|
self._docHandle = None
|
3021
2996
|
|
3022
|
-
|
3023
|
-
|
3024
|
-
self.sPx = int(round(0.9*SHARED.theme.baseIconSize))
|
2997
|
+
iPx = round(0.9*SHARED.theme.baseIconHeight)
|
3025
2998
|
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
2999
|
+
mPx = CONFIG.pxInt(8)
|
3026
3000
|
bSp = CONFIG.pxInt(4)
|
3027
3001
|
hSp = CONFIG.pxInt(6)
|
3028
3002
|
|
3029
3003
|
lblFont = self.font()
|
3030
3004
|
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
3031
3005
|
|
3006
|
+
# Cached Translations
|
3007
|
+
self._trLineCount = self.tr("Line: {0} ({1})")
|
3008
|
+
self._trWordCount = self.tr("Words: {0} ({1})")
|
3009
|
+
self._trSelectCount = self.tr("Words: {0} selected")
|
3010
|
+
|
3032
3011
|
# Main Widget Settings
|
3033
3012
|
self.setContentsMargins(0, 0, 0, 0)
|
3034
3013
|
self.setAutoFillBackground(True)
|
3035
3014
|
|
3036
|
-
alLeftTop = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
|
3037
|
-
|
3038
3015
|
# Status
|
3039
|
-
self.statusIcon = QLabel("")
|
3016
|
+
self.statusIcon = QLabel("", self)
|
3040
3017
|
self.statusIcon.setContentsMargins(0, 0, 0, 0)
|
3041
|
-
self.statusIcon.setFixedHeight(
|
3042
|
-
self.statusIcon.setAlignment(
|
3018
|
+
self.statusIcon.setFixedHeight(iPx)
|
3019
|
+
self.statusIcon.setAlignment(QtAlignLeftTop)
|
3043
3020
|
|
3044
|
-
self.statusText = QLabel(self.tr("Status"))
|
3021
|
+
self.statusText = QLabel(self.tr("Status"), self)
|
3045
3022
|
self.statusText.setIndent(0)
|
3046
3023
|
self.statusText.setMargin(0)
|
3047
3024
|
self.statusText.setContentsMargins(0, 0, 0, 0)
|
3048
3025
|
self.statusText.setAutoFillBackground(True)
|
3049
3026
|
self.statusText.setFixedHeight(fPx)
|
3050
|
-
self.statusText.setAlignment(
|
3027
|
+
self.statusText.setAlignment(QtAlignLeftTop)
|
3051
3028
|
self.statusText.setFont(lblFont)
|
3052
3029
|
|
3053
3030
|
# Lines
|
3054
|
-
self.linesIcon = QLabel("")
|
3031
|
+
self.linesIcon = QLabel("", self)
|
3055
3032
|
self.linesIcon.setContentsMargins(0, 0, 0, 0)
|
3056
|
-
self.linesIcon.setFixedHeight(
|
3057
|
-
self.linesIcon.setAlignment(
|
3033
|
+
self.linesIcon.setFixedHeight(iPx)
|
3034
|
+
self.linesIcon.setAlignment(QtAlignLeftTop)
|
3058
3035
|
|
3059
|
-
self.linesText = QLabel("")
|
3036
|
+
self.linesText = QLabel("", self)
|
3060
3037
|
self.linesText.setIndent(0)
|
3061
3038
|
self.linesText.setMargin(0)
|
3062
3039
|
self.linesText.setContentsMargins(0, 0, 0, 0)
|
3063
3040
|
self.linesText.setAutoFillBackground(True)
|
3064
3041
|
self.linesText.setFixedHeight(fPx)
|
3065
|
-
self.linesText.setAlignment(
|
3042
|
+
self.linesText.setAlignment(QtAlignLeftTop)
|
3066
3043
|
self.linesText.setFont(lblFont)
|
3067
3044
|
|
3068
3045
|
# Words
|
3069
|
-
self.wordsIcon = QLabel("")
|
3046
|
+
self.wordsIcon = QLabel("", self)
|
3070
3047
|
self.wordsIcon.setContentsMargins(0, 0, 0, 0)
|
3071
|
-
self.wordsIcon.setFixedHeight(
|
3072
|
-
self.wordsIcon.setAlignment(
|
3048
|
+
self.wordsIcon.setFixedHeight(iPx)
|
3049
|
+
self.wordsIcon.setAlignment(QtAlignLeftTop)
|
3073
3050
|
|
3074
|
-
self.wordsText = QLabel("")
|
3051
|
+
self.wordsText = QLabel("", self)
|
3075
3052
|
self.wordsText.setIndent(0)
|
3076
3053
|
self.wordsText.setMargin(0)
|
3077
3054
|
self.wordsText.setContentsMargins(0, 0, 0, 0)
|
3078
3055
|
self.wordsText.setAutoFillBackground(True)
|
3079
3056
|
self.wordsText.setFixedHeight(fPx)
|
3080
|
-
self.wordsText.setAlignment(
|
3057
|
+
self.wordsText.setAlignment(QtAlignLeftTop)
|
3081
3058
|
self.wordsText.setFont(lblFont)
|
3082
3059
|
|
3083
3060
|
# Assemble Layout
|
@@ -3091,19 +3068,20 @@ class GuiDocEditFooter(QWidget):
|
|
3091
3068
|
self.outerBox.addSpacing(hSp)
|
3092
3069
|
self.outerBox.addWidget(self.wordsIcon)
|
3093
3070
|
self.outerBox.addWidget(self.wordsText)
|
3071
|
+
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
|
3072
|
+
|
3094
3073
|
self.setLayout(self.outerBox)
|
3095
3074
|
|
3096
3075
|
# Fix Margins and Size
|
3097
3076
|
# This is needed for high DPI systems. See issue #499.
|
3098
|
-
cM = CONFIG.pxInt(8)
|
3099
3077
|
self.setContentsMargins(0, 0, 0, 0)
|
3100
|
-
self.
|
3101
|
-
self.setMinimumHeight(fPx + 2*cM)
|
3078
|
+
self.setMinimumHeight(fPx + 2*mPx)
|
3102
3079
|
|
3103
3080
|
# Fix the Colours
|
3104
3081
|
self.updateTheme()
|
3105
|
-
|
3106
|
-
|
3082
|
+
|
3083
|
+
# Initialise Info
|
3084
|
+
self.updateWordCount(0, False)
|
3107
3085
|
|
3108
3086
|
logger.debug("Ready: GuiDocEditFooter")
|
3109
3087
|
|
@@ -3115,8 +3093,9 @@ class GuiDocEditFooter(QWidget):
|
|
3115
3093
|
|
3116
3094
|
def updateTheme(self) -> None:
|
3117
3095
|
"""Update theme elements."""
|
3118
|
-
|
3119
|
-
self.
|
3096
|
+
iPx = round(0.9*SHARED.theme.baseIconHeight)
|
3097
|
+
self.linesIcon.setPixmap(SHARED.theme.getPixmap("status_lines", (iPx, iPx)))
|
3098
|
+
self.wordsIcon.setPixmap(SHARED.theme.getPixmap("status_stats", (iPx, iPx)))
|
3120
3099
|
self.matchColours()
|
3121
3100
|
return
|
3122
3101
|
|
@@ -3145,27 +3124,20 @@ class GuiDocEditFooter(QWidget):
|
|
3145
3124
|
else:
|
3146
3125
|
self._tItem = SHARED.project.tree[self._docHandle]
|
3147
3126
|
|
3148
|
-
self.setHasSelection(False)
|
3149
3127
|
self.updateInfo()
|
3150
|
-
self.
|
3128
|
+
self.updateWordCount(0, False)
|
3151
3129
|
|
3152
3130
|
return
|
3153
3131
|
|
3154
|
-
def setHasSelection(self, hasSelection: bool) -> None:
|
3155
|
-
"""Toggle the word counter mode between full count and selection
|
3156
|
-
count mode.
|
3157
|
-
"""
|
3158
|
-
self._docSelection = hasSelection
|
3159
|
-
return
|
3160
|
-
|
3161
3132
|
def updateInfo(self) -> None:
|
3162
3133
|
"""Update the content of text labels."""
|
3163
3134
|
if self._tItem is None:
|
3164
3135
|
sIcon = QPixmap()
|
3165
3136
|
sText = ""
|
3166
3137
|
else:
|
3138
|
+
iPx = round(0.9*SHARED.theme.baseIconHeight)
|
3167
3139
|
status, icon = self._tItem.getImportStatus(incIcon=True)
|
3168
|
-
sIcon = icon.pixmap(
|
3140
|
+
sIcon = icon.pixmap(iPx, iPx)
|
3169
3141
|
sText = f"{status} / {self._tItem.describeMe()}"
|
3170
3142
|
|
3171
3143
|
self.statusIcon.setPixmap(sIcon)
|
@@ -3173,62 +3145,27 @@ class GuiDocEditFooter(QWidget):
|
|
3173
3145
|
|
3174
3146
|
return
|
3175
3147
|
|
3176
|
-
def updateLineCount(self) -> None:
|
3177
|
-
"""Update the line counter."""
|
3178
|
-
|
3179
|
-
|
3180
|
-
|
3181
|
-
else:
|
3182
|
-
cursor = self.docEditor.textCursor()
|
3183
|
-
iLine = cursor.blockNumber() + 1
|
3184
|
-
iDist = 100*iLine/self.docEditor._qDocument.blockCount()
|
3148
|
+
def updateLineCount(self, cursor: QTextCursor) -> None:
|
3149
|
+
"""Update the line and document position counter."""
|
3150
|
+
cPos = cursor.position() + 1
|
3151
|
+
cLine = cursor.blockNumber() + 1
|
3152
|
+
cCount = max(cursor.document().characterCount(), 1)
|
3185
3153
|
self.linesText.setText(
|
3186
|
-
self.
|
3154
|
+
self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %")
|
3187
3155
|
)
|
3188
3156
|
return
|
3189
3157
|
|
3190
|
-
def
|
3191
|
-
"""
|
3192
|
-
if
|
3193
|
-
self.
|
3194
|
-
|
3195
|
-
self._updateWordCounts()
|
3196
|
-
return
|
3197
|
-
|
3198
|
-
##
|
3199
|
-
# Internal Functions
|
3200
|
-
##
|
3201
|
-
|
3202
|
-
def _updateWordCounts(self) -> None:
|
3203
|
-
"""Update the word count for the whole document."""
|
3204
|
-
if self._tItem is None:
|
3205
|
-
wCount = 0
|
3206
|
-
wDiff = 0
|
3207
|
-
else:
|
3158
|
+
def updateWordCount(self, wCount: int, selection: bool) -> None:
|
3159
|
+
"""Update word counter information."""
|
3160
|
+
if selection and wCount:
|
3161
|
+
wText = self._trSelectCount.format(f"{wCount:n}")
|
3162
|
+
elif self._tItem:
|
3208
3163
|
wCount = self._tItem.wordCount
|
3209
3164
|
wDiff = wCount - self._tItem.initCount
|
3210
|
-
|
3211
|
-
|
3212
|
-
|
3213
|
-
)
|
3214
|
-
|
3215
|
-
byteSize = self.docEditor._qDocument.characterCount()
|
3216
|
-
self.wordsText.setToolTip(
|
3217
|
-
self.tr("Document size is {0} bytes").format(f"{byteSize:n}")
|
3218
|
-
)
|
3219
|
-
|
3220
|
-
return
|
3221
|
-
|
3222
|
-
def _updateSelectionWordCounts(self, wCount: int | None, cCount: int | None) -> None:
|
3223
|
-
"""Update the word count for a selection."""
|
3224
|
-
if wCount is None or cCount is None:
|
3225
|
-
return
|
3226
|
-
self.wordsText.setText(
|
3227
|
-
self.tr("Words: {0} selected").format(f"{wCount:n}")
|
3228
|
-
)
|
3229
|
-
self.wordsText.setToolTip(
|
3230
|
-
self.tr("Character count: {0}").format(f"{cCount:n}")
|
3231
|
-
)
|
3165
|
+
wText = self._trWordCount.format(f"{wCount:n}", f"{wDiff:+n}")
|
3166
|
+
else:
|
3167
|
+
wText = self._trWordCount.format("0", "+0")
|
3168
|
+
self.wordsText.setText(wText)
|
3232
3169
|
return
|
3233
3170
|
|
3234
3171
|
# END Class GuiDocEditFooter
|