novelWriter 2.3rc1__py3-none-any.whl → 2.4b1__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.4b1.dist-info}/METADATA +1 -1
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4b1.dist-info}/RECORD +99 -85
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4b1.dist-info}/WHEEL +1 -1
- novelWriter-2.4b1.dist-info/entry_points.txt +2 -0
- novelwriter/__init__.py +5 -5
- 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/project_nl_NL.json +11 -0
- novelwriter/assets/i18n/project_pt_BR.json +11 -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_arrow-down.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_arrow-right.svg +4 -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_light/icons.conf +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_light/typ_arrow-down.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_arrow-right.svg +4 -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/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 +1 -1
- novelwriter/config.py +35 -12
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +60 -40
- novelwriter/core/coretools.py +98 -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 +2 -1
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/status.py +4 -4
- novelwriter/core/storage.py +16 -6
- novelwriter/core/tohtml.py +22 -25
- novelwriter/core/tokenizer.py +416 -236
- novelwriter/core/tomd.py +17 -8
- novelwriter/core/toodt.py +65 -7
- novelwriter/core/tree.py +8 -8
- novelwriter/dialogs/about.py +2 -2
- novelwriter/dialogs/docsplit.py +7 -8
- novelwriter/dialogs/preferences.py +3 -6
- novelwriter/dialogs/wordlist.py +1 -1
- novelwriter/enum.py +17 -14
- novelwriter/extensions/configlayout.py +22 -0
- novelwriter/extensions/modified.py +20 -2
- novelwriter/extensions/versioninfo.py +1 -1
- novelwriter/gui/doceditor.py +257 -279
- novelwriter/gui/dochighlight.py +29 -25
- novelwriter/gui/docviewer.py +139 -148
- novelwriter/gui/docviewerpanel.py +4 -24
- novelwriter/gui/editordocument.py +12 -1
- novelwriter/gui/itemdetails.py +6 -6
- novelwriter/gui/mainmenu.py +37 -17
- novelwriter/gui/noveltree.py +11 -19
- novelwriter/gui/outline.py +43 -20
- novelwriter/gui/projtree.py +88 -88
- novelwriter/gui/search.py +316 -0
- novelwriter/gui/sidebar.py +25 -30
- novelwriter/gui/theme.py +68 -8
- novelwriter/guimain.py +183 -178
- novelwriter/shared.py +26 -1
- novelwriter/text/__init__.py +3 -0
- novelwriter/text/counting.py +137 -0
- novelwriter/tools/manuscript.py +344 -55
- novelwriter/tools/manussettings.py +214 -71
- novelwriter/tools/noveldetails.py +1 -1
- novelwriter/tools/welcome.py +8 -9
- novelWriter-2.3rc1.dist-info/entry_points.txt +0 -5
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4b1.dist-info}/top_level.txt +0 -0
novelwriter/gui/doceditor.py
CHANGED
@@ -53,15 +53,17 @@ from PyQt5.QtWidgets import (
|
|
53
53
|
)
|
54
54
|
|
55
55
|
from novelwriter import CONFIG, SHARED
|
56
|
-
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary
|
57
56
|
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
|
57
|
+
from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
|
61
58
|
from novelwriter.core.document import NWDocument
|
62
|
-
from novelwriter.
|
63
|
-
from novelwriter.gui.editordocument import GuiTextDocument
|
59
|
+
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary
|
64
60
|
from novelwriter.extensions.eventfilters import WheelEventFilter
|
61
|
+
from novelwriter.extensions.modified import NIconToolButton
|
62
|
+
from novelwriter.gui.dochighlight import BLOCK_META, BLOCK_TITLE
|
63
|
+
from novelwriter.gui.editordocument import GuiTextDocument
|
64
|
+
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
65
|
+
from novelwriter.text.counting import standardCounter
|
66
|
+
from novelwriter.tools.lipsum import GuiLipsum
|
65
67
|
|
66
68
|
if TYPE_CHECKING: # pragma: no cover
|
67
69
|
from novelwriter.guimain import GuiMain
|
@@ -99,6 +101,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
99
101
|
toggleFocusModeRequest = pyqtSignal()
|
100
102
|
requestProjectItemSelected = pyqtSignal(str, bool)
|
101
103
|
requestProjectItemRenamed = pyqtSignal(str, str)
|
104
|
+
requestNewNoteCreation = pyqtSignal(str, nwItemClass)
|
102
105
|
|
103
106
|
def __init__(self, mainGui: GuiMain) -> None:
|
104
107
|
super().__init__(parent=mainGui)
|
@@ -142,9 +145,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
142
145
|
self._qDocument = GuiTextDocument(self)
|
143
146
|
self.setDocument(self._qDocument)
|
144
147
|
|
145
|
-
# Connect Signals
|
148
|
+
# Connect Editor and Document Signals
|
146
149
|
self._qDocument.contentsChange.connect(self._docChange)
|
147
150
|
self.selectionChanged.connect(self._updateSelectedStatus)
|
151
|
+
self.cursorPositionChanged.connect(self._cursorMoved)
|
148
152
|
self.spellCheckStateChanged.connect(self._qDocument.setSpellCheckState)
|
149
153
|
|
150
154
|
# Document Title
|
@@ -153,7 +157,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
153
157
|
self.docSearch = GuiDocEditSearch(self)
|
154
158
|
self.docToolBar = GuiDocToolBar(self)
|
155
159
|
|
156
|
-
# Connect Signals
|
160
|
+
# Connect Widget Signals
|
157
161
|
self.docHeader.closeDocumentRequest.connect(self._closeCurrentDocument)
|
158
162
|
self.docHeader.toggleToolBarRequest.connect(self._toggleToolBarVisibility)
|
159
163
|
self.docToolBar.requestDocAction.connect(self.docAction)
|
@@ -184,18 +188,18 @@ class GuiDocEditor(QPlainTextEdit):
|
|
184
188
|
self.followTag2.activated.connect(self._processTag)
|
185
189
|
|
186
190
|
# Set Up Document Word Counter
|
187
|
-
self.
|
188
|
-
self.
|
189
|
-
self.
|
191
|
+
self.timerDoc = QTimer(self)
|
192
|
+
self.timerDoc.timeout.connect(self._runDocumentTasks)
|
193
|
+
self.timerDoc.setInterval(5000)
|
190
194
|
|
191
195
|
self.wCounterDoc = BackgroundWordCounter(self)
|
192
196
|
self.wCounterDoc.setAutoDelete(False)
|
193
197
|
self.wCounterDoc.signals.countsReady.connect(self._updateDocCounts)
|
194
198
|
|
195
199
|
# Set Up Selection Word Counter
|
196
|
-
self.
|
197
|
-
self.
|
198
|
-
self.
|
200
|
+
self.timerSel = QTimer(self)
|
201
|
+
self.timerSel.timeout.connect(self._runSelCounter)
|
202
|
+
self.timerSel.setInterval(500)
|
199
203
|
|
200
204
|
self.wCounterSel = BackgroundWordCounter(self, forSelection=True)
|
201
205
|
self.wCounterSel.setAutoDelete(False)
|
@@ -248,8 +252,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
248
252
|
self._nwDocument = None
|
249
253
|
self.setReadOnly(True)
|
250
254
|
self.clear()
|
251
|
-
self.
|
252
|
-
self.
|
255
|
+
self.timerDoc.stop()
|
256
|
+
self.timerSel.stop()
|
253
257
|
|
254
258
|
self._docHandle = None
|
255
259
|
self._lastEdit = 0.0
|
@@ -258,7 +262,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
258
262
|
self._doReplace = False
|
259
263
|
|
260
264
|
self.setDocumentChanged(False)
|
261
|
-
self.docHeader.
|
265
|
+
self.docHeader.clearHeader()
|
262
266
|
self.docFooter.setHandle(self._docHandle)
|
263
267
|
self.docToolBar.setVisible(False)
|
264
268
|
|
@@ -362,7 +366,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
362
366
|
# which makes it read only.
|
363
367
|
if self._docHandle:
|
364
368
|
self._qDocument.syntaxHighlighter.rehighlight()
|
365
|
-
self.docHeader.
|
369
|
+
self.docHeader.setHandle(self._docHandle)
|
366
370
|
else:
|
367
371
|
self.clearEditor()
|
368
372
|
|
@@ -396,12 +400,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
396
400
|
|
397
401
|
self._lastEdit = time()
|
398
402
|
self._lastActive = time()
|
399
|
-
self.
|
400
|
-
self.
|
403
|
+
self._runDocumentTasks()
|
404
|
+
self.timerDoc.start()
|
401
405
|
|
402
406
|
self.setReadOnly(False)
|
403
|
-
self.docHeader.setTitleFromHandle(self._docHandle)
|
404
|
-
self.docFooter.setHandle(self._docHandle)
|
405
407
|
self.updateDocMargins()
|
406
408
|
|
407
409
|
if tLine is None and self._nwItem is not None:
|
@@ -409,7 +411,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
409
411
|
elif isinstance(tLine, int):
|
410
412
|
self.setCursorLine(tLine)
|
411
413
|
|
412
|
-
self.
|
414
|
+
self.docHeader.setHandle(tHandle)
|
415
|
+
self.docFooter.setHandle(tHandle)
|
413
416
|
|
414
417
|
# This is a hack to fix invisible cursor on an empty document
|
415
418
|
if self._qDocument.characterCount() <= 1:
|
@@ -432,7 +435,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
432
435
|
|
433
436
|
def updateTagHighLighting(self) -> None:
|
434
437
|
"""Rerun the syntax highlighter on all meta data lines."""
|
435
|
-
self._qDocument.syntaxHighlighter.rehighlightByType(
|
438
|
+
self._qDocument.syntaxHighlighter.rehighlightByType(BLOCK_META)
|
436
439
|
return
|
437
440
|
|
438
441
|
def replaceText(self, text: str) -> None:
|
@@ -462,7 +465,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
462
465
|
return False
|
463
466
|
|
464
467
|
docText = self.getText()
|
465
|
-
cC, wC, pC =
|
468
|
+
cC, wC, pC = standardCounter(docText)
|
466
469
|
self._updateDocCounts(cC, wC, pC)
|
467
470
|
|
468
471
|
self.saveCursorPosition()
|
@@ -548,8 +551,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
548
551
|
sH = hBar.height() if hBar.isVisible() else 0
|
549
552
|
|
550
553
|
tM = self._vpMargin
|
551
|
-
if CONFIG.textWidth > 0 or
|
552
|
-
tW = CONFIG.getTextWidth(
|
554
|
+
if CONFIG.textWidth > 0 or SHARED.focusMode:
|
555
|
+
tW = CONFIG.getTextWidth(SHARED.focusMode)
|
553
556
|
tM = max((wW - sW - tW)//2, self._vpMargin)
|
554
557
|
|
555
558
|
tB = self.frameWidth()
|
@@ -600,13 +603,15 @@ class GuiDocEditor(QPlainTextEdit):
|
|
600
603
|
# Setters
|
601
604
|
##
|
602
605
|
|
603
|
-
def setDocumentChanged(self, state: bool) ->
|
606
|
+
def setDocumentChanged(self, state: bool) -> None:
|
604
607
|
"""Keep track of the document changed variable, and emit the
|
605
608
|
document change signal.
|
606
609
|
"""
|
607
|
-
self._docChanged
|
608
|
-
|
609
|
-
|
610
|
+
if self._docChanged != state:
|
611
|
+
logger.debug("Document changed status is '%s'", state)
|
612
|
+
self._docChanged = state
|
613
|
+
self.editedStatusChanged.emit(self._docChanged)
|
614
|
+
return
|
610
615
|
|
611
616
|
def setCursorPosition(self, position: int) -> None:
|
612
617
|
"""Move the cursor to a given position in the document."""
|
@@ -616,7 +621,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
616
621
|
cursor.setPosition(minmax(position, 0, nChars-1))
|
617
622
|
self.setTextCursor(cursor)
|
618
623
|
self.centerCursor()
|
619
|
-
self.docFooter.updateLineCount()
|
620
624
|
return
|
621
625
|
|
622
626
|
def saveCursorPosition(self) -> None:
|
@@ -635,6 +639,14 @@ class GuiDocEditor(QPlainTextEdit):
|
|
635
639
|
logger.debug("Cursor moved to line %d", line)
|
636
640
|
return
|
637
641
|
|
642
|
+
def setCursorSelection(self, selStart: int, selLength: int) -> None:
|
643
|
+
"""Make a text selection."""
|
644
|
+
cursor = self.textCursor()
|
645
|
+
cursor.setPosition(selStart, QTextCursor.MoveMode.MoveAnchor)
|
646
|
+
cursor.setPosition(selStart + selLength, QTextCursor.MoveMode.KeepAnchor)
|
647
|
+
self.setTextCursor(cursor)
|
648
|
+
return
|
649
|
+
|
638
650
|
##
|
639
651
|
# Spell Checking
|
640
652
|
##
|
@@ -745,6 +757,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
745
757
|
self._formatBlock(nwDocAction.BLOCK_TTL)
|
746
758
|
elif action == nwDocAction.BLOCK_UNN:
|
747
759
|
self._formatBlock(nwDocAction.BLOCK_UNN)
|
760
|
+
elif action == nwDocAction.BLOCK_HSC:
|
761
|
+
self._formatBlock(nwDocAction.BLOCK_HSC)
|
748
762
|
elif action == nwDocAction.REPL_SNG:
|
749
763
|
self._replaceQuotes("'", self._typSQuoteO, self._typSQuoteC)
|
750
764
|
elif action == nwDocAction.REPL_DBL:
|
@@ -769,6 +783,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
769
783
|
self._wrapSelection(nwShortcode.STRIKE_O, nwShortcode.STRIKE_C)
|
770
784
|
elif action == nwDocAction.SC_ULINE:
|
771
785
|
self._wrapSelection(nwShortcode.ULINE_O, nwShortcode.ULINE_C)
|
786
|
+
elif action == nwDocAction.SC_MARK:
|
787
|
+
self._wrapSelection(nwShortcode.MARK_O, nwShortcode.MARK_C)
|
772
788
|
elif action == nwDocAction.SC_SUP:
|
773
789
|
self._wrapSelection(nwShortcode.SUP_O, nwShortcode.SUP_C)
|
774
790
|
elif action == nwDocAction.SC_SUB:
|
@@ -785,11 +801,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
785
801
|
|
786
802
|
def anyFocus(self) -> bool:
|
787
803
|
"""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
|
804
|
+
return self.hasFocus() or self.isAncestorOf(qApp.focusWidget())
|
793
805
|
|
794
806
|
def revealLocation(self) -> None:
|
795
807
|
"""Tell the user where on the file system the file in the editor
|
@@ -947,8 +959,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
947
959
|
else:
|
948
960
|
super().keyPressEvent(event)
|
949
961
|
|
950
|
-
self.docFooter.updateLineCount()
|
951
|
-
|
952
962
|
return
|
953
963
|
|
954
964
|
def focusNextPrevChild(self, next: bool) -> bool:
|
@@ -971,7 +981,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
971
981
|
if qApp.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier:
|
972
982
|
self._processTag(self.cursorForPosition(event.pos()))
|
973
983
|
super().mouseReleaseEvent(event)
|
974
|
-
self.docFooter.updateLineCount()
|
975
984
|
return
|
976
985
|
|
977
986
|
def resizeEvent(self, event: QResizeEvent) -> None:
|
@@ -991,8 +1000,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
991
1000
|
"""Called when an item label is changed to check if the document
|
992
1001
|
title bar needs updating,
|
993
1002
|
"""
|
994
|
-
if tHandle == self._docHandle:
|
995
|
-
self.docHeader.
|
1003
|
+
if tHandle and tHandle == self._docHandle:
|
1004
|
+
self.docHeader.setHandle(tHandle)
|
996
1005
|
self.docFooter.updateInfo()
|
997
1006
|
self.updateDocMargins()
|
998
1007
|
return
|
@@ -1033,32 +1042,35 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1033
1042
|
if not self._docChanged:
|
1034
1043
|
self.setDocumentChanged(removed != 0 or added != 0)
|
1035
1044
|
|
1036
|
-
if not self.
|
1037
|
-
self.
|
1038
|
-
|
1039
|
-
block = self._qDocument.findBlock(pos)
|
1040
|
-
if not block.isValid():
|
1041
|
-
return
|
1045
|
+
if not self.timerDoc.isActive():
|
1046
|
+
self.timerDoc.start()
|
1042
1047
|
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1048
|
+
if (block := self._qDocument.findBlock(pos)).isValid():
|
1049
|
+
text = block.text()
|
1050
|
+
if text.startswith("@") and added + removed == 1:
|
1051
|
+
# Only run on single keypresses, otherwise it will trigger
|
1052
|
+
# at unwanted times when other changes are made to the document
|
1053
|
+
cursor = self.textCursor()
|
1054
|
+
bPos = cursor.positionInBlock()
|
1055
|
+
if bPos > 0:
|
1056
|
+
show = self._completer.updateText(text, bPos)
|
1057
|
+
point = self.cursorRect().bottomRight()
|
1058
|
+
self._completer.move(self.viewport().mapToGlobal(point))
|
1059
|
+
self._completer.setVisible(show)
|
1060
|
+
else:
|
1061
|
+
self._completer.setVisible(False)
|
1056
1062
|
|
1057
1063
|
if self._doReplace and added == 1:
|
1058
1064
|
self._docAutoReplace(text)
|
1059
1065
|
|
1060
1066
|
return
|
1061
1067
|
|
1068
|
+
@pyqtSlot()
|
1069
|
+
def _cursorMoved(self):
|
1070
|
+
"""Triggered when the cursor moved in the editor."""
|
1071
|
+
self.docFooter.updateLineCount(self.textCursor())
|
1072
|
+
return
|
1073
|
+
|
1062
1074
|
@pyqtSlot(int, int, str)
|
1063
1075
|
def _insertCompletion(self, pos: int, length: int, text: str) -> None:
|
1064
1076
|
"""Insert choice from the completer menu."""
|
@@ -1081,7 +1093,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1081
1093
|
|
1082
1094
|
ctxMenu = QMenu(self)
|
1083
1095
|
ctxMenu.setObjectName("ContextMenu")
|
1084
|
-
if pBlock.userState() ==
|
1096
|
+
if pBlock.userState() == BLOCK_TITLE:
|
1085
1097
|
action = ctxMenu.addAction(self.tr("Set as Document Name"))
|
1086
1098
|
action.triggered.connect(lambda: self._emitRenameItem(pBlock))
|
1087
1099
|
|
@@ -1176,10 +1188,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1176
1188
|
return
|
1177
1189
|
|
1178
1190
|
@pyqtSlot()
|
1179
|
-
def
|
1180
|
-
"""
|
1181
|
-
inactivity.
|
1182
|
-
"""
|
1191
|
+
def _runDocumentTasks(self) -> None:
|
1192
|
+
"""Run timer document tasks."""
|
1183
1193
|
if self._docHandle is None:
|
1184
1194
|
return
|
1185
1195
|
|
@@ -1190,25 +1200,23 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1190
1200
|
if time() - self._lastEdit < 25.0:
|
1191
1201
|
logger.debug("Running word counter")
|
1192
1202
|
SHARED.runInThreadPool(self.wCounterDoc)
|
1203
|
+
self.docHeader.setOutline({
|
1204
|
+
block.blockNumber(): block.text()
|
1205
|
+
for block in self._qDocument.iterBlockByType(BLOCK_TITLE, maxCount=30)
|
1206
|
+
})
|
1193
1207
|
|
1194
1208
|
return
|
1195
1209
|
|
1196
1210
|
@pyqtSlot(int, int, int)
|
1197
1211
|
def _updateDocCounts(self, cCount: int, wCount: int, pCount: int) -> None:
|
1198
1212
|
"""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
|
-
|
1213
|
+
if self._docHandle and self._nwItem:
|
1214
|
+
logger.debug("Updating word count")
|
1215
|
+
self._nwItem.setCharCount(cCount)
|
1216
|
+
self._nwItem.setWordCount(wCount)
|
1217
|
+
self._nwItem.setParaCount(pCount)
|
1218
|
+
self.docCountsChanged.emit(self._docHandle, cCount, wCount, pCount)
|
1219
|
+
self.docFooter.updateWordCount(wCount, False)
|
1212
1220
|
return
|
1213
1221
|
|
1214
1222
|
@pyqtSlot()
|
@@ -1217,13 +1225,11 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1217
1225
|
information to the footer, and start the selection word counter.
|
1218
1226
|
"""
|
1219
1227
|
if self.textCursor().hasSelection():
|
1220
|
-
if not self.
|
1221
|
-
self.
|
1222
|
-
self.docFooter.setHasSelection(True)
|
1228
|
+
if not self.timerSel.isActive():
|
1229
|
+
self.timerSel.start()
|
1223
1230
|
else:
|
1224
|
-
self.
|
1225
|
-
self.docFooter.
|
1226
|
-
self.docFooter.updateCounts()
|
1231
|
+
self.timerSel.stop()
|
1232
|
+
self.docFooter.updateWordCount(0, False)
|
1227
1233
|
return
|
1228
1234
|
|
1229
1235
|
@pyqtSlot()
|
@@ -1243,13 +1249,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1243
1249
|
@pyqtSlot(int, int, int)
|
1244
1250
|
def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
|
1245
1251
|
"""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
|
-
|
1252
|
+
if self._docHandle and self._nwItem:
|
1253
|
+
logger.debug("User selected %d words", wCount)
|
1254
|
+
self.docFooter.updateWordCount(wCount, True)
|
1255
|
+
self.timerSel.stop()
|
1253
1256
|
return
|
1254
1257
|
|
1255
1258
|
@pyqtSlot()
|
@@ -1311,6 +1314,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1311
1314
|
self._docHandle, wrapAround=self.docSearch.doLoop
|
1312
1315
|
)
|
1313
1316
|
self.beginSearch()
|
1317
|
+
self.setFocus()
|
1314
1318
|
return
|
1315
1319
|
|
1316
1320
|
cursor = self.textCursor()
|
@@ -1331,6 +1335,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1331
1335
|
self._docHandle, wrapAround=self.docSearch.doLoop
|
1332
1336
|
)
|
1333
1337
|
self.beginSearch()
|
1338
|
+
self.setFocus()
|
1334
1339
|
return
|
1335
1340
|
else:
|
1336
1341
|
resIdx = 0 if doLoop else maxIdx
|
@@ -1339,7 +1344,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1339
1344
|
cursor.setPosition(resE[resIdx], QTextCursor.MoveMode.KeepAnchor)
|
1340
1345
|
self.setTextCursor(cursor)
|
1341
1346
|
|
1342
|
-
self.docFooter.updateLineCount()
|
1343
1347
|
self.docSearch.setResultCount(resIdx + 1, len(resS))
|
1344
1348
|
self._lastFind = (resS[resIdx], resE[resIdx])
|
1345
1349
|
|
@@ -1370,9 +1374,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1370
1374
|
cursor.setPosition(0)
|
1371
1375
|
self.setTextCursor(cursor)
|
1372
1376
|
|
1373
|
-
# Search up to a maximum of
|
1374
|
-
# searches like a regex search for .* don't loop
|
1375
|
-
|
1377
|
+
# Search up to a maximum of MAX_SEARCH_RESULT, and make sure
|
1378
|
+
# certain special searches like a regex search for .* don't loop
|
1379
|
+
# infinitely
|
1380
|
+
while self.find(searchFor, findOpt) and len(resE) <= nwConst.MAX_SEARCH_RESULT:
|
1376
1381
|
cursor = self.textCursor()
|
1377
1382
|
if cursor.hasSelection():
|
1378
1383
|
resS.append(cursor.selectionStart())
|
@@ -1436,13 +1441,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1436
1441
|
|
1437
1442
|
# Make sure the selected text was selected by an actual find
|
1438
1443
|
# 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:
|
1444
|
+
if self._lastFind == (cursor.selectionStart(), cursor.selectionEnd()):
|
1446
1445
|
cursor.beginEditBlock()
|
1447
1446
|
cursor.removeSelectedText()
|
1448
1447
|
cursor.insertText(replWith)
|
@@ -1451,10 +1450,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1451
1450
|
self.setTextCursor(cursor)
|
1452
1451
|
logger.debug(
|
1453
1452
|
"Replaced occurrence of '%s' with '%s' on line %d",
|
1454
|
-
searchFor, replWith, cursor.blockNumber()
|
1453
|
+
searchFor, replWith, cursor.blockNumber() + 1
|
1455
1454
|
)
|
1456
|
-
else:
|
1457
|
-
logger.error("The selected text is not a search result, skipping replace")
|
1458
1455
|
|
1459
1456
|
self.findNext()
|
1460
1457
|
|
@@ -1670,6 +1667,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1670
1667
|
elif text.startswith("##! "):
|
1671
1668
|
temp = text[4:]
|
1672
1669
|
offset = 4
|
1670
|
+
elif text.startswith("###! "):
|
1671
|
+
temp = text[5:]
|
1672
|
+
offset = 5
|
1673
1673
|
elif text.startswith(">> "):
|
1674
1674
|
temp = text[3:]
|
1675
1675
|
offset = 3
|
@@ -1721,6 +1721,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1721
1721
|
elif action == nwDocAction.BLOCK_UNN:
|
1722
1722
|
text = f"##! {temp}"
|
1723
1723
|
offset -= 4
|
1724
|
+
elif action == nwDocAction.BLOCK_HSC:
|
1725
|
+
text = f"###! {temp}"
|
1726
|
+
offset -= 5
|
1724
1727
|
elif action == nwDocAction.ALIGN_L:
|
1725
1728
|
text = f"{temp} <<"
|
1726
1729
|
elif action == nwDocAction.ALIGN_C:
|
@@ -1867,7 +1870,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1867
1870
|
if text.startswith("@") and self._docHandle:
|
1868
1871
|
|
1869
1872
|
isGood, tBits, tPos = SHARED.project.index.scanThis(text)
|
1870
|
-
if
|
1873
|
+
if (
|
1874
|
+
not isGood or not tBits or tBits[0] == nwKeyWords.TAG_KEY
|
1875
|
+
or tBits[0] not in nwKeyWords.VALID_KEYS
|
1876
|
+
):
|
1871
1877
|
return nwTrinary.NEUTRAL
|
1872
1878
|
|
1873
1879
|
tag = ""
|
@@ -1895,13 +1901,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1895
1901
|
"Do you want to create a new project note for the tag '{0}'?"
|
1896
1902
|
).format(tag)):
|
1897
1903
|
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])))
|
1904
|
+
self.requestNewNoteCreation.emit(tag, itemClass)
|
1905
|
+
qApp.processEvents()
|
1906
|
+
self._qDocument.syntaxHighlighter.rehighlightBlock(block)
|
1905
1907
|
|
1906
1908
|
return nwTrinary.POSITIVE if exist else nwTrinary.NEGATIVE
|
1907
1909
|
|
@@ -2224,7 +2226,7 @@ class BackgroundWordCounter(QRunnable):
|
|
2224
2226
|
else:
|
2225
2227
|
text = self._docEditor.getText()
|
2226
2228
|
|
2227
|
-
cC, wC, pC =
|
2229
|
+
cC, wC, pC = standardCounter(text)
|
2228
2230
|
self.signals.countsReady.emit(cC, wC, pC)
|
2229
2231
|
self._isRunning = False
|
2230
2232
|
|
@@ -2313,6 +2315,13 @@ class GuiDocToolBar(QWidget):
|
|
2313
2315
|
lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE)
|
2314
2316
|
)
|
2315
2317
|
|
2318
|
+
self.tbMark = QToolButton(self)
|
2319
|
+
self.tbMark.setIconSize(iconSize)
|
2320
|
+
self.tbMark.setToolTip(self.tr("Shortcode Highlight"))
|
2321
|
+
self.tbMark.clicked.connect(
|
2322
|
+
lambda: self.requestDocAction.emit(nwDocAction.SC_MARK)
|
2323
|
+
)
|
2324
|
+
|
2316
2325
|
self.tbSuperscript = QToolButton(self)
|
2317
2326
|
self.tbSuperscript.setIconSize(iconSize)
|
2318
2327
|
self.tbSuperscript.setToolTip(self.tr("Shortcode Superscript"))
|
@@ -2339,6 +2348,7 @@ class GuiDocToolBar(QWidget):
|
|
2339
2348
|
self.outerBox.addWidget(self.tbItalic)
|
2340
2349
|
self.outerBox.addWidget(self.tbStrike)
|
2341
2350
|
self.outerBox.addWidget(self.tbUnderline)
|
2351
|
+
self.outerBox.addWidget(self.tbMark)
|
2342
2352
|
self.outerBox.addWidget(self.tbSuperscript)
|
2343
2353
|
self.outerBox.addWidget(self.tbSubscript)
|
2344
2354
|
self.outerBox.setContentsMargins(cM, cM, cM, cM)
|
@@ -2369,6 +2379,7 @@ class GuiDocToolBar(QWidget):
|
|
2369
2379
|
self.tbItalic.setIcon(SHARED.theme.getIcon("fmt_italic"))
|
2370
2380
|
self.tbStrike.setIcon(SHARED.theme.getIcon("fmt_strike"))
|
2371
2381
|
self.tbUnderline.setIcon(SHARED.theme.getIcon("fmt_underline"))
|
2382
|
+
self.tbMark.setIcon(SHARED.theme.getIcon("fmt_mark"))
|
2372
2383
|
self.tbSuperscript.setIcon(SHARED.theme.getIcon("fmt_superscript"))
|
2373
2384
|
self.tbSubscript.setIcon(SHARED.theme.getIcon("fmt_subscript"))
|
2374
2385
|
|
@@ -2605,8 +2616,10 @@ class GuiDocEditSearch(QFrame):
|
|
2605
2616
|
|
2606
2617
|
def setResultCount(self, currRes: int | None, resCount: int | None) -> None:
|
2607
2618
|
"""Set the count values for the current search."""
|
2619
|
+
lim = nwConst.MAX_SEARCH_RESULT
|
2620
|
+
numCount = f"{lim:n}+" if (resCount or 0) > lim else f"{resCount:n}"
|
2608
2621
|
sCurrRes = "?" if currRes is None else str(currRes)
|
2609
|
-
sResCount = "?" if resCount is None else
|
2622
|
+
sResCount = "?" if resCount is None else numCount
|
2610
2623
|
minWidth = SHARED.theme.getTextWidth(f"{sResCount}//{sResCount}", self.boxFont)
|
2611
2624
|
self.resultLabel.setText(f"{sCurrRes}/{sResCount}")
|
2612
2625
|
self.resultLabel.setMinimumWidth(minWidth)
|
@@ -2706,11 +2719,9 @@ class GuiDocEditSearch(QFrame):
|
|
2706
2719
|
@pyqtSlot()
|
2707
2720
|
def _doSearch(self) -> None:
|
2708
2721
|
"""Call the search action function for the document editor."""
|
2709
|
-
|
2710
|
-
|
2711
|
-
|
2712
|
-
else:
|
2713
|
-
self.docEditor.findNext()
|
2722
|
+
self.docEditor.findNext(goBack=(
|
2723
|
+
qApp.keyboardModifiers() == Qt.KeyboardModifier.ShiftModifier)
|
2724
|
+
)
|
2714
2725
|
return
|
2715
2726
|
|
2716
2727
|
@pyqtSlot()
|
@@ -2801,84 +2812,81 @@ class GuiDocEditHeader(QWidget):
|
|
2801
2812
|
logger.debug("Create: GuiDocEditHeader")
|
2802
2813
|
|
2803
2814
|
self.docEditor = docEditor
|
2804
|
-
self.mainGui = docEditor.mainGui
|
2805
2815
|
|
2806
2816
|
self._docHandle = None
|
2817
|
+
self._docOutline: dict[int, str] = {}
|
2807
2818
|
|
2808
|
-
|
2809
|
-
|
2810
|
-
iconSize = QSize(fPx, fPx)
|
2819
|
+
iPx = SHARED.theme.baseIconSize
|
2820
|
+
mPx = CONFIG.pxInt(4)
|
2811
2821
|
|
2812
2822
|
# Main Widget Settings
|
2813
2823
|
self.setAutoFillBackground(True)
|
2814
2824
|
|
2815
2825
|
# Title Label
|
2816
|
-
self.itemTitle = QLabel()
|
2817
|
-
self.itemTitle.setText("")
|
2826
|
+
self.itemTitle = QLabel("", self)
|
2818
2827
|
self.itemTitle.setIndent(0)
|
2819
2828
|
self.itemTitle.setMargin(0)
|
2820
2829
|
self.itemTitle.setContentsMargins(0, 0, 0, 0)
|
2821
2830
|
self.itemTitle.setAutoFillBackground(True)
|
2822
2831
|
self.itemTitle.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop)
|
2823
|
-
self.itemTitle.setFixedHeight(
|
2832
|
+
self.itemTitle.setFixedHeight(iPx)
|
2824
2833
|
|
2825
2834
|
lblFont = self.itemTitle.font()
|
2826
2835
|
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
2827
2836
|
self.itemTitle.setFont(lblFont)
|
2828
2837
|
|
2838
|
+
# Other Widgets
|
2839
|
+
self.outlineMenu = QMenu(self)
|
2840
|
+
|
2829
2841
|
# 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)
|
2842
|
+
self.tbButton = NIconToolButton(self, iPx)
|
2835
2843
|
self.tbButton.setVisible(False)
|
2836
2844
|
self.tbButton.setToolTip(self.tr("Toggle Tool Bar"))
|
2837
2845
|
self.tbButton.clicked.connect(lambda: self.toggleToolBarRequest.emit())
|
2838
2846
|
|
2839
|
-
self.
|
2840
|
-
self.
|
2841
|
-
self.
|
2842
|
-
self.
|
2843
|
-
|
2847
|
+
self.outlineButton = NIconToolButton(self, iPx)
|
2848
|
+
self.outlineButton.setVisible(False)
|
2849
|
+
self.outlineButton.setToolTip(self.tr("Outline"))
|
2850
|
+
self.outlineButton.setMenu(self.outlineMenu)
|
2851
|
+
|
2852
|
+
self.searchButton = NIconToolButton(self, iPx)
|
2844
2853
|
self.searchButton.setVisible(False)
|
2845
2854
|
self.searchButton.setToolTip(self.tr("Search"))
|
2846
2855
|
self.searchButton.clicked.connect(self.docEditor.toggleSearch)
|
2847
2856
|
|
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)
|
2857
|
+
self.minmaxButton = NIconToolButton(self, iPx)
|
2853
2858
|
self.minmaxButton.setVisible(False)
|
2854
2859
|
self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode"))
|
2855
2860
|
self.minmaxButton.clicked.connect(lambda: self.docEditor.toggleFocusModeRequest.emit())
|
2856
2861
|
|
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)
|
2862
|
+
self.closeButton = NIconToolButton(self, iPx)
|
2862
2863
|
self.closeButton.setVisible(False)
|
2863
2864
|
self.closeButton.setToolTip(self.tr("Close"))
|
2864
2865
|
self.closeButton.clicked.connect(self._closeDocument)
|
2865
2866
|
|
2866
2867
|
# Assemble Layout
|
2867
2868
|
self.outerBox = QHBoxLayout()
|
2868
|
-
self.outerBox.setSpacing(hSp)
|
2869
2869
|
self.outerBox.addWidget(self.tbButton, 0)
|
2870
|
+
self.outerBox.addWidget(self.outlineButton, 0)
|
2870
2871
|
self.outerBox.addWidget(self.searchButton, 0)
|
2872
|
+
self.outerBox.addSpacing(mPx)
|
2871
2873
|
self.outerBox.addWidget(self.itemTitle, 1)
|
2874
|
+
self.outerBox.addSpacing(mPx)
|
2875
|
+
self.outerBox.addSpacing(iPx)
|
2872
2876
|
self.outerBox.addWidget(self.minmaxButton, 0)
|
2873
2877
|
self.outerBox.addWidget(self.closeButton, 0)
|
2878
|
+
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
|
2879
|
+
self.outerBox.setSpacing(0)
|
2880
|
+
|
2874
2881
|
self.setLayout(self.outerBox)
|
2875
2882
|
|
2883
|
+
# Other Signals
|
2884
|
+
SHARED.focusModeChanged.connect(self._focusModeChanged)
|
2885
|
+
|
2876
2886
|
# Fix Margins and Size
|
2877
2887
|
# This is needed for high DPI systems. See issue #499.
|
2878
|
-
cM = CONFIG.pxInt(8)
|
2879
2888
|
self.setContentsMargins(0, 0, 0, 0)
|
2880
|
-
self.
|
2881
|
-
self.setMinimumHeight(fPx + 2*cM)
|
2889
|
+
self.setMinimumHeight(iPx + 2*mPx)
|
2882
2890
|
|
2883
2891
|
self.updateTheme()
|
2884
2892
|
|
@@ -2890,20 +2898,45 @@ class GuiDocEditHeader(QWidget):
|
|
2890
2898
|
# Methods
|
2891
2899
|
##
|
2892
2900
|
|
2901
|
+
def clearHeader(self) -> None:
|
2902
|
+
"""Clear the header."""
|
2903
|
+
self._docHandle = None
|
2904
|
+
self._docOutline = {}
|
2905
|
+
|
2906
|
+
self.itemTitle.setText("")
|
2907
|
+
self.outlineMenu.clear()
|
2908
|
+
self.tbButton.setVisible(False)
|
2909
|
+
self.outlineButton.setVisible(False)
|
2910
|
+
self.searchButton.setVisible(False)
|
2911
|
+
self.closeButton.setVisible(False)
|
2912
|
+
self.minmaxButton.setVisible(False)
|
2913
|
+
return
|
2914
|
+
|
2915
|
+
def setOutline(self, data: dict[int, str]) -> None:
|
2916
|
+
"""Set the document outline dataset."""
|
2917
|
+
if data != self._docOutline:
|
2918
|
+
tStart = time()
|
2919
|
+
self.outlineMenu.clear()
|
2920
|
+
for number, text in data.items():
|
2921
|
+
action = self.outlineMenu.addAction(text)
|
2922
|
+
action.triggered.connect(
|
2923
|
+
lambda _, number=number: self._gotoBlock(number)
|
2924
|
+
)
|
2925
|
+
self._docOutline = data
|
2926
|
+
logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart))
|
2927
|
+
return
|
2928
|
+
|
2893
2929
|
def updateTheme(self) -> None:
|
2894
2930
|
"""Update theme elements."""
|
2895
2931
|
self.tbButton.setIcon(SHARED.theme.getIcon("menu"))
|
2932
|
+
self.outlineButton.setIcon(SHARED.theme.getIcon("list"))
|
2896
2933
|
self.searchButton.setIcon(SHARED.theme.getIcon("search"))
|
2897
2934
|
self.minmaxButton.setIcon(SHARED.theme.getIcon("maximise"))
|
2898
2935
|
self.closeButton.setIcon(SHARED.theme.getIcon("close"))
|
2899
2936
|
|
2900
|
-
|
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())
|
2905
|
-
|
2937
|
+
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
2906
2938
|
self.tbButton.setStyleSheet(buttonStyle)
|
2939
|
+
self.outlineButton.setStyleSheet(buttonStyle)
|
2907
2940
|
self.searchButton.setStyleSheet(buttonStyle)
|
2908
2941
|
self.minmaxButton.setStyleSheet(buttonStyle)
|
2909
2942
|
self.closeButton.setStyleSheet(buttonStyle)
|
@@ -2926,51 +2959,25 @@ class GuiDocEditHeader(QWidget):
|
|
2926
2959
|
|
2927
2960
|
return
|
2928
2961
|
|
2929
|
-
def
|
2962
|
+
def setHandle(self, tHandle: str) -> None:
|
2930
2963
|
"""Set the document title from the handle, or alternatively, set
|
2931
2964
|
the whole document path within the project.
|
2932
2965
|
"""
|
2933
2966
|
self._docHandle = tHandle
|
2934
|
-
|
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
|
-
|
2942
|
-
pTree = SHARED.project.tree
|
2967
|
+
|
2943
2968
|
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))
|
2969
|
+
self.itemTitle.setText(f" {nwUnicode.U_RSAQUO} ".join(reversed(
|
2970
|
+
[name for name in SHARED.project.tree.getItemPath(tHandle, asName=True)]
|
2971
|
+
)))
|
2952
2972
|
else:
|
2953
|
-
|
2954
|
-
if nwItem is None:
|
2955
|
-
return False
|
2956
|
-
self.itemTitle.setText(nwItem.itemName)
|
2973
|
+
self.itemTitle.setText(i.itemName if (i := SHARED.project.tree[tHandle]) else "")
|
2957
2974
|
|
2958
2975
|
self.tbButton.setVisible(True)
|
2959
2976
|
self.searchButton.setVisible(True)
|
2977
|
+
self.outlineButton.setVisible(True)
|
2960
2978
|
self.closeButton.setVisible(True)
|
2961
2979
|
self.minmaxButton.setVisible(True)
|
2962
2980
|
|
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
2981
|
return
|
2975
2982
|
|
2976
2983
|
##
|
@@ -2980,11 +2987,20 @@ class GuiDocEditHeader(QWidget):
|
|
2980
2987
|
@pyqtSlot()
|
2981
2988
|
def _closeDocument(self) -> None:
|
2982
2989
|
"""Trigger the close editor on the main window."""
|
2990
|
+
self.clearHeader()
|
2983
2991
|
self.closeDocumentRequest.emit()
|
2984
|
-
|
2985
|
-
|
2986
|
-
|
2987
|
-
|
2992
|
+
return
|
2993
|
+
|
2994
|
+
@pyqtSlot(int)
|
2995
|
+
def _gotoBlock(self, blockNumber: int) -> None:
|
2996
|
+
"""Move cursor to a specific heading."""
|
2997
|
+
self.docEditor.setCursorLine(blockNumber + 1)
|
2998
|
+
return
|
2999
|
+
|
3000
|
+
@pyqtSlot(bool)
|
3001
|
+
def _focusModeChanged(self, focusMode: bool) -> None:
|
3002
|
+
"""Update minimise/maximise icon of the Focus Mode button."""
|
3003
|
+
self.minmaxButton.setIcon(SHARED.theme.getIcon("minimise" if focusMode else "maximise"))
|
2988
3004
|
return
|
2989
3005
|
|
2990
3006
|
##
|
@@ -2996,7 +3012,7 @@ class GuiDocEditHeader(QWidget):
|
|
2996
3012
|
selected in the project tree.
|
2997
3013
|
"""
|
2998
3014
|
if event.button() == Qt.MouseButton.LeftButton:
|
2999
|
-
self.docEditor.requestProjectItemSelected.emit(self._docHandle, True)
|
3015
|
+
self.docEditor.requestProjectItemSelected.emit(self._docHandle or "", True)
|
3000
3016
|
return
|
3001
3017
|
|
3002
3018
|
# END Class GuiDocEditHeader
|
@@ -3009,26 +3025,28 @@ class GuiDocEditHeader(QWidget):
|
|
3009
3025
|
|
3010
3026
|
class GuiDocEditFooter(QWidget):
|
3011
3027
|
|
3012
|
-
def __init__(self,
|
3013
|
-
super().__init__(parent=
|
3028
|
+
def __init__(self, parent: QWidget) -> None:
|
3029
|
+
super().__init__(parent=parent)
|
3014
3030
|
|
3015
3031
|
logger.debug("Create: GuiDocEditFooter")
|
3016
3032
|
|
3017
|
-
self.
|
3018
|
-
|
3019
|
-
self._tItem = None
|
3033
|
+
self._tItem = None
|
3020
3034
|
self._docHandle = None
|
3021
3035
|
|
3022
|
-
|
3023
|
-
|
3024
|
-
self.sPx = int(round(0.9*SHARED.theme.baseIconSize))
|
3036
|
+
iPx = round(0.9*SHARED.theme.baseIconSize)
|
3025
3037
|
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
3038
|
+
mPx = CONFIG.pxInt(8)
|
3026
3039
|
bSp = CONFIG.pxInt(4)
|
3027
3040
|
hSp = CONFIG.pxInt(6)
|
3028
3041
|
|
3029
3042
|
lblFont = self.font()
|
3030
3043
|
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
3031
3044
|
|
3045
|
+
# Cached Translations
|
3046
|
+
self._trLineCount = self.tr("Line: {0} ({1})")
|
3047
|
+
self._trWordCount = self.tr("Words: {0} ({1})")
|
3048
|
+
self._trSelectCount = self.tr("Words: {0} selected")
|
3049
|
+
|
3032
3050
|
# Main Widget Settings
|
3033
3051
|
self.setContentsMargins(0, 0, 0, 0)
|
3034
3052
|
self.setAutoFillBackground(True)
|
@@ -3036,9 +3054,9 @@ class GuiDocEditFooter(QWidget):
|
|
3036
3054
|
alLeftTop = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
|
3037
3055
|
|
3038
3056
|
# Status
|
3039
|
-
self.statusIcon = QLabel("")
|
3057
|
+
self.statusIcon = QLabel("", self)
|
3040
3058
|
self.statusIcon.setContentsMargins(0, 0, 0, 0)
|
3041
|
-
self.statusIcon.setFixedHeight(
|
3059
|
+
self.statusIcon.setFixedHeight(iPx)
|
3042
3060
|
self.statusIcon.setAlignment(alLeftTop)
|
3043
3061
|
|
3044
3062
|
self.statusText = QLabel(self.tr("Status"))
|
@@ -3051,12 +3069,12 @@ class GuiDocEditFooter(QWidget):
|
|
3051
3069
|
self.statusText.setFont(lblFont)
|
3052
3070
|
|
3053
3071
|
# Lines
|
3054
|
-
self.linesIcon = QLabel("")
|
3072
|
+
self.linesIcon = QLabel("", self)
|
3055
3073
|
self.linesIcon.setContentsMargins(0, 0, 0, 0)
|
3056
|
-
self.linesIcon.setFixedHeight(
|
3074
|
+
self.linesIcon.setFixedHeight(iPx)
|
3057
3075
|
self.linesIcon.setAlignment(alLeftTop)
|
3058
3076
|
|
3059
|
-
self.linesText = QLabel("")
|
3077
|
+
self.linesText = QLabel("", self)
|
3060
3078
|
self.linesText.setIndent(0)
|
3061
3079
|
self.linesText.setMargin(0)
|
3062
3080
|
self.linesText.setContentsMargins(0, 0, 0, 0)
|
@@ -3066,12 +3084,12 @@ class GuiDocEditFooter(QWidget):
|
|
3066
3084
|
self.linesText.setFont(lblFont)
|
3067
3085
|
|
3068
3086
|
# Words
|
3069
|
-
self.wordsIcon = QLabel("")
|
3087
|
+
self.wordsIcon = QLabel("", self)
|
3070
3088
|
self.wordsIcon.setContentsMargins(0, 0, 0, 0)
|
3071
|
-
self.wordsIcon.setFixedHeight(
|
3089
|
+
self.wordsIcon.setFixedHeight(iPx)
|
3072
3090
|
self.wordsIcon.setAlignment(alLeftTop)
|
3073
3091
|
|
3074
|
-
self.wordsText = QLabel("")
|
3092
|
+
self.wordsText = QLabel("", self)
|
3075
3093
|
self.wordsText.setIndent(0)
|
3076
3094
|
self.wordsText.setMargin(0)
|
3077
3095
|
self.wordsText.setContentsMargins(0, 0, 0, 0)
|
@@ -3091,19 +3109,20 @@ class GuiDocEditFooter(QWidget):
|
|
3091
3109
|
self.outerBox.addSpacing(hSp)
|
3092
3110
|
self.outerBox.addWidget(self.wordsIcon)
|
3093
3111
|
self.outerBox.addWidget(self.wordsText)
|
3112
|
+
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
|
3113
|
+
|
3094
3114
|
self.setLayout(self.outerBox)
|
3095
3115
|
|
3096
3116
|
# Fix Margins and Size
|
3097
3117
|
# This is needed for high DPI systems. See issue #499.
|
3098
|
-
cM = CONFIG.pxInt(8)
|
3099
3118
|
self.setContentsMargins(0, 0, 0, 0)
|
3100
|
-
self.
|
3101
|
-
self.setMinimumHeight(fPx + 2*cM)
|
3119
|
+
self.setMinimumHeight(fPx + 2*mPx)
|
3102
3120
|
|
3103
3121
|
# Fix the Colours
|
3104
3122
|
self.updateTheme()
|
3105
|
-
|
3106
|
-
|
3123
|
+
|
3124
|
+
# Initialise Info
|
3125
|
+
self.updateWordCount(0, False)
|
3107
3126
|
|
3108
3127
|
logger.debug("Ready: GuiDocEditFooter")
|
3109
3128
|
|
@@ -3115,8 +3134,9 @@ class GuiDocEditFooter(QWidget):
|
|
3115
3134
|
|
3116
3135
|
def updateTheme(self) -> None:
|
3117
3136
|
"""Update theme elements."""
|
3118
|
-
|
3119
|
-
self.
|
3137
|
+
iPx = round(0.9*SHARED.theme.baseIconSize)
|
3138
|
+
self.linesIcon.setPixmap(SHARED.theme.getPixmap("status_lines", (iPx, iPx)))
|
3139
|
+
self.wordsIcon.setPixmap(SHARED.theme.getPixmap("status_stats", (iPx, iPx)))
|
3120
3140
|
self.matchColours()
|
3121
3141
|
return
|
3122
3142
|
|
@@ -3145,27 +3165,20 @@ class GuiDocEditFooter(QWidget):
|
|
3145
3165
|
else:
|
3146
3166
|
self._tItem = SHARED.project.tree[self._docHandle]
|
3147
3167
|
|
3148
|
-
self.setHasSelection(False)
|
3149
3168
|
self.updateInfo()
|
3150
|
-
self.
|
3169
|
+
self.updateWordCount(0, False)
|
3151
3170
|
|
3152
3171
|
return
|
3153
3172
|
|
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
3173
|
def updateInfo(self) -> None:
|
3162
3174
|
"""Update the content of text labels."""
|
3163
3175
|
if self._tItem is None:
|
3164
3176
|
sIcon = QPixmap()
|
3165
3177
|
sText = ""
|
3166
3178
|
else:
|
3179
|
+
iPx = round(0.9*SHARED.theme.baseIconSize)
|
3167
3180
|
status, icon = self._tItem.getImportStatus(incIcon=True)
|
3168
|
-
sIcon = icon.pixmap(
|
3181
|
+
sIcon = icon.pixmap(iPx, iPx)
|
3169
3182
|
sText = f"{status} / {self._tItem.describeMe()}"
|
3170
3183
|
|
3171
3184
|
self.statusIcon.setPixmap(sIcon)
|
@@ -3173,62 +3186,27 @@ class GuiDocEditFooter(QWidget):
|
|
3173
3186
|
|
3174
3187
|
return
|
3175
3188
|
|
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()
|
3189
|
+
def updateLineCount(self, cursor: QTextCursor) -> None:
|
3190
|
+
"""Update the line and document position counter."""
|
3191
|
+
cPos = cursor.position() + 1
|
3192
|
+
cLine = cursor.blockNumber() + 1
|
3193
|
+
cCount = max(cursor.document().characterCount(), 1)
|
3185
3194
|
self.linesText.setText(
|
3186
|
-
self.
|
3195
|
+
self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %")
|
3187
3196
|
)
|
3188
3197
|
return
|
3189
3198
|
|
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:
|
3199
|
+
def updateWordCount(self, wCount: int, selection: bool) -> None:
|
3200
|
+
"""Update word counter information."""
|
3201
|
+
if selection and wCount:
|
3202
|
+
wText = self._trSelectCount.format(f"{wCount:n}")
|
3203
|
+
elif self._tItem:
|
3208
3204
|
wCount = self._tItem.wordCount
|
3209
3205
|
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
|
-
)
|
3206
|
+
wText = self._trWordCount.format(f"{wCount:n}", f"{wDiff:+n}")
|
3207
|
+
else:
|
3208
|
+
wText = self._trWordCount.format("0", "+0")
|
3209
|
+
self.wordsText.setText(wText)
|
3232
3210
|
return
|
3233
3211
|
|
3234
3212
|
# END Class GuiDocEditFooter
|