novelWriter 2.3.1__py3-none-any.whl → 2.4rc1__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.3.1.dist-info → novelWriter-2.4rc1.dist-info}/METADATA +5 -6
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/RECORD +102 -95
- novelwriter/__init__.py +7 -7
- 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/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/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/common.py +7 -2
- novelwriter/config.py +43 -16
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +60 -40
- 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 +10 -15
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/status.py +6 -5
- novelwriter/core/storage.py +8 -2
- novelwriter/core/tohtml.py +22 -25
- novelwriter/core/tokenizer.py +416 -232
- novelwriter/core/tomd.py +17 -8
- novelwriter/core/toodt.py +385 -350
- novelwriter/core/tree.py +8 -8
- novelwriter/dialogs/about.py +9 -11
- novelwriter/dialogs/docmerge.py +17 -14
- novelwriter/dialogs/docsplit.py +20 -19
- novelwriter/dialogs/editlabel.py +5 -4
- novelwriter/dialogs/preferences.py +31 -39
- novelwriter/dialogs/projectsettings.py +29 -26
- novelwriter/dialogs/quotes.py +10 -9
- novelwriter/dialogs/wordlist.py +15 -12
- novelwriter/enum.py +17 -14
- novelwriter/error.py +13 -11
- novelwriter/extensions/circularprogress.py +12 -8
- novelwriter/extensions/configlayout.py +1 -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 +413 -478
- novelwriter/gui/dochighlight.py +33 -29
- 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 -16
- novelwriter/gui/noveltree.py +30 -36
- novelwriter/gui/outline.py +114 -92
- novelwriter/gui/projtree.py +60 -66
- novelwriter/gui/search.py +362 -0
- novelwriter/gui/sidebar.py +36 -45
- novelwriter/gui/statusbar.py +14 -14
- novelwriter/gui/theme.py +93 -28
- novelwriter/guimain.py +207 -200
- novelwriter/shared.py +31 -6
- novelwriter/text/counting.py +137 -0
- novelwriter/tools/dictionaries.py +13 -12
- novelwriter/tools/lipsum.py +20 -17
- novelwriter/tools/manusbuild.py +35 -27
- novelwriter/tools/manuscript.py +374 -90
- novelwriter/tools/manussettings.py +261 -124
- novelwriter/tools/noveldetails.py +20 -18
- novelwriter/tools/welcome.py +48 -44
- novelwriter/tools/writingstats.py +61 -55
- novelwriter/types.py +90 -0
- 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.3.1.dist-info → novelWriter-2.4rc1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/WHEEL +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.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,7 +375,7 @@ 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
|
|
@@ -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()
|
@@ -530,7 +542,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
530
542
|
while self.cursorRect().bottom() > vH and count < 100000:
|
531
543
|
vBar.setValue(vBar.value() + 1)
|
532
544
|
count += 1
|
533
|
-
|
545
|
+
QApplication.processEvents()
|
534
546
|
return
|
535
547
|
|
536
548
|
def updateDocMargins(self) -> None:
|
@@ -548,8 +560,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
548
560
|
sH = hBar.height() if hBar.isVisible() else 0
|
549
561
|
|
550
562
|
tM = self._vpMargin
|
551
|
-
if CONFIG.textWidth > 0 or
|
552
|
-
tW = CONFIG.getTextWidth(
|
563
|
+
if CONFIG.textWidth > 0 or SHARED.focusMode:
|
564
|
+
tW = CONFIG.getTextWidth(SHARED.focusMode)
|
553
565
|
tM = max((wW - sW - tW)//2, self._vpMargin)
|
554
566
|
|
555
567
|
tB = self.frameWidth()
|
@@ -590,6 +602,15 @@ class GuiDocEditor(QPlainTextEdit):
|
|
590
602
|
text = text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
|
591
603
|
return text
|
592
604
|
|
605
|
+
def getSelectedText(self) -> str:
|
606
|
+
"""Get currently selected text."""
|
607
|
+
if (cursor := self.textCursor()).hasSelection():
|
608
|
+
text = cursor.selectedText()
|
609
|
+
text = text.replace(nwUnicode.U_LSEP, "\n") # Line separators
|
610
|
+
text = text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
|
611
|
+
return text
|
612
|
+
return ""
|
613
|
+
|
593
614
|
def getCursorPosition(self) -> int:
|
594
615
|
"""Find the cursor position in the document. If the editor has a
|
595
616
|
selection, return the position of the end of the selection.
|
@@ -600,13 +621,15 @@ class GuiDocEditor(QPlainTextEdit):
|
|
600
621
|
# Setters
|
601
622
|
##
|
602
623
|
|
603
|
-
def setDocumentChanged(self, state: bool) ->
|
624
|
+
def setDocumentChanged(self, state: bool) -> None:
|
604
625
|
"""Keep track of the document changed variable, and emit the
|
605
626
|
document change signal.
|
606
627
|
"""
|
607
|
-
self._docChanged
|
608
|
-
|
609
|
-
|
628
|
+
if self._docChanged != state:
|
629
|
+
logger.debug("Document changed status is '%s'", state)
|
630
|
+
self._docChanged = state
|
631
|
+
self.editedStatusChanged.emit(self._docChanged)
|
632
|
+
return
|
610
633
|
|
611
634
|
def setCursorPosition(self, position: int) -> None:
|
612
635
|
"""Move the cursor to a given position in the document."""
|
@@ -616,7 +639,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
616
639
|
cursor.setPosition(minmax(position, 0, nChars-1))
|
617
640
|
self.setTextCursor(cursor)
|
618
641
|
self.centerCursor()
|
619
|
-
self.docFooter.updateLineCount()
|
620
642
|
return
|
621
643
|
|
622
644
|
def saveCursorPosition(self) -> None:
|
@@ -635,6 +657,15 @@ class GuiDocEditor(QPlainTextEdit):
|
|
635
657
|
logger.debug("Cursor moved to line %d", line)
|
636
658
|
return
|
637
659
|
|
660
|
+
def setCursorSelection(self, start: int, length: int) -> None:
|
661
|
+
"""Make a text selection."""
|
662
|
+
if start >= 0 and length > 0:
|
663
|
+
cursor = self.textCursor()
|
664
|
+
cursor.setPosition(start, QtMoveAnchor)
|
665
|
+
cursor.setPosition(start + length, QtKeepAnchor)
|
666
|
+
self.setTextCursor(cursor)
|
667
|
+
return
|
668
|
+
|
638
669
|
##
|
639
670
|
# Spell Checking
|
640
671
|
##
|
@@ -674,9 +705,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
674
705
|
"""
|
675
706
|
logger.debug("Running spell checker")
|
676
707
|
start = time()
|
677
|
-
|
708
|
+
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
678
709
|
self._qDocument.syntaxHighlighter.rehighlight()
|
679
|
-
|
710
|
+
QApplication.restoreOverrideCursor()
|
680
711
|
logger.debug("Document highlighted in %.3f ms", 1000*(time() - start))
|
681
712
|
self.statusMessage.emit(self.tr("Spell check complete"))
|
682
713
|
return
|
@@ -745,6 +776,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
745
776
|
self._formatBlock(nwDocAction.BLOCK_TTL)
|
746
777
|
elif action == nwDocAction.BLOCK_UNN:
|
747
778
|
self._formatBlock(nwDocAction.BLOCK_UNN)
|
779
|
+
elif action == nwDocAction.BLOCK_HSC:
|
780
|
+
self._formatBlock(nwDocAction.BLOCK_HSC)
|
748
781
|
elif action == nwDocAction.REPL_SNG:
|
749
782
|
self._replaceQuotes("'", self._typSQuoteO, self._typSQuoteC)
|
750
783
|
elif action == nwDocAction.REPL_DBL:
|
@@ -769,6 +802,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
769
802
|
self._wrapSelection(nwShortcode.STRIKE_O, nwShortcode.STRIKE_C)
|
770
803
|
elif action == nwDocAction.SC_ULINE:
|
771
804
|
self._wrapSelection(nwShortcode.ULINE_O, nwShortcode.ULINE_C)
|
805
|
+
elif action == nwDocAction.SC_MARK:
|
806
|
+
self._wrapSelection(nwShortcode.MARK_O, nwShortcode.MARK_C)
|
772
807
|
elif action == nwDocAction.SC_SUP:
|
773
808
|
self._wrapSelection(nwShortcode.SUP_O, nwShortcode.SUP_C)
|
774
809
|
elif action == nwDocAction.SC_SUB:
|
@@ -785,11 +820,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
785
820
|
|
786
821
|
def anyFocus(self) -> bool:
|
787
822
|
"""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
|
823
|
+
return self.hasFocus() or self.isAncestorOf(QApplication.focusWidget())
|
793
824
|
|
794
825
|
def revealLocation(self) -> None:
|
795
826
|
"""Tell the user where on the file system the file in the editor
|
@@ -898,11 +929,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
898
929
|
|
899
930
|
return True
|
900
931
|
|
901
|
-
def closeSearch(self) -> bool:
|
902
|
-
"""Close the search box."""
|
903
|
-
self.docSearch.closeSearch()
|
904
|
-
return self.docSearch.isVisible()
|
905
|
-
|
906
932
|
##
|
907
933
|
# Document Events and Maintenance
|
908
934
|
##
|
@@ -937,7 +963,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
937
963
|
super().keyPressEvent(event)
|
938
964
|
nPos = self.cursorRect().topLeft().y()
|
939
965
|
kMod = event.modifiers()
|
940
|
-
okMod = kMod in (
|
966
|
+
okMod = kMod in (QtModeNone, QtModShift)
|
941
967
|
okKey = event.key() not in self.MOVE_KEYS
|
942
968
|
if nPos != cPos and okMod and okKey:
|
943
969
|
mPos = CONFIG.autoScrollPos*0.01 * self.viewport().height()
|
@@ -947,8 +973,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
947
973
|
else:
|
948
974
|
super().keyPressEvent(event)
|
949
975
|
|
950
|
-
self.docFooter.updateLineCount()
|
951
|
-
|
952
976
|
return
|
953
977
|
|
954
978
|
def focusNextPrevChild(self, next: bool) -> bool:
|
@@ -960,7 +984,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
960
984
|
if self.hasFocus():
|
961
985
|
return False
|
962
986
|
elif self.docSearch.isVisible():
|
963
|
-
return self.docSearch.cycleFocus(
|
987
|
+
return self.docSearch.cycleFocus()
|
964
988
|
return True
|
965
989
|
|
966
990
|
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
@@ -968,10 +992,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
968
992
|
pressed, check if we're clicking on a tag, and trigger the
|
969
993
|
follow tag function.
|
970
994
|
"""
|
971
|
-
if
|
995
|
+
if QApplication.keyboardModifiers() == QtModCtrl:
|
972
996
|
self._processTag(self.cursorForPosition(event.pos()))
|
973
997
|
super().mouseReleaseEvent(event)
|
974
|
-
self.docFooter.updateLineCount()
|
975
998
|
return
|
976
999
|
|
977
1000
|
def resizeEvent(self, event: QResizeEvent) -> None:
|
@@ -991,8 +1014,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
991
1014
|
"""Called when an item label is changed to check if the document
|
992
1015
|
title bar needs updating,
|
993
1016
|
"""
|
994
|
-
if tHandle == self._docHandle:
|
995
|
-
self.docHeader.
|
1017
|
+
if tHandle and tHandle == self._docHandle:
|
1018
|
+
self.docHeader.setHandle(tHandle)
|
996
1019
|
self.docFooter.updateInfo()
|
997
1020
|
self.updateDocMargins()
|
998
1021
|
return
|
@@ -1012,8 +1035,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1012
1035
|
@pyqtSlot()
|
1013
1036
|
def toggleSearch(self) -> None:
|
1014
1037
|
"""Toggle the visibility of the search box."""
|
1015
|
-
if self.
|
1016
|
-
self.
|
1038
|
+
if self.searchVisible():
|
1039
|
+
self.closeSearch()
|
1017
1040
|
else:
|
1018
1041
|
self.beginSearch()
|
1019
1042
|
return
|
@@ -1033,32 +1056,35 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1033
1056
|
if not self._docChanged:
|
1034
1057
|
self.setDocumentChanged(removed != 0 or added != 0)
|
1035
1058
|
|
1036
|
-
if not self.
|
1037
|
-
self.
|
1038
|
-
|
1039
|
-
block = self._qDocument.findBlock(pos)
|
1040
|
-
if not block.isValid():
|
1041
|
-
return
|
1059
|
+
if not self.timerDoc.isActive():
|
1060
|
+
self.timerDoc.start()
|
1042
1061
|
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1062
|
+
if (block := self._qDocument.findBlock(pos)).isValid():
|
1063
|
+
text = block.text()
|
1064
|
+
if text.startswith("@") and added + removed == 1:
|
1065
|
+
# Only run on single keypresses, otherwise it will trigger
|
1066
|
+
# at unwanted times when other changes are made to the document
|
1067
|
+
cursor = self.textCursor()
|
1068
|
+
bPos = cursor.positionInBlock()
|
1069
|
+
if bPos > 0:
|
1070
|
+
show = self._completer.updateText(text, bPos)
|
1071
|
+
point = self.cursorRect().bottomRight()
|
1072
|
+
self._completer.move(self.viewport().mapToGlobal(point))
|
1073
|
+
self._completer.setVisible(show)
|
1074
|
+
else:
|
1075
|
+
self._completer.setVisible(False)
|
1056
1076
|
|
1057
1077
|
if self._doReplace and added == 1:
|
1058
1078
|
self._docAutoReplace(text)
|
1059
1079
|
|
1060
1080
|
return
|
1061
1081
|
|
1082
|
+
@pyqtSlot()
|
1083
|
+
def _cursorMoved(self):
|
1084
|
+
"""Triggered when the cursor moved in the editor."""
|
1085
|
+
self.docFooter.updateLineCount(self.textCursor())
|
1086
|
+
return
|
1087
|
+
|
1062
1088
|
@pyqtSlot(int, int, str)
|
1063
1089
|
def _insertCompletion(self, pos: int, length: int, text: str) -> None:
|
1064
1090
|
"""Insert choice from the completer menu."""
|
@@ -1066,8 +1092,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1066
1092
|
block = cursor.block()
|
1067
1093
|
if block.isValid():
|
1068
1094
|
pos += block.position()
|
1069
|
-
cursor.setPosition(pos,
|
1070
|
-
cursor.setPosition(pos + length,
|
1095
|
+
cursor.setPosition(pos, QtMoveAnchor)
|
1096
|
+
cursor.setPosition(pos + length, QtKeepAnchor)
|
1071
1097
|
cursor.insertText(text)
|
1072
1098
|
self._completer.hide()
|
1073
1099
|
return
|
@@ -1081,7 +1107,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1081
1107
|
|
1082
1108
|
ctxMenu = QMenu(self)
|
1083
1109
|
ctxMenu.setObjectName("ContextMenu")
|
1084
|
-
if pBlock.userState() ==
|
1110
|
+
if pBlock.userState() == BLOCK_TITLE:
|
1085
1111
|
action = ctxMenu.addAction(self.tr("Set as Document Name"))
|
1086
1112
|
action.triggered.connect(lambda: self._emitRenameItem(pBlock))
|
1087
1113
|
|
@@ -1127,9 +1153,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1127
1153
|
block = pCursor.block()
|
1128
1154
|
sCursor = self.textCursor()
|
1129
1155
|
sCursor.setPosition(block.position() + cPos)
|
1130
|
-
sCursor.movePosition(
|
1131
|
-
QTextCursor.MoveOperation.Right, QTextCursor.MoveMode.KeepAnchor, cLen
|
1132
|
-
)
|
1156
|
+
sCursor.movePosition(QtMoveRight, QtKeepAnchor, cLen)
|
1133
1157
|
if suggest:
|
1134
1158
|
ctxMenu.addSeparator()
|
1135
1159
|
ctxMenu.addAction(self.tr("Spelling Suggestion(s)"))
|
@@ -1146,7 +1170,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1146
1170
|
action.triggered.connect(lambda: self._addWord(word, block))
|
1147
1171
|
|
1148
1172
|
# Execute the context menu
|
1149
|
-
ctxMenu.
|
1173
|
+
ctxMenu.exec(self.viewport().mapToGlobal(pos))
|
1150
1174
|
ctxMenu.deleteLater()
|
1151
1175
|
|
1152
1176
|
return
|
@@ -1176,39 +1200,36 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1176
1200
|
return
|
1177
1201
|
|
1178
1202
|
@pyqtSlot()
|
1179
|
-
def
|
1180
|
-
"""
|
1181
|
-
inactivity.
|
1182
|
-
"""
|
1203
|
+
def _runDocumentTasks(self) -> None:
|
1204
|
+
"""Run timer document tasks."""
|
1183
1205
|
if self._docHandle is None:
|
1184
1206
|
return
|
1185
1207
|
|
1186
|
-
if self.wCounterDoc.isRunning():
|
1187
|
-
logger.debug("Word counter is busy")
|
1188
|
-
return
|
1189
|
-
|
1190
1208
|
if time() - self._lastEdit < 25.0:
|
1191
|
-
logger.debug("Running
|
1192
|
-
|
1209
|
+
logger.debug("Running document tasks")
|
1210
|
+
if not self.wCounterDoc.isRunning():
|
1211
|
+
SHARED.runInThreadPool(self.wCounterDoc)
|
1212
|
+
|
1213
|
+
self.docHeader.setOutline({
|
1214
|
+
block.blockNumber(): block.text()
|
1215
|
+
for block in self._qDocument.iterBlockByType(BLOCK_TITLE, maxCount=30)
|
1216
|
+
})
|
1217
|
+
|
1218
|
+
if self._docChanged:
|
1219
|
+
self.docTextChanged.emit(self._docHandle, self._lastEdit)
|
1193
1220
|
|
1194
1221
|
return
|
1195
1222
|
|
1196
1223
|
@pyqtSlot(int, int, int)
|
1197
1224
|
def _updateDocCounts(self, cCount: int, wCount: int, pCount: int) -> None:
|
1198
1225
|
"""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
|
-
|
1226
|
+
if self._docHandle and self._nwItem:
|
1227
|
+
logger.debug("Updating word count")
|
1228
|
+
self._nwItem.setCharCount(cCount)
|
1229
|
+
self._nwItem.setWordCount(wCount)
|
1230
|
+
self._nwItem.setParaCount(pCount)
|
1231
|
+
self.docCountsChanged.emit(self._docHandle, cCount, wCount, pCount)
|
1232
|
+
self.docFooter.updateWordCount(wCount, False)
|
1212
1233
|
return
|
1213
1234
|
|
1214
1235
|
@pyqtSlot()
|
@@ -1217,13 +1238,11 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1217
1238
|
information to the footer, and start the selection word counter.
|
1218
1239
|
"""
|
1219
1240
|
if self.textCursor().hasSelection():
|
1220
|
-
if not self.
|
1221
|
-
self.
|
1222
|
-
self.docFooter.setHasSelection(True)
|
1241
|
+
if not self.timerSel.isActive():
|
1242
|
+
self.timerSel.start()
|
1223
1243
|
else:
|
1224
|
-
self.
|
1225
|
-
self.docFooter.
|
1226
|
-
self.docFooter.updateCounts()
|
1244
|
+
self.timerSel.stop()
|
1245
|
+
self.docFooter.updateWordCount(0, False)
|
1227
1246
|
return
|
1228
1247
|
|
1229
1248
|
@pyqtSlot()
|
@@ -1243,13 +1262,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1243
1262
|
@pyqtSlot(int, int, int)
|
1244
1263
|
def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
|
1245
1264
|
"""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
|
-
|
1265
|
+
if self._docHandle and self._nwItem:
|
1266
|
+
logger.debug("User selected %d words", wCount)
|
1267
|
+
self.docFooter.updateWordCount(wCount, True)
|
1268
|
+
self.timerSel.stop()
|
1253
1269
|
return
|
1254
1270
|
|
1255
1271
|
@pyqtSlot()
|
@@ -1273,11 +1289,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1273
1289
|
|
1274
1290
|
def beginSearch(self) -> None:
|
1275
1291
|
"""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)
|
1292
|
+
self.docSearch.setSearchText(self.getSelectedText() or None)
|
1281
1293
|
resS, _ = self.findAllOccurences()
|
1282
1294
|
self.docSearch.setResultCount(None, len(resS))
|
1283
1295
|
return
|
@@ -1306,17 +1318,18 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1306
1318
|
if len(resS) == 0 and self._docHandle:
|
1307
1319
|
self.docSearch.setResultCount(0, 0)
|
1308
1320
|
self._lastFind = None
|
1309
|
-
if
|
1321
|
+
if CONFIG.searchNextFile and not goBack:
|
1310
1322
|
self.mainGui.openNextDocument(
|
1311
|
-
self._docHandle, wrapAround=
|
1323
|
+
self._docHandle, wrapAround=CONFIG.searchLoop
|
1312
1324
|
)
|
1313
1325
|
self.beginSearch()
|
1326
|
+
self.setFocus()
|
1314
1327
|
return
|
1315
1328
|
|
1316
1329
|
cursor = self.textCursor()
|
1317
1330
|
resIdx = bisect.bisect_left(resS, cursor.position())
|
1318
1331
|
|
1319
|
-
doLoop =
|
1332
|
+
doLoop = CONFIG.searchLoop
|
1320
1333
|
maxIdx = len(resS) - 1
|
1321
1334
|
|
1322
1335
|
if goBack:
|
@@ -1326,20 +1339,20 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1326
1339
|
resIdx = maxIdx if doLoop else 0
|
1327
1340
|
|
1328
1341
|
if resIdx > maxIdx and self._docHandle:
|
1329
|
-
if
|
1342
|
+
if CONFIG.searchNextFile and not goBack:
|
1330
1343
|
self.mainGui.openNextDocument(
|
1331
|
-
self._docHandle, wrapAround=
|
1344
|
+
self._docHandle, wrapAround=CONFIG.searchLoop
|
1332
1345
|
)
|
1333
1346
|
self.beginSearch()
|
1347
|
+
self.setFocus()
|
1334
1348
|
return
|
1335
1349
|
else:
|
1336
1350
|
resIdx = 0 if doLoop else maxIdx
|
1337
1351
|
|
1338
|
-
cursor.setPosition(resS[resIdx],
|
1339
|
-
cursor.setPosition(resE[resIdx],
|
1352
|
+
cursor.setPosition(resS[resIdx], QtMoveAnchor)
|
1353
|
+
cursor.setPosition(resE[resIdx], QtKeepAnchor)
|
1340
1354
|
self.setTextCursor(cursor)
|
1341
1355
|
|
1342
|
-
self.docFooter.updateLineCount()
|
1343
1356
|
self.docSearch.setResultCount(resIdx + 1, len(resS))
|
1344
1357
|
self._lastFind = (resS[resIdx], resE[resIdx])
|
1345
1358
|
|
@@ -1361,18 +1374,19 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1361
1374
|
origB = cursor.position()
|
1362
1375
|
|
1363
1376
|
findOpt = QTextDocument.FindFlag(0)
|
1364
|
-
if
|
1377
|
+
if CONFIG.searchCase:
|
1365
1378
|
findOpt |= QTextDocument.FindFlag.FindCaseSensitively
|
1366
|
-
if
|
1379
|
+
if CONFIG.searchWord:
|
1367
1380
|
findOpt |= QTextDocument.FindFlag.FindWholeWords
|
1368
1381
|
|
1369
1382
|
searchFor = self.docSearch.getSearchObject()
|
1370
1383
|
cursor.setPosition(0)
|
1371
1384
|
self.setTextCursor(cursor)
|
1372
1385
|
|
1373
|
-
# Search up to a maximum of
|
1374
|
-
# searches like a regex search for .* don't loop
|
1375
|
-
|
1386
|
+
# Search up to a maximum of MAX_SEARCH_RESULT, and make sure
|
1387
|
+
# certain special searches like a regex search for .* don't loop
|
1388
|
+
# infinitely
|
1389
|
+
while self.find(searchFor, findOpt) and len(resE) <= nwConst.MAX_SEARCH_RESULT:
|
1376
1390
|
cursor = self.textCursor()
|
1377
1391
|
if cursor.hasSelection():
|
1378
1392
|
resS.append(cursor.selectionStart())
|
@@ -1382,8 +1396,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1382
1396
|
break
|
1383
1397
|
|
1384
1398
|
if hasSelection:
|
1385
|
-
cursor.setPosition(origA,
|
1386
|
-
cursor.setPosition(origB,
|
1399
|
+
cursor.setPosition(origA, QtMoveAnchor)
|
1400
|
+
cursor.setPosition(origB, QtKeepAnchor)
|
1387
1401
|
else:
|
1388
1402
|
cursor.setPosition(origA)
|
1389
1403
|
|
@@ -1431,18 +1445,12 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1431
1445
|
searchFor = self.docSearch.searchText
|
1432
1446
|
replWith = self.docSearch.replaceText
|
1433
1447
|
|
1434
|
-
if
|
1448
|
+
if CONFIG.searchMatchCap:
|
1435
1449
|
replWith = transferCase(cursor.selectedText(), replWith)
|
1436
1450
|
|
1437
1451
|
# Make sure the selected text was selected by an actual find
|
1438
1452
|
# 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:
|
1453
|
+
if self._lastFind == (cursor.selectionStart(), cursor.selectionEnd()):
|
1446
1454
|
cursor.beginEditBlock()
|
1447
1455
|
cursor.removeSelectedText()
|
1448
1456
|
cursor.insertText(replWith)
|
@@ -1451,10 +1459,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1451
1459
|
self.setTextCursor(cursor)
|
1452
1460
|
logger.debug(
|
1453
1461
|
"Replaced occurrence of '%s' with '%s' on line %d",
|
1454
|
-
searchFor, replWith, cursor.blockNumber()
|
1462
|
+
searchFor, replWith, cursor.blockNumber() + 1
|
1455
1463
|
)
|
1456
|
-
else:
|
1457
|
-
logger.error("The selected text is not a search result, skipping replace")
|
1458
1464
|
|
1459
1465
|
self.findNext()
|
1460
1466
|
|
@@ -1494,8 +1500,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1494
1500
|
if blockS != blockE:
|
1495
1501
|
posE = blockS.position() + blockS.length() - 1
|
1496
1502
|
cursor.clearSelection()
|
1497
|
-
cursor.setPosition(posS,
|
1498
|
-
cursor.setPosition(posE,
|
1503
|
+
cursor.setPosition(posS, QtMoveAnchor)
|
1504
|
+
cursor.setPosition(posE, QtKeepAnchor)
|
1499
1505
|
self.setTextCursor(cursor)
|
1500
1506
|
|
1501
1507
|
numB = 0
|
@@ -1572,8 +1578,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1572
1578
|
if select == _SelectAction.MOVE_AFTER:
|
1573
1579
|
cursor.setPosition(posE + len(before + after))
|
1574
1580
|
elif select == _SelectAction.KEEP_SELECTION:
|
1575
|
-
cursor.setPosition(posE + len(before),
|
1576
|
-
cursor.setPosition(posS + len(before),
|
1581
|
+
cursor.setPosition(posE + len(before), QtMoveAnchor)
|
1582
|
+
cursor.setPosition(posS + len(before), QtKeepAnchor)
|
1577
1583
|
elif select == _SelectAction.KEEP_POSITION:
|
1578
1584
|
cursor.setPosition(posO + len(before))
|
1579
1585
|
|
@@ -1595,9 +1601,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1595
1601
|
self._allowAutoReplace(False)
|
1596
1602
|
for posC in range(posS, posE+1):
|
1597
1603
|
cursor.setPosition(posC)
|
1598
|
-
cursor.movePosition(
|
1599
|
-
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.KeepAnchor, 2
|
1600
|
-
)
|
1604
|
+
cursor.movePosition(QtMoveLeft, QtKeepAnchor, 2)
|
1601
1605
|
selText = cursor.selectedText()
|
1602
1606
|
|
1603
1607
|
nS = len(selText)
|
@@ -1617,16 +1621,12 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1617
1621
|
cursor.setPosition(posC)
|
1618
1622
|
if pC in closeCheck:
|
1619
1623
|
cursor.beginEditBlock()
|
1620
|
-
cursor.movePosition(
|
1621
|
-
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.KeepAnchor, 1
|
1622
|
-
)
|
1624
|
+
cursor.movePosition(QtMoveLeft, QtKeepAnchor, 1)
|
1623
1625
|
cursor.insertText(oQuote)
|
1624
1626
|
cursor.endEditBlock()
|
1625
1627
|
else:
|
1626
1628
|
cursor.beginEditBlock()
|
1627
|
-
cursor.movePosition(
|
1628
|
-
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.KeepAnchor, 1
|
1629
|
-
)
|
1629
|
+
cursor.movePosition(QtMoveLeft, QtKeepAnchor, 1)
|
1630
1630
|
cursor.insertText(cQuote)
|
1631
1631
|
cursor.endEditBlock()
|
1632
1632
|
|
@@ -1670,6 +1670,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1670
1670
|
elif text.startswith("##! "):
|
1671
1671
|
temp = text[4:]
|
1672
1672
|
offset = 4
|
1673
|
+
elif text.startswith("###! "):
|
1674
|
+
temp = text[5:]
|
1675
|
+
offset = 5
|
1673
1676
|
elif text.startswith(">> "):
|
1674
1677
|
temp = text[3:]
|
1675
1678
|
offset = 3
|
@@ -1721,6 +1724,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1721
1724
|
elif action == nwDocAction.BLOCK_UNN:
|
1722
1725
|
text = f"##! {temp}"
|
1723
1726
|
offset -= 4
|
1727
|
+
elif action == nwDocAction.BLOCK_HSC:
|
1728
|
+
text = f"###! {temp}"
|
1729
|
+
offset -= 5
|
1724
1730
|
elif action == nwDocAction.ALIGN_L:
|
1725
1731
|
text = f"{temp} <<"
|
1726
1732
|
elif action == nwDocAction.ALIGN_C:
|
@@ -1836,9 +1842,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1836
1842
|
cursor.beginEditBlock()
|
1837
1843
|
cursor.clearSelection()
|
1838
1844
|
cursor.setPosition(rS)
|
1839
|
-
cursor.movePosition(
|
1840
|
-
QTextCursor.MoveOperation.Right, QTextCursor.MoveMode.KeepAnchor, rE-rS
|
1841
|
-
)
|
1845
|
+
cursor.movePosition(QtMoveRight, QtKeepAnchor, rE-rS)
|
1842
1846
|
cursor.insertText(cleanText.rstrip() + "\n")
|
1843
1847
|
cursor.endEditBlock()
|
1844
1848
|
|
@@ -1867,7 +1871,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1867
1871
|
if text.startswith("@") and self._docHandle:
|
1868
1872
|
|
1869
1873
|
isGood, tBits, tPos = SHARED.project.index.scanThis(text)
|
1870
|
-
if
|
1874
|
+
if (
|
1875
|
+
not isGood or not tBits or tBits[0] == nwKeyWords.TAG_KEY
|
1876
|
+
or tBits[0] not in nwKeyWords.VALID_KEYS
|
1877
|
+
):
|
1871
1878
|
return nwTrinary.NEUTRAL
|
1872
1879
|
|
1873
1880
|
tag = ""
|
@@ -1895,13 +1902,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1895
1902
|
"Do you want to create a new project note for the tag '{0}'?"
|
1896
1903
|
).format(tag)):
|
1897
1904
|
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])))
|
1905
|
+
self.requestNewNoteCreation.emit(tag, itemClass)
|
1906
|
+
QApplication.processEvents()
|
1907
|
+
self._qDocument.syntaxHighlighter.rehighlightBlock(block)
|
1905
1908
|
|
1906
1909
|
return nwTrinary.POSITIVE if exist else nwTrinary.NEGATIVE
|
1907
1910
|
|
@@ -2005,9 +2008,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2005
2008
|
tInsert = tInsert + self._typPadChar
|
2006
2009
|
|
2007
2010
|
if nDelete > 0:
|
2008
|
-
cursor.movePosition(
|
2009
|
-
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.KeepAnchor, nDelete
|
2010
|
-
)
|
2011
|
+
cursor.movePosition(QtMoveLeft, QtKeepAnchor, nDelete)
|
2011
2012
|
cursor.insertText(tInsert)
|
2012
2013
|
|
2013
2014
|
return
|
@@ -2063,8 +2064,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2063
2064
|
return cursor
|
2064
2065
|
|
2065
2066
|
cursor.clearSelection()
|
2066
|
-
cursor.setPosition(sPos,
|
2067
|
-
cursor.setPosition(ePos,
|
2067
|
+
cursor.setPosition(sPos, QtMoveAnchor)
|
2068
|
+
cursor.setPosition(ePos, QtKeepAnchor)
|
2068
2069
|
|
2069
2070
|
self.setTextCursor(cursor)
|
2070
2071
|
|
@@ -2088,8 +2089,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2088
2089
|
posE = cursor.selectionEnd()
|
2089
2090
|
selTxt = cursor.selectedText()
|
2090
2091
|
if selTxt.startswith(nwUnicode.U_PSEP):
|
2091
|
-
cursor.setPosition(posS+1,
|
2092
|
-
cursor.setPosition(posE,
|
2092
|
+
cursor.setPosition(posS+1, QtMoveAnchor)
|
2093
|
+
cursor.setPosition(posE, QtKeepAnchor)
|
2093
2094
|
|
2094
2095
|
self.setTextCursor(cursor)
|
2095
2096
|
|
@@ -2220,11 +2221,11 @@ class BackgroundWordCounter(QRunnable):
|
|
2220
2221
|
"""
|
2221
2222
|
self._isRunning = True
|
2222
2223
|
if self._forSelection:
|
2223
|
-
text = self._docEditor.
|
2224
|
+
text = self._docEditor.getSelectedText()
|
2224
2225
|
else:
|
2225
2226
|
text = self._docEditor.getText()
|
2226
2227
|
|
2227
|
-
cC, wC, pC =
|
2228
|
+
cC, wC, pC = standardCounter(text)
|
2228
2229
|
self.signals.countsReady.emit(cC, wC, pC)
|
2229
2230
|
self._isRunning = False
|
2230
2231
|
|
@@ -2256,72 +2257,68 @@ class GuiDocToolBar(QWidget):
|
|
2256
2257
|
|
2257
2258
|
logger.debug("Create: GuiDocToolBar")
|
2258
2259
|
|
2260
|
+
iSz = SHARED.theme.baseIconSize
|
2259
2261
|
cM = CONFIG.pxInt(4)
|
2260
|
-
tPx = int(0.8*SHARED.theme.fontPixelSize)
|
2261
|
-
iconSize = QSize(tPx, tPx)
|
2262
2262
|
self.setContentsMargins(0, 0, 0, 0)
|
2263
2263
|
|
2264
2264
|
# General Buttons
|
2265
2265
|
# ===============
|
2266
2266
|
|
2267
|
-
self.tbBoldMD =
|
2268
|
-
self.tbBoldMD.setIconSize(iconSize)
|
2267
|
+
self.tbBoldMD = NIconToolButton(self, iSz)
|
2269
2268
|
self.tbBoldMD.setToolTip(self.tr("Markdown Bold"))
|
2270
2269
|
self.tbBoldMD.clicked.connect(
|
2271
2270
|
lambda: self.requestDocAction.emit(nwDocAction.MD_BOLD)
|
2272
2271
|
)
|
2273
2272
|
|
2274
|
-
self.tbItalicMD =
|
2275
|
-
self.tbItalicMD.setIconSize(iconSize)
|
2273
|
+
self.tbItalicMD = NIconToolButton(self, iSz)
|
2276
2274
|
self.tbItalicMD.setToolTip(self.tr("Markdown Italic"))
|
2277
2275
|
self.tbItalicMD.clicked.connect(
|
2278
2276
|
lambda: self.requestDocAction.emit(nwDocAction.MD_ITALIC)
|
2279
2277
|
)
|
2280
2278
|
|
2281
|
-
self.tbStrikeMD =
|
2282
|
-
self.tbStrikeMD.setIconSize(iconSize)
|
2279
|
+
self.tbStrikeMD = NIconToolButton(self, iSz)
|
2283
2280
|
self.tbStrikeMD.setToolTip(self.tr("Markdown Strikethrough"))
|
2284
2281
|
self.tbStrikeMD.clicked.connect(
|
2285
2282
|
lambda: self.requestDocAction.emit(nwDocAction.MD_STRIKE)
|
2286
2283
|
)
|
2287
2284
|
|
2288
|
-
self.tbBold =
|
2289
|
-
self.tbBold.setIconSize(iconSize)
|
2285
|
+
self.tbBold = NIconToolButton(self, iSz)
|
2290
2286
|
self.tbBold.setToolTip(self.tr("Shortcode Bold"))
|
2291
2287
|
self.tbBold.clicked.connect(
|
2292
2288
|
lambda: self.requestDocAction.emit(nwDocAction.SC_BOLD)
|
2293
2289
|
)
|
2294
2290
|
|
2295
|
-
self.tbItalic =
|
2296
|
-
self.tbItalic.setIconSize(iconSize)
|
2291
|
+
self.tbItalic = NIconToolButton(self, iSz)
|
2297
2292
|
self.tbItalic.setToolTip(self.tr("Shortcode Italic"))
|
2298
2293
|
self.tbItalic.clicked.connect(
|
2299
2294
|
lambda: self.requestDocAction.emit(nwDocAction.SC_ITALIC)
|
2300
2295
|
)
|
2301
2296
|
|
2302
|
-
self.tbStrike =
|
2303
|
-
self.tbStrike.setIconSize(iconSize)
|
2297
|
+
self.tbStrike = NIconToolButton(self, iSz)
|
2304
2298
|
self.tbStrike.setToolTip(self.tr("Shortcode Strikethrough"))
|
2305
2299
|
self.tbStrike.clicked.connect(
|
2306
2300
|
lambda: self.requestDocAction.emit(nwDocAction.SC_STRIKE)
|
2307
2301
|
)
|
2308
2302
|
|
2309
|
-
self.tbUnderline =
|
2310
|
-
self.tbUnderline.setIconSize(iconSize)
|
2303
|
+
self.tbUnderline = NIconToolButton(self, iSz)
|
2311
2304
|
self.tbUnderline.setToolTip(self.tr("Shortcode Underline"))
|
2312
2305
|
self.tbUnderline.clicked.connect(
|
2313
2306
|
lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE)
|
2314
2307
|
)
|
2315
2308
|
|
2316
|
-
self.
|
2317
|
-
self.
|
2309
|
+
self.tbMark = NIconToolButton(self, iSz)
|
2310
|
+
self.tbMark.setToolTip(self.tr("Shortcode Highlight"))
|
2311
|
+
self.tbMark.clicked.connect(
|
2312
|
+
lambda: self.requestDocAction.emit(nwDocAction.SC_MARK)
|
2313
|
+
)
|
2314
|
+
|
2315
|
+
self.tbSuperscript = NIconToolButton(self, iSz)
|
2318
2316
|
self.tbSuperscript.setToolTip(self.tr("Shortcode Superscript"))
|
2319
2317
|
self.tbSuperscript.clicked.connect(
|
2320
2318
|
lambda: self.requestDocAction.emit(nwDocAction.SC_SUP)
|
2321
2319
|
)
|
2322
2320
|
|
2323
|
-
self.tbSubscript =
|
2324
|
-
self.tbSubscript.setIconSize(iconSize)
|
2321
|
+
self.tbSubscript = NIconToolButton(self, iSz)
|
2325
2322
|
self.tbSubscript.setToolTip(self.tr("Shortcode Subscript"))
|
2326
2323
|
self.tbSubscript.clicked.connect(
|
2327
2324
|
lambda: self.requestDocAction.emit(nwDocAction.SC_SUB)
|
@@ -2339,6 +2336,7 @@ class GuiDocToolBar(QWidget):
|
|
2339
2336
|
self.outerBox.addWidget(self.tbItalic)
|
2340
2337
|
self.outerBox.addWidget(self.tbStrike)
|
2341
2338
|
self.outerBox.addWidget(self.tbUnderline)
|
2339
|
+
self.outerBox.addWidget(self.tbMark)
|
2342
2340
|
self.outerBox.addWidget(self.tbSuperscript)
|
2343
2341
|
self.outerBox.addWidget(self.tbSubscript)
|
2344
2342
|
self.outerBox.setContentsMargins(cM, cM, cM, cM)
|
@@ -2362,15 +2360,16 @@ class GuiDocToolBar(QWidget):
|
|
2362
2360
|
palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
|
2363
2361
|
self.setPalette(palette)
|
2364
2362
|
|
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.
|
2363
|
+
self.tbBoldMD.setThemeIcon("fmt_bold-md")
|
2364
|
+
self.tbItalicMD.setThemeIcon("fmt_italic-md")
|
2365
|
+
self.tbStrikeMD.setThemeIcon("fmt_strike-md")
|
2366
|
+
self.tbBold.setThemeIcon("fmt_bold")
|
2367
|
+
self.tbItalic.setThemeIcon("fmt_italic")
|
2368
|
+
self.tbStrike.setThemeIcon("fmt_strike")
|
2369
|
+
self.tbUnderline.setThemeIcon("fmt_underline")
|
2370
|
+
self.tbMark.setThemeIcon("fmt_mark")
|
2371
|
+
self.tbSuperscript.setThemeIcon("fmt_superscript")
|
2372
|
+
self.tbSubscript.setThemeIcon("fmt_subscript")
|
2374
2373
|
|
2375
2374
|
return
|
2376
2375
|
|
@@ -2391,16 +2390,9 @@ class GuiDocEditSearch(QFrame):
|
|
2391
2390
|
|
2392
2391
|
self.docEditor = docEditor
|
2393
2392
|
|
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
|
-
|
2393
|
+
iSz = SHARED.theme.baseIconSize
|
2402
2394
|
mPx = CONFIG.pxInt(6)
|
2403
|
-
|
2395
|
+
|
2404
2396
|
self.boxFont = SHARED.theme.guiFont
|
2405
2397
|
self.boxFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
2406
2398
|
|
@@ -2426,44 +2418,44 @@ class GuiDocEditSearch(QFrame):
|
|
2426
2418
|
|
2427
2419
|
self.searchOpt = QToolBar(self)
|
2428
2420
|
self.searchOpt.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
2429
|
-
self.searchOpt.setIconSize(
|
2421
|
+
self.searchOpt.setIconSize(iSz)
|
2430
2422
|
self.searchOpt.setContentsMargins(0, 0, 0, 0)
|
2431
2423
|
|
2432
|
-
self.searchLabel = QLabel(self.tr("Search"))
|
2424
|
+
self.searchLabel = QLabel(self.tr("Search"), self)
|
2433
2425
|
self.searchLabel.setFont(self.boxFont)
|
2434
2426
|
self.searchLabel.setIndent(CONFIG.pxInt(6))
|
2435
2427
|
|
2436
|
-
self.resultLabel = QLabel("?/?")
|
2428
|
+
self.resultLabel = QLabel("?/?", self)
|
2437
2429
|
self.resultLabel.setFont(self.boxFont)
|
2438
2430
|
self.resultLabel.setMinimumWidth(SHARED.theme.getTextWidth("?/?", self.boxFont))
|
2439
2431
|
|
2440
2432
|
self.toggleCase = QAction(self.tr("Case Sensitive"), self)
|
2441
2433
|
self.toggleCase.setCheckable(True)
|
2442
|
-
self.toggleCase.setChecked(
|
2434
|
+
self.toggleCase.setChecked(CONFIG.searchCase)
|
2443
2435
|
self.toggleCase.toggled.connect(self._doToggleCase)
|
2444
2436
|
self.searchOpt.addAction(self.toggleCase)
|
2445
2437
|
|
2446
2438
|
self.toggleWord = QAction(self.tr("Whole Words Only"), self)
|
2447
2439
|
self.toggleWord.setCheckable(True)
|
2448
|
-
self.toggleWord.setChecked(
|
2440
|
+
self.toggleWord.setChecked(CONFIG.searchWord)
|
2449
2441
|
self.toggleWord.toggled.connect(self._doToggleWord)
|
2450
2442
|
self.searchOpt.addAction(self.toggleWord)
|
2451
2443
|
|
2452
2444
|
self.toggleRegEx = QAction(self.tr("RegEx Mode"), self)
|
2453
2445
|
self.toggleRegEx.setCheckable(True)
|
2454
|
-
self.toggleRegEx.setChecked(
|
2446
|
+
self.toggleRegEx.setChecked(CONFIG.searchRegEx)
|
2455
2447
|
self.toggleRegEx.toggled.connect(self._doToggleRegEx)
|
2456
2448
|
self.searchOpt.addAction(self.toggleRegEx)
|
2457
2449
|
|
2458
2450
|
self.toggleLoop = QAction(self.tr("Loop Search"), self)
|
2459
2451
|
self.toggleLoop.setCheckable(True)
|
2460
|
-
self.toggleLoop.setChecked(
|
2452
|
+
self.toggleLoop.setChecked(CONFIG.searchLoop)
|
2461
2453
|
self.toggleLoop.toggled.connect(self._doToggleLoop)
|
2462
2454
|
self.searchOpt.addAction(self.toggleLoop)
|
2463
2455
|
|
2464
2456
|
self.toggleProject = QAction(self.tr("Search Next File"), self)
|
2465
2457
|
self.toggleProject.setCheckable(True)
|
2466
|
-
self.toggleProject.setChecked(
|
2458
|
+
self.toggleProject.setChecked(CONFIG.searchNextFile)
|
2467
2459
|
self.toggleProject.toggled.connect(self._doToggleProject)
|
2468
2460
|
self.searchOpt.addAction(self.toggleProject)
|
2469
2461
|
|
@@ -2471,38 +2463,32 @@ class GuiDocEditSearch(QFrame):
|
|
2471
2463
|
|
2472
2464
|
self.toggleMatchCap = QAction(self.tr("Preserve Case"), self)
|
2473
2465
|
self.toggleMatchCap.setCheckable(True)
|
2474
|
-
self.toggleMatchCap.setChecked(
|
2466
|
+
self.toggleMatchCap.setChecked(CONFIG.searchMatchCap)
|
2475
2467
|
self.toggleMatchCap.toggled.connect(self._doToggleMatchCap)
|
2476
2468
|
self.searchOpt.addAction(self.toggleMatchCap)
|
2477
2469
|
|
2478
2470
|
self.searchOpt.addSeparator()
|
2479
2471
|
|
2480
2472
|
self.cancelSearch = QAction(self.tr("Close Search"), self)
|
2481
|
-
self.cancelSearch.triggered.connect(self.
|
2473
|
+
self.cancelSearch.triggered.connect(self.closeSearch)
|
2482
2474
|
self.searchOpt.addAction(self.cancelSearch)
|
2483
2475
|
|
2484
2476
|
# Buttons
|
2485
2477
|
# =======
|
2486
2478
|
|
2487
|
-
|
2488
|
-
|
2489
|
-
self.showReplace = QToolButton(self)
|
2490
|
-
self.showReplace.setArrowType(Qt.ArrowType.RightArrow)
|
2491
|
-
self.showReplace.setCheckable(True)
|
2479
|
+
self.showReplace = NIconToggleButton(self, iSz, "unfold")
|
2492
2480
|
self.showReplace.toggled.connect(self._doToggleReplace)
|
2493
2481
|
|
2494
|
-
self.searchButton =
|
2495
|
-
self.searchButton.setFixedSize(QSize(bPx, bPx))
|
2482
|
+
self.searchButton = NIconToolButton(self, iSz)
|
2496
2483
|
self.searchButton.setToolTip(self.tr("Find in current document"))
|
2497
2484
|
self.searchButton.clicked.connect(self._doSearch)
|
2498
2485
|
|
2499
|
-
self.replaceButton =
|
2500
|
-
self.replaceButton.setFixedSize(QSize(bPx, bPx))
|
2486
|
+
self.replaceButton = NIconToolButton(self, iSz)
|
2501
2487
|
self.replaceButton.setToolTip(self.tr("Find and replace in current document"))
|
2502
2488
|
self.replaceButton.clicked.connect(self._doReplace)
|
2503
2489
|
|
2504
|
-
self.mainBox.addWidget(self.searchLabel, 0, 0, 1, 2,
|
2505
|
-
self.mainBox.addWidget(self.searchOpt, 0, 2, 1, 3,
|
2490
|
+
self.mainBox.addWidget(self.searchLabel, 0, 0, 1, 2, QtAlignLeft)
|
2491
|
+
self.mainBox.addWidget(self.searchOpt, 0, 2, 1, 3, QtAlignRight)
|
2506
2492
|
self.mainBox.addWidget(self.showReplace, 1, 0, 1, 1)
|
2507
2493
|
self.mainBox.addWidget(self.searchBox, 1, 1, 1, 2)
|
2508
2494
|
self.mainBox.addWidget(self.searchButton, 1, 3, 1, 1)
|
@@ -2555,20 +2541,20 @@ class GuiDocEditSearch(QFrame):
|
|
2555
2541
|
expression object.
|
2556
2542
|
"""
|
2557
2543
|
text = self.searchBox.text()
|
2558
|
-
if
|
2544
|
+
if CONFIG.searchRegEx:
|
2559
2545
|
# Using the Unicode-capable QRegularExpression class was
|
2560
2546
|
# only added in Qt 5.13. Otherwise, 5.3 and up supports
|
2561
2547
|
# only the QRegExp class.
|
2562
2548
|
if CONFIG.verQtValue >= 0x050d00:
|
2563
2549
|
rxOpt = QRegularExpression.PatternOption.UseUnicodePropertiesOption
|
2564
|
-
if not
|
2550
|
+
if not CONFIG.searchCase:
|
2565
2551
|
rxOpt |= QRegularExpression.PatternOption.CaseInsensitiveOption
|
2566
2552
|
regEx = QRegularExpression(text, rxOpt)
|
2567
2553
|
self._alertSearchValid(regEx.isValid())
|
2568
2554
|
return regEx
|
2569
2555
|
else: # pragma: no cover
|
2570
2556
|
# >= 50300 to < 51300
|
2571
|
-
if
|
2557
|
+
if CONFIG.searchCase:
|
2572
2558
|
rxOpt = Qt.CaseSensitivity.CaseSensitive
|
2573
2559
|
else:
|
2574
2560
|
rxOpt = Qt.CaseSensitivity.CaseInsensitive
|
@@ -2592,7 +2578,7 @@ class GuiDocEditSearch(QFrame):
|
|
2592
2578
|
self.searchBox.setText(text)
|
2593
2579
|
self.searchBox.setFocus()
|
2594
2580
|
self.searchBox.selectAll()
|
2595
|
-
if
|
2581
|
+
if CONFIG.searchRegEx:
|
2596
2582
|
self._alertSearchValid(True)
|
2597
2583
|
return
|
2598
2584
|
|
@@ -2605,8 +2591,10 @@ class GuiDocEditSearch(QFrame):
|
|
2605
2591
|
|
2606
2592
|
def setResultCount(self, currRes: int | None, resCount: int | None) -> None:
|
2607
2593
|
"""Set the count values for the current search."""
|
2594
|
+
lim = nwConst.MAX_SEARCH_RESULT
|
2595
|
+
numCount = f"{lim:n}+" if (resCount or 0) > lim else f"{resCount:n}"
|
2608
2596
|
sCurrRes = "?" if currRes is None else str(currRes)
|
2609
|
-
sResCount = "?" if resCount is None else
|
2597
|
+
sResCount = "?" if resCount is None else numCount
|
2610
2598
|
minWidth = SHARED.theme.getTextWidth(f"{sResCount}//{sResCount}", self.boxFont)
|
2611
2599
|
self.resultLabel.setText(f"{sCurrRes}/{sResCount}")
|
2612
2600
|
self.resultLabel.setMinimumWidth(minWidth)
|
@@ -2620,7 +2608,7 @@ class GuiDocEditSearch(QFrame):
|
|
2620
2608
|
|
2621
2609
|
def updateTheme(self) -> None:
|
2622
2610
|
"""Update theme elements."""
|
2623
|
-
qPalette =
|
2611
|
+
qPalette = QApplication.palette()
|
2624
2612
|
self.setPalette(qPalette)
|
2625
2613
|
self.searchBox.setPalette(qPalette)
|
2626
2614
|
self.replaceBox.setPalette(qPalette)
|
@@ -2633,8 +2621,8 @@ class GuiDocEditSearch(QFrame):
|
|
2633
2621
|
self.toggleProject.setIcon(SHARED.theme.getIcon("search_project"))
|
2634
2622
|
self.toggleMatchCap.setIcon(SHARED.theme.getIcon("search_preserve"))
|
2635
2623
|
self.cancelSearch.setIcon(SHARED.theme.getIcon("search_cancel"))
|
2636
|
-
self.searchButton.
|
2637
|
-
self.replaceButton.
|
2624
|
+
self.searchButton.setThemeIcon("search")
|
2625
|
+
self.replaceButton.setThemeIcon("search_replace")
|
2638
2626
|
|
2639
2627
|
# Set stylesheets
|
2640
2628
|
self.searchOpt.setStyleSheet("QToolBar {padding: 0;}")
|
@@ -2660,57 +2648,43 @@ class GuiDocEditSearch(QFrame):
|
|
2660
2648
|
|
2661
2649
|
return
|
2662
2650
|
|
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:
|
2651
|
+
def cycleFocus(self) -> bool:
|
2680
2652
|
"""The tab key just alternates focus between the two input
|
2681
2653
|
boxes, if the replace box is visible.
|
2682
2654
|
"""
|
2683
|
-
if self.
|
2684
|
-
|
2685
|
-
|
2686
|
-
|
2687
|
-
|
2688
|
-
|
2689
|
-
return True
|
2655
|
+
if self.searchBox.hasFocus():
|
2656
|
+
self.replaceBox.setFocus()
|
2657
|
+
return True
|
2658
|
+
elif self.replaceBox.hasFocus():
|
2659
|
+
self.searchBox.setFocus()
|
2660
|
+
return True
|
2690
2661
|
return False
|
2691
2662
|
|
2692
2663
|
def anyFocus(self) -> bool:
|
2693
2664
|
"""Return True if any of the input boxes have focus."""
|
2694
|
-
return self.searchBox.hasFocus()
|
2665
|
+
return self.searchBox.hasFocus() or self.replaceBox.hasFocus()
|
2695
2666
|
|
2696
2667
|
##
|
2697
|
-
#
|
2668
|
+
# Public Slots
|
2698
2669
|
##
|
2699
2670
|
|
2700
2671
|
@pyqtSlot()
|
2701
|
-
def
|
2702
|
-
"""
|
2703
|
-
self.
|
2672
|
+
def closeSearch(self) -> None:
|
2673
|
+
"""Close the search box."""
|
2674
|
+
self.showReplace.setChecked(False)
|
2675
|
+
self.setVisible(False)
|
2676
|
+
self.docEditor.updateDocMargins()
|
2677
|
+
self.docEditor.setFocus()
|
2704
2678
|
return
|
2705
2679
|
|
2680
|
+
##
|
2681
|
+
# Private Slots
|
2682
|
+
##
|
2683
|
+
|
2706
2684
|
@pyqtSlot()
|
2707
2685
|
def _doSearch(self) -> None:
|
2708
2686
|
"""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()
|
2687
|
+
self.docEditor.findNext(goBack=(QApplication.keyboardModifiers() == QtModShift))
|
2714
2688
|
return
|
2715
2689
|
|
2716
2690
|
@pyqtSlot()
|
@@ -2722,13 +2696,8 @@ class GuiDocEditSearch(QFrame):
|
|
2722
2696
|
@pyqtSlot(bool)
|
2723
2697
|
def _doToggleReplace(self, state: bool) -> None:
|
2724
2698
|
"""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
2699
|
self.replaceBox.setVisible(state)
|
2730
2700
|
self.replaceButton.setVisible(state)
|
2731
|
-
self.repVisible = state
|
2732
2701
|
self.adjustSize()
|
2733
2702
|
self.docEditor.updateDocMargins()
|
2734
2703
|
return
|
@@ -2736,37 +2705,37 @@ class GuiDocEditSearch(QFrame):
|
|
2736
2705
|
@pyqtSlot(bool)
|
2737
2706
|
def _doToggleCase(self, state: bool) -> None:
|
2738
2707
|
"""Enable/disable case sensitive mode."""
|
2739
|
-
|
2708
|
+
CONFIG.searchCase = state
|
2740
2709
|
return
|
2741
2710
|
|
2742
2711
|
@pyqtSlot(bool)
|
2743
2712
|
def _doToggleWord(self, state: bool) -> None:
|
2744
2713
|
"""Enable/disable whole word search mode."""
|
2745
|
-
|
2714
|
+
CONFIG.searchWord = state
|
2746
2715
|
return
|
2747
2716
|
|
2748
2717
|
@pyqtSlot(bool)
|
2749
2718
|
def _doToggleRegEx(self, state: bool) -> None:
|
2750
2719
|
"""Enable/disable regular expression search mode."""
|
2751
|
-
|
2720
|
+
CONFIG.searchRegEx = state
|
2752
2721
|
return
|
2753
2722
|
|
2754
2723
|
@pyqtSlot(bool)
|
2755
2724
|
def _doToggleLoop(self, state: bool) -> None:
|
2756
2725
|
"""Enable/disable looping the search."""
|
2757
|
-
|
2726
|
+
CONFIG.searchLoop = state
|
2758
2727
|
return
|
2759
2728
|
|
2760
2729
|
@pyqtSlot(bool)
|
2761
2730
|
def _doToggleProject(self, state: bool) -> None:
|
2762
2731
|
"""Enable/disable continuing search in next project file."""
|
2763
|
-
|
2732
|
+
CONFIG.searchNextFile = state
|
2764
2733
|
return
|
2765
2734
|
|
2766
2735
|
@pyqtSlot(bool)
|
2767
2736
|
def _doToggleMatchCap(self, state: bool) -> None:
|
2768
2737
|
"""Enable/disable preserving capitalisation when replacing."""
|
2769
|
-
|
2738
|
+
CONFIG.searchMatchCap = state
|
2770
2739
|
return
|
2771
2740
|
|
2772
2741
|
##
|
@@ -2801,84 +2770,82 @@ class GuiDocEditHeader(QWidget):
|
|
2801
2770
|
logger.debug("Create: GuiDocEditHeader")
|
2802
2771
|
|
2803
2772
|
self.docEditor = docEditor
|
2804
|
-
self.mainGui = docEditor.mainGui
|
2805
2773
|
|
2806
2774
|
self._docHandle = None
|
2775
|
+
self._docOutline: dict[int, str] = {}
|
2807
2776
|
|
2808
|
-
|
2809
|
-
|
2810
|
-
|
2777
|
+
iPx = SHARED.theme.baseIconHeight
|
2778
|
+
iSz = SHARED.theme.baseIconSize
|
2779
|
+
mPx = CONFIG.pxInt(4)
|
2811
2780
|
|
2812
2781
|
# Main Widget Settings
|
2813
2782
|
self.setAutoFillBackground(True)
|
2814
2783
|
|
2815
2784
|
# Title Label
|
2816
|
-
self.itemTitle = QLabel()
|
2817
|
-
self.itemTitle.setText("")
|
2785
|
+
self.itemTitle = QLabel("", self)
|
2818
2786
|
self.itemTitle.setIndent(0)
|
2819
2787
|
self.itemTitle.setMargin(0)
|
2820
2788
|
self.itemTitle.setContentsMargins(0, 0, 0, 0)
|
2821
2789
|
self.itemTitle.setAutoFillBackground(True)
|
2822
|
-
self.itemTitle.setAlignment(
|
2823
|
-
self.itemTitle.setFixedHeight(
|
2790
|
+
self.itemTitle.setAlignment(QtAlignCenterTop)
|
2791
|
+
self.itemTitle.setFixedHeight(iPx)
|
2824
2792
|
|
2825
2793
|
lblFont = self.itemTitle.font()
|
2826
2794
|
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
2827
2795
|
self.itemTitle.setFont(lblFont)
|
2828
2796
|
|
2797
|
+
# Other Widgets
|
2798
|
+
self.outlineMenu = QMenu(self)
|
2799
|
+
|
2829
2800
|
# 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)
|
2801
|
+
self.tbButton = NIconToolButton(self, iSz)
|
2835
2802
|
self.tbButton.setVisible(False)
|
2836
2803
|
self.tbButton.setToolTip(self.tr("Toggle Tool Bar"))
|
2837
2804
|
self.tbButton.clicked.connect(lambda: self.toggleToolBarRequest.emit())
|
2838
2805
|
|
2839
|
-
self.
|
2840
|
-
self.
|
2841
|
-
self.
|
2842
|
-
self.
|
2843
|
-
|
2806
|
+
self.outlineButton = NIconToolButton(self, iSz)
|
2807
|
+
self.outlineButton.setVisible(False)
|
2808
|
+
self.outlineButton.setToolTip(self.tr("Outline"))
|
2809
|
+
self.outlineButton.setMenu(self.outlineMenu)
|
2810
|
+
|
2811
|
+
self.searchButton = NIconToolButton(self, iSz)
|
2844
2812
|
self.searchButton.setVisible(False)
|
2845
2813
|
self.searchButton.setToolTip(self.tr("Search"))
|
2846
2814
|
self.searchButton.clicked.connect(self.docEditor.toggleSearch)
|
2847
2815
|
|
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)
|
2816
|
+
self.minmaxButton = NIconToolButton(self, iSz)
|
2853
2817
|
self.minmaxButton.setVisible(False)
|
2854
2818
|
self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode"))
|
2855
2819
|
self.minmaxButton.clicked.connect(lambda: self.docEditor.toggleFocusModeRequest.emit())
|
2856
2820
|
|
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)
|
2821
|
+
self.closeButton = NIconToolButton(self, iSz)
|
2862
2822
|
self.closeButton.setVisible(False)
|
2863
2823
|
self.closeButton.setToolTip(self.tr("Close"))
|
2864
2824
|
self.closeButton.clicked.connect(self._closeDocument)
|
2865
2825
|
|
2866
2826
|
# Assemble Layout
|
2867
2827
|
self.outerBox = QHBoxLayout()
|
2868
|
-
self.outerBox.setSpacing(hSp)
|
2869
2828
|
self.outerBox.addWidget(self.tbButton, 0)
|
2829
|
+
self.outerBox.addWidget(self.outlineButton, 0)
|
2870
2830
|
self.outerBox.addWidget(self.searchButton, 0)
|
2831
|
+
self.outerBox.addSpacing(mPx)
|
2871
2832
|
self.outerBox.addWidget(self.itemTitle, 1)
|
2833
|
+
self.outerBox.addSpacing(mPx)
|
2834
|
+
self.outerBox.addSpacing(iPx)
|
2872
2835
|
self.outerBox.addWidget(self.minmaxButton, 0)
|
2873
2836
|
self.outerBox.addWidget(self.closeButton, 0)
|
2837
|
+
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
|
2838
|
+
self.outerBox.setSpacing(0)
|
2839
|
+
|
2874
2840
|
self.setLayout(self.outerBox)
|
2875
2841
|
|
2842
|
+
# Other Signals
|
2843
|
+
SHARED.focusModeChanged.connect(self._focusModeChanged)
|
2844
|
+
|
2876
2845
|
# Fix Margins and Size
|
2877
2846
|
# This is needed for high DPI systems. See issue #499.
|
2878
|
-
cM = CONFIG.pxInt(8)
|
2879
2847
|
self.setContentsMargins(0, 0, 0, 0)
|
2880
|
-
self.
|
2881
|
-
self.setMinimumHeight(fPx + 2*cM)
|
2848
|
+
self.setMinimumHeight(iPx + 2*mPx)
|
2882
2849
|
|
2883
2850
|
self.updateTheme()
|
2884
2851
|
|
@@ -2890,20 +2857,45 @@ class GuiDocEditHeader(QWidget):
|
|
2890
2857
|
# Methods
|
2891
2858
|
##
|
2892
2859
|
|
2860
|
+
def clearHeader(self) -> None:
|
2861
|
+
"""Clear the header."""
|
2862
|
+
self._docHandle = None
|
2863
|
+
self._docOutline = {}
|
2864
|
+
|
2865
|
+
self.itemTitle.setText("")
|
2866
|
+
self.outlineMenu.clear()
|
2867
|
+
self.tbButton.setVisible(False)
|
2868
|
+
self.outlineButton.setVisible(False)
|
2869
|
+
self.searchButton.setVisible(False)
|
2870
|
+
self.closeButton.setVisible(False)
|
2871
|
+
self.minmaxButton.setVisible(False)
|
2872
|
+
return
|
2873
|
+
|
2874
|
+
def setOutline(self, data: dict[int, str]) -> None:
|
2875
|
+
"""Set the document outline dataset."""
|
2876
|
+
if data != self._docOutline:
|
2877
|
+
tStart = time()
|
2878
|
+
self.outlineMenu.clear()
|
2879
|
+
for number, text in data.items():
|
2880
|
+
action = self.outlineMenu.addAction(text)
|
2881
|
+
action.triggered.connect(
|
2882
|
+
lambda _, number=number: self._gotoBlock(number)
|
2883
|
+
)
|
2884
|
+
self._docOutline = data
|
2885
|
+
logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart))
|
2886
|
+
return
|
2887
|
+
|
2893
2888
|
def updateTheme(self) -> None:
|
2894
2889
|
"""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())
|
2890
|
+
self.tbButton.setThemeIcon("menu")
|
2891
|
+
self.outlineButton.setThemeIcon("list")
|
2892
|
+
self.searchButton.setThemeIcon("search")
|
2893
|
+
self.minmaxButton.setThemeIcon("maximise")
|
2894
|
+
self.closeButton.setThemeIcon("close")
|
2905
2895
|
|
2896
|
+
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
2906
2897
|
self.tbButton.setStyleSheet(buttonStyle)
|
2898
|
+
self.outlineButton.setStyleSheet(buttonStyle)
|
2907
2899
|
self.searchButton.setStyleSheet(buttonStyle)
|
2908
2900
|
self.minmaxButton.setStyleSheet(buttonStyle)
|
2909
2901
|
self.closeButton.setStyleSheet(buttonStyle)
|
@@ -2926,51 +2918,25 @@ class GuiDocEditHeader(QWidget):
|
|
2926
2918
|
|
2927
2919
|
return
|
2928
2920
|
|
2929
|
-
def
|
2921
|
+
def setHandle(self, tHandle: str) -> None:
|
2930
2922
|
"""Set the document title from the handle, or alternatively, set
|
2931
2923
|
the whole document path within the project.
|
2932
2924
|
"""
|
2933
2925
|
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
2926
|
|
2942
|
-
pTree = SHARED.project.tree
|
2943
2927
|
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))
|
2928
|
+
self.itemTitle.setText(f" {nwUnicode.U_RSAQUO} ".join(reversed(
|
2929
|
+
[name for name in SHARED.project.tree.getItemPath(tHandle, asName=True)]
|
2930
|
+
)))
|
2952
2931
|
else:
|
2953
|
-
|
2954
|
-
if nwItem is None:
|
2955
|
-
return False
|
2956
|
-
self.itemTitle.setText(nwItem.itemName)
|
2932
|
+
self.itemTitle.setText(i.itemName if (i := SHARED.project.tree[tHandle]) else "")
|
2957
2933
|
|
2958
2934
|
self.tbButton.setVisible(True)
|
2959
2935
|
self.searchButton.setVisible(True)
|
2936
|
+
self.outlineButton.setVisible(True)
|
2960
2937
|
self.closeButton.setVisible(True)
|
2961
2938
|
self.minmaxButton.setVisible(True)
|
2962
2939
|
|
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
2940
|
return
|
2975
2941
|
|
2976
2942
|
##
|
@@ -2980,11 +2946,20 @@ class GuiDocEditHeader(QWidget):
|
|
2980
2946
|
@pyqtSlot()
|
2981
2947
|
def _closeDocument(self) -> None:
|
2982
2948
|
"""Trigger the close editor on the main window."""
|
2949
|
+
self.clearHeader()
|
2983
2950
|
self.closeDocumentRequest.emit()
|
2984
|
-
|
2985
|
-
|
2986
|
-
|
2987
|
-
|
2951
|
+
return
|
2952
|
+
|
2953
|
+
@pyqtSlot(int)
|
2954
|
+
def _gotoBlock(self, blockNumber: int) -> None:
|
2955
|
+
"""Move cursor to a specific heading."""
|
2956
|
+
self.docEditor.setCursorLine(blockNumber + 1)
|
2957
|
+
return
|
2958
|
+
|
2959
|
+
@pyqtSlot(bool)
|
2960
|
+
def _focusModeChanged(self, focusMode: bool) -> None:
|
2961
|
+
"""Update minimise/maximise icon of the Focus Mode button."""
|
2962
|
+
self.minmaxButton.setThemeIcon("minimise" if focusMode else "maximise")
|
2988
2963
|
return
|
2989
2964
|
|
2990
2965
|
##
|
@@ -2995,8 +2970,8 @@ class GuiDocEditHeader(QWidget):
|
|
2995
2970
|
"""Capture a click on the title and ensure that the item is
|
2996
2971
|
selected in the project tree.
|
2997
2972
|
"""
|
2998
|
-
if event.button() ==
|
2999
|
-
self.docEditor.requestProjectItemSelected.emit(self._docHandle, True)
|
2973
|
+
if event.button() == QtMouseLeft:
|
2974
|
+
self.docEditor.requestProjectItemSelected.emit(self._docHandle or "", True)
|
3000
2975
|
return
|
3001
2976
|
|
3002
2977
|
# END Class GuiDocEditHeader
|
@@ -3009,75 +2984,75 @@ class GuiDocEditHeader(QWidget):
|
|
3009
2984
|
|
3010
2985
|
class GuiDocEditFooter(QWidget):
|
3011
2986
|
|
3012
|
-
def __init__(self,
|
3013
|
-
super().__init__(parent=
|
2987
|
+
def __init__(self, parent: QWidget) -> None:
|
2988
|
+
super().__init__(parent=parent)
|
3014
2989
|
|
3015
2990
|
logger.debug("Create: GuiDocEditFooter")
|
3016
2991
|
|
3017
|
-
self.
|
3018
|
-
|
3019
|
-
self._tItem = None
|
2992
|
+
self._tItem = None
|
3020
2993
|
self._docHandle = None
|
3021
2994
|
|
3022
|
-
|
3023
|
-
|
3024
|
-
self.sPx = int(round(0.9*SHARED.theme.baseIconSize))
|
2995
|
+
iPx = round(0.9*SHARED.theme.baseIconHeight)
|
3025
2996
|
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
2997
|
+
mPx = CONFIG.pxInt(8)
|
3026
2998
|
bSp = CONFIG.pxInt(4)
|
3027
2999
|
hSp = CONFIG.pxInt(6)
|
3028
3000
|
|
3029
3001
|
lblFont = self.font()
|
3030
3002
|
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
3031
3003
|
|
3004
|
+
# Cached Translations
|
3005
|
+
self._trLineCount = self.tr("Line: {0} ({1})")
|
3006
|
+
self._trWordCount = self.tr("Words: {0} ({1})")
|
3007
|
+
self._trSelectCount = self.tr("Words: {0} selected")
|
3008
|
+
|
3032
3009
|
# Main Widget Settings
|
3033
3010
|
self.setContentsMargins(0, 0, 0, 0)
|
3034
3011
|
self.setAutoFillBackground(True)
|
3035
3012
|
|
3036
|
-
alLeftTop = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
|
3037
|
-
|
3038
3013
|
# Status
|
3039
|
-
self.statusIcon = QLabel("")
|
3014
|
+
self.statusIcon = QLabel("", self)
|
3040
3015
|
self.statusIcon.setContentsMargins(0, 0, 0, 0)
|
3041
|
-
self.statusIcon.setFixedHeight(
|
3042
|
-
self.statusIcon.setAlignment(
|
3016
|
+
self.statusIcon.setFixedHeight(iPx)
|
3017
|
+
self.statusIcon.setAlignment(QtAlignLeftTop)
|
3043
3018
|
|
3044
|
-
self.statusText = QLabel(self.tr("Status"))
|
3019
|
+
self.statusText = QLabel(self.tr("Status"), self)
|
3045
3020
|
self.statusText.setIndent(0)
|
3046
3021
|
self.statusText.setMargin(0)
|
3047
3022
|
self.statusText.setContentsMargins(0, 0, 0, 0)
|
3048
3023
|
self.statusText.setAutoFillBackground(True)
|
3049
3024
|
self.statusText.setFixedHeight(fPx)
|
3050
|
-
self.statusText.setAlignment(
|
3025
|
+
self.statusText.setAlignment(QtAlignLeftTop)
|
3051
3026
|
self.statusText.setFont(lblFont)
|
3052
3027
|
|
3053
3028
|
# Lines
|
3054
|
-
self.linesIcon = QLabel("")
|
3029
|
+
self.linesIcon = QLabel("", self)
|
3055
3030
|
self.linesIcon.setContentsMargins(0, 0, 0, 0)
|
3056
|
-
self.linesIcon.setFixedHeight(
|
3057
|
-
self.linesIcon.setAlignment(
|
3031
|
+
self.linesIcon.setFixedHeight(iPx)
|
3032
|
+
self.linesIcon.setAlignment(QtAlignLeftTop)
|
3058
3033
|
|
3059
|
-
self.linesText = QLabel("")
|
3034
|
+
self.linesText = QLabel("", self)
|
3060
3035
|
self.linesText.setIndent(0)
|
3061
3036
|
self.linesText.setMargin(0)
|
3062
3037
|
self.linesText.setContentsMargins(0, 0, 0, 0)
|
3063
3038
|
self.linesText.setAutoFillBackground(True)
|
3064
3039
|
self.linesText.setFixedHeight(fPx)
|
3065
|
-
self.linesText.setAlignment(
|
3040
|
+
self.linesText.setAlignment(QtAlignLeftTop)
|
3066
3041
|
self.linesText.setFont(lblFont)
|
3067
3042
|
|
3068
3043
|
# Words
|
3069
|
-
self.wordsIcon = QLabel("")
|
3044
|
+
self.wordsIcon = QLabel("", self)
|
3070
3045
|
self.wordsIcon.setContentsMargins(0, 0, 0, 0)
|
3071
|
-
self.wordsIcon.setFixedHeight(
|
3072
|
-
self.wordsIcon.setAlignment(
|
3046
|
+
self.wordsIcon.setFixedHeight(iPx)
|
3047
|
+
self.wordsIcon.setAlignment(QtAlignLeftTop)
|
3073
3048
|
|
3074
|
-
self.wordsText = QLabel("")
|
3049
|
+
self.wordsText = QLabel("", self)
|
3075
3050
|
self.wordsText.setIndent(0)
|
3076
3051
|
self.wordsText.setMargin(0)
|
3077
3052
|
self.wordsText.setContentsMargins(0, 0, 0, 0)
|
3078
3053
|
self.wordsText.setAutoFillBackground(True)
|
3079
3054
|
self.wordsText.setFixedHeight(fPx)
|
3080
|
-
self.wordsText.setAlignment(
|
3055
|
+
self.wordsText.setAlignment(QtAlignLeftTop)
|
3081
3056
|
self.wordsText.setFont(lblFont)
|
3082
3057
|
|
3083
3058
|
# Assemble Layout
|
@@ -3091,19 +3066,20 @@ class GuiDocEditFooter(QWidget):
|
|
3091
3066
|
self.outerBox.addSpacing(hSp)
|
3092
3067
|
self.outerBox.addWidget(self.wordsIcon)
|
3093
3068
|
self.outerBox.addWidget(self.wordsText)
|
3069
|
+
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
|
3070
|
+
|
3094
3071
|
self.setLayout(self.outerBox)
|
3095
3072
|
|
3096
3073
|
# Fix Margins and Size
|
3097
3074
|
# This is needed for high DPI systems. See issue #499.
|
3098
|
-
cM = CONFIG.pxInt(8)
|
3099
3075
|
self.setContentsMargins(0, 0, 0, 0)
|
3100
|
-
self.
|
3101
|
-
self.setMinimumHeight(fPx + 2*cM)
|
3076
|
+
self.setMinimumHeight(fPx + 2*mPx)
|
3102
3077
|
|
3103
3078
|
# Fix the Colours
|
3104
3079
|
self.updateTheme()
|
3105
|
-
|
3106
|
-
|
3080
|
+
|
3081
|
+
# Initialise Info
|
3082
|
+
self.updateWordCount(0, False)
|
3107
3083
|
|
3108
3084
|
logger.debug("Ready: GuiDocEditFooter")
|
3109
3085
|
|
@@ -3115,8 +3091,9 @@ class GuiDocEditFooter(QWidget):
|
|
3115
3091
|
|
3116
3092
|
def updateTheme(self) -> None:
|
3117
3093
|
"""Update theme elements."""
|
3118
|
-
|
3119
|
-
self.
|
3094
|
+
iPx = round(0.9*SHARED.theme.baseIconHeight)
|
3095
|
+
self.linesIcon.setPixmap(SHARED.theme.getPixmap("status_lines", (iPx, iPx)))
|
3096
|
+
self.wordsIcon.setPixmap(SHARED.theme.getPixmap("status_stats", (iPx, iPx)))
|
3120
3097
|
self.matchColours()
|
3121
3098
|
return
|
3122
3099
|
|
@@ -3145,27 +3122,20 @@ class GuiDocEditFooter(QWidget):
|
|
3145
3122
|
else:
|
3146
3123
|
self._tItem = SHARED.project.tree[self._docHandle]
|
3147
3124
|
|
3148
|
-
self.setHasSelection(False)
|
3149
3125
|
self.updateInfo()
|
3150
|
-
self.
|
3126
|
+
self.updateWordCount(0, False)
|
3151
3127
|
|
3152
3128
|
return
|
3153
3129
|
|
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
3130
|
def updateInfo(self) -> None:
|
3162
3131
|
"""Update the content of text labels."""
|
3163
3132
|
if self._tItem is None:
|
3164
3133
|
sIcon = QPixmap()
|
3165
3134
|
sText = ""
|
3166
3135
|
else:
|
3136
|
+
iPx = round(0.9*SHARED.theme.baseIconHeight)
|
3167
3137
|
status, icon = self._tItem.getImportStatus(incIcon=True)
|
3168
|
-
sIcon = icon.pixmap(
|
3138
|
+
sIcon = icon.pixmap(iPx, iPx)
|
3169
3139
|
sText = f"{status} / {self._tItem.describeMe()}"
|
3170
3140
|
|
3171
3141
|
self.statusIcon.setPixmap(sIcon)
|
@@ -3173,62 +3143,27 @@ class GuiDocEditFooter(QWidget):
|
|
3173
3143
|
|
3174
3144
|
return
|
3175
3145
|
|
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()
|
3146
|
+
def updateLineCount(self, cursor: QTextCursor) -> None:
|
3147
|
+
"""Update the line and document position counter."""
|
3148
|
+
cPos = cursor.position() + 1
|
3149
|
+
cLine = cursor.blockNumber() + 1
|
3150
|
+
cCount = max(cursor.document().characterCount(), 1)
|
3185
3151
|
self.linesText.setText(
|
3186
|
-
self.
|
3152
|
+
self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %")
|
3187
3153
|
)
|
3188
3154
|
return
|
3189
3155
|
|
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:
|
3156
|
+
def updateWordCount(self, wCount: int, selection: bool) -> None:
|
3157
|
+
"""Update word counter information."""
|
3158
|
+
if selection and wCount:
|
3159
|
+
wText = self._trSelectCount.format(f"{wCount:n}")
|
3160
|
+
elif self._tItem:
|
3208
3161
|
wCount = self._tItem.wordCount
|
3209
3162
|
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
|
-
)
|
3163
|
+
wText = self._trWordCount.format(f"{wCount:n}", f"{wDiff:+n}")
|
3164
|
+
else:
|
3165
|
+
wText = self._trWordCount.format("0", "+0")
|
3166
|
+
self.wordsText.setText(wText)
|
3232
3167
|
return
|
3233
3168
|
|
3234
3169
|
# END Class GuiDocEditFooter
|