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.
Files changed (107) hide show
  1. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/METADATA +5 -6
  2. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/RECORD +102 -95
  3. novelwriter/__init__.py +7 -7
  4. novelwriter/assets/icons/none.svg +4 -0
  5. novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
  6. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
  7. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
  8. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
  9. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
  10. novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
  11. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
  12. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
  13. novelwriter/assets/icons/typicons_light/icons.conf +4 -0
  14. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
  15. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
  16. novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
  17. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
  18. novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
  19. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
  20. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
  21. novelwriter/assets/manual.pdf +0 -0
  22. novelwriter/assets/sample.zip +0 -0
  23. novelwriter/assets/syntax/default_dark.conf +1 -0
  24. novelwriter/assets/syntax/default_light.conf +1 -0
  25. novelwriter/assets/syntax/grey_dark.conf +1 -0
  26. novelwriter/assets/syntax/grey_light.conf +1 -0
  27. novelwriter/assets/syntax/light_owl.conf +1 -0
  28. novelwriter/assets/syntax/night_owl.conf +1 -0
  29. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  30. novelwriter/assets/syntax/solarized_light.conf +1 -0
  31. novelwriter/assets/syntax/tomorrow.conf +1 -0
  32. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  33. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  34. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  35. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  36. novelwriter/assets/text/credits_en.htm +25 -23
  37. novelwriter/common.py +7 -2
  38. novelwriter/config.py +43 -16
  39. novelwriter/constants.py +5 -6
  40. novelwriter/core/buildsettings.py +60 -40
  41. novelwriter/core/coretools.py +97 -13
  42. novelwriter/core/docbuild.py +74 -7
  43. novelwriter/core/document.py +24 -3
  44. novelwriter/core/index.py +31 -112
  45. novelwriter/core/project.py +10 -15
  46. novelwriter/core/sessions.py +2 -2
  47. novelwriter/core/status.py +6 -5
  48. novelwriter/core/storage.py +8 -2
  49. novelwriter/core/tohtml.py +22 -25
  50. novelwriter/core/tokenizer.py +416 -232
  51. novelwriter/core/tomd.py +17 -8
  52. novelwriter/core/toodt.py +385 -350
  53. novelwriter/core/tree.py +8 -8
  54. novelwriter/dialogs/about.py +9 -11
  55. novelwriter/dialogs/docmerge.py +17 -14
  56. novelwriter/dialogs/docsplit.py +20 -19
  57. novelwriter/dialogs/editlabel.py +5 -4
  58. novelwriter/dialogs/preferences.py +31 -39
  59. novelwriter/dialogs/projectsettings.py +29 -26
  60. novelwriter/dialogs/quotes.py +10 -9
  61. novelwriter/dialogs/wordlist.py +15 -12
  62. novelwriter/enum.py +17 -14
  63. novelwriter/error.py +13 -11
  64. novelwriter/extensions/circularprogress.py +12 -8
  65. novelwriter/extensions/configlayout.py +1 -3
  66. novelwriter/extensions/modified.py +51 -2
  67. novelwriter/extensions/pagedsidebar.py +16 -14
  68. novelwriter/extensions/simpleprogress.py +3 -1
  69. novelwriter/extensions/statusled.py +3 -1
  70. novelwriter/extensions/switch.py +10 -9
  71. novelwriter/extensions/switchbox.py +14 -13
  72. novelwriter/extensions/versioninfo.py +1 -1
  73. novelwriter/gui/doceditor.py +413 -478
  74. novelwriter/gui/dochighlight.py +33 -29
  75. novelwriter/gui/docviewer.py +162 -175
  76. novelwriter/gui/docviewerpanel.py +20 -37
  77. novelwriter/gui/editordocument.py +15 -4
  78. novelwriter/gui/itemdetails.py +51 -54
  79. novelwriter/gui/mainmenu.py +37 -16
  80. novelwriter/gui/noveltree.py +30 -36
  81. novelwriter/gui/outline.py +114 -92
  82. novelwriter/gui/projtree.py +60 -66
  83. novelwriter/gui/search.py +362 -0
  84. novelwriter/gui/sidebar.py +36 -45
  85. novelwriter/gui/statusbar.py +14 -14
  86. novelwriter/gui/theme.py +93 -28
  87. novelwriter/guimain.py +207 -200
  88. novelwriter/shared.py +31 -6
  89. novelwriter/text/counting.py +137 -0
  90. novelwriter/tools/dictionaries.py +13 -12
  91. novelwriter/tools/lipsum.py +20 -17
  92. novelwriter/tools/manusbuild.py +35 -27
  93. novelwriter/tools/manuscript.py +374 -90
  94. novelwriter/tools/manussettings.py +261 -124
  95. novelwriter/tools/noveldetails.py +20 -18
  96. novelwriter/tools/welcome.py +48 -44
  97. novelwriter/tools/writingstats.py +61 -55
  98. novelwriter/types.py +90 -0
  99. novelwriter/core/__init__.py +0 -3
  100. novelwriter/dialogs/__init__.py +0 -3
  101. novelwriter/extensions/__init__.py +0 -3
  102. novelwriter/gui/__init__.py +0 -3
  103. novelwriter/tools/__init__.py +0 -3
  104. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/LICENSE.md +0 -0
  105. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/WHEEL +0 -0
  106. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/entry_points.txt +0 -0
  107. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/top_level.txt +0 -0
@@ -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, QSize, Qt, QTimer
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, QMenu,
51
- QPlainTextEdit, QPushButton, QShortcut, QToolBar, QToolButton, QVBoxLayout,
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 nwKeyWords, nwLabels, nwShortcode, nwUnicode, trConst
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.gui.dochighlight import GuiDocHighlighter
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 | Qt.KeyboardModifier.ControlModifier)
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 | Qt.KeyboardModifier.ControlModifier)
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.wcTimerDoc = QTimer()
188
- self.wcTimerDoc.timeout.connect(self._runDocCounter)
189
- self.wcTimerDoc.setInterval(5000)
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.wcTimerSel = QTimer()
197
- self.wcTimerSel.timeout.connect(self._runSelCounter)
198
- self.wcTimerSel.setInterval(500)
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.wcTimerDoc.stop()
252
- self.wcTimerSel.stop()
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.setTitleFromHandle(self._docHandle)
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(Qt.AlignmentFlag.AlignJustify)
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.setTitleFromHandle(self._docHandle)
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
- qApp.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
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
- qApp.processEvents()
408
+ QApplication.processEvents()
396
409
 
397
410
  self._lastEdit = time()
398
411
  self._lastActive = time()
399
- self._runDocCounter()
400
- self.wcTimerDoc.start()
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.docFooter.updateLineCount()
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
- qApp.processEvents()
432
+ QApplication.processEvents()
421
433
  self.setDocumentChanged(False)
422
434
  self._qDocument.clearUndoRedoStacks()
423
435
  self.docToolBar.setVisible(CONFIG.showEditToolBar)
424
436
 
425
- qApp.restoreOverrideCursor()
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(GuiDocHighlighter.BLOCK_META)
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
- qApp.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
454
+ QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
443
455
  self.setPlainText(text)
444
456
  self.updateDocMargins()
445
457
  self.setDocumentChanged(True)
446
- qApp.restoreOverrideCursor()
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 = countWords(docText)
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
- qApp.processEvents()
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 self.mainGui.isFocusMode:
552
- tW = CONFIG.getTextWidth(self.mainGui.isFocusMode)
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) -> 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 = state
608
- self.editedStatusChanged.emit(self._docChanged)
609
- return self._docChanged
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
- qApp.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
708
+ QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
678
709
  self._qDocument.syntaxHighlighter.rehighlight()
679
- qApp.restoreOverrideCursor()
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
- if self.hasFocus():
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 (Qt.KeyboardModifier.NoModifier, Qt.KeyboardModifier.ShiftModifier)
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(next)
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 qApp.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier:
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.setTitleFromHandle(self._docHandle)
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.docSearch.isVisible():
1016
- self.docSearch.closeSearch()
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.wcTimerDoc.isActive():
1037
- self.wcTimerDoc.start()
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
- text = block.text()
1044
- if text.startswith("@") and added + removed == 1:
1045
- # Only run on single keypresses, otherwise it will trigger
1046
- # at unwanted times when other changes are made to the document
1047
- cursor = self.textCursor()
1048
- bPos = cursor.positionInBlock()
1049
- if bPos > 0:
1050
- show = self._completer.updateText(text, bPos)
1051
- point = self.cursorRect().bottomRight()
1052
- self._completer.move(self.viewport().mapToGlobal(point))
1053
- self._completer.setVisible(show)
1054
- else:
1055
- self._completer.setVisible(False)
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, QTextCursor.MoveMode.MoveAnchor)
1070
- cursor.setPosition(pos + length, QTextCursor.MoveMode.KeepAnchor)
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() == GuiDocHighlighter.BLOCK_TITLE:
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.exec_(self.viewport().mapToGlobal(pos))
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 _runDocCounter(self) -> None:
1180
- """Decide whether to run the word counter, or not due to
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 word counter")
1192
- SHARED.runInThreadPool(self.wCounterDoc)
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 is None or self._nwItem is None:
1200
- return
1201
-
1202
- logger.debug("Updating word count")
1203
-
1204
- self._nwItem.setCharCount(cCount)
1205
- self._nwItem.setWordCount(wCount)
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.wcTimerSel.isActive():
1221
- self.wcTimerSel.start()
1222
- self.docFooter.setHasSelection(True)
1241
+ if not self.timerSel.isActive():
1242
+ self.timerSel.start()
1223
1243
  else:
1224
- self.wcTimerSel.stop()
1225
- self.docFooter.setHasSelection(False)
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 is None or self._nwItem is None:
1247
- return
1248
-
1249
- logger.debug("User selected %d words", wCount)
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
- cursor = self.textCursor()
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 self.docSearch.doNextFile and not goBack:
1321
+ if CONFIG.searchNextFile and not goBack:
1310
1322
  self.mainGui.openNextDocument(
1311
- self._docHandle, wrapAround=self.docSearch.doLoop
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 = self.docSearch.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 self.docSearch.doNextFile and not goBack:
1342
+ if CONFIG.searchNextFile and not goBack:
1330
1343
  self.mainGui.openNextDocument(
1331
- self._docHandle, wrapAround=self.docSearch.doLoop
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], QTextCursor.MoveMode.MoveAnchor)
1339
- cursor.setPosition(resE[resIdx], QTextCursor.MoveMode.KeepAnchor)
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 self.docSearch.isCaseSense:
1377
+ if CONFIG.searchCase:
1365
1378
  findOpt |= QTextDocument.FindFlag.FindCaseSensitively
1366
- if self.docSearch.isWholeWord:
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 1000, and make sure certain special
1374
- # searches like a regex search for .* don't loop infinitely
1375
- while self.find(searchFor, findOpt) and len(resE) <= 1000:
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, QTextCursor.MoveMode.MoveAnchor)
1386
- cursor.setPosition(origB, QTextCursor.MoveMode.KeepAnchor)
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 self.docSearch.doMatchCap:
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
- try:
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, QTextCursor.MoveMode.MoveAnchor)
1498
- cursor.setPosition(posE, QTextCursor.MoveMode.KeepAnchor)
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), QTextCursor.MoveMode.MoveAnchor)
1576
- cursor.setPosition(posS + len(before), QTextCursor.MoveMode.KeepAnchor)
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 not isGood or not tBits or tBits[0] == nwKeyWords.TAG_KEY:
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
- if SHARED.mainGui.projView.createNewNote(tag, itemClass):
1899
- self._qDocument.syntaxHighlighter.rehighlightBlock(block)
1900
- else:
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, QTextCursor.MoveMode.MoveAnchor)
2067
- cursor.setPosition(ePos, QTextCursor.MoveMode.KeepAnchor)
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, QTextCursor.MoveMode.MoveAnchor)
2092
- cursor.setPosition(posE, QTextCursor.MoveMode.KeepAnchor)
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.textCursor().selectedText()
2224
+ text = self._docEditor.getSelectedText()
2224
2225
  else:
2225
2226
  text = self._docEditor.getText()
2226
2227
 
2227
- cC, wC, pC = countWords(text)
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 = QToolButton(self)
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 = QToolButton(self)
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 = QToolButton(self)
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 = QToolButton(self)
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 = QToolButton(self)
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 = QToolButton(self)
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 = QToolButton(self)
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.tbSuperscript = QToolButton(self)
2317
- self.tbSuperscript.setIconSize(iconSize)
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 = QToolButton(self)
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.setIcon(SHARED.theme.getIcon("fmt_bold-md"))
2366
- self.tbItalicMD.setIcon(SHARED.theme.getIcon("fmt_italic-md"))
2367
- self.tbStrikeMD.setIcon(SHARED.theme.getIcon("fmt_strike-md"))
2368
- self.tbBold.setIcon(SHARED.theme.getIcon("fmt_bold"))
2369
- self.tbItalic.setIcon(SHARED.theme.getIcon("fmt_italic"))
2370
- self.tbStrike.setIcon(SHARED.theme.getIcon("fmt_strike"))
2371
- self.tbUnderline.setIcon(SHARED.theme.getIcon("fmt_underline"))
2372
- self.tbSuperscript.setIcon(SHARED.theme.getIcon("fmt_superscript"))
2373
- self.tbSubscript.setIcon(SHARED.theme.getIcon("fmt_subscript"))
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
- self.repVisible = False
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
- tPx = int(0.8*SHARED.theme.fontPixelSize)
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(QSize(tPx, tPx))
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(self.isCaseSense)
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(self.isWholeWord)
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(self.isRegEx)
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(self.doLoop)
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(self.doNextFile)
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(self.doMatchCap)
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._doClose)
2473
+ self.cancelSearch.triggered.connect(self.closeSearch)
2482
2474
  self.searchOpt.addAction(self.cancelSearch)
2483
2475
 
2484
2476
  # Buttons
2485
2477
  # =======
2486
2478
 
2487
- bPx = self.searchBox.sizeHint().height()
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 = QPushButton("")
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 = QPushButton("")
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, Qt.AlignmentFlag.AlignLeft)
2505
- self.mainBox.addWidget(self.searchOpt, 0, 2, 1, 3, Qt.AlignmentFlag.AlignRight)
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 self.isRegEx:
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 self.isCaseSense:
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 self.isCaseSense:
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 self.isRegEx:
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 "1000+" if resCount > 1000 else str(resCount)
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 = qApp.palette()
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.setIcon(SHARED.theme.getIcon("search"))
2637
- self.replaceButton.setIcon(SHARED.theme.getIcon("search_replace"))
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 closeSearch(self) -> None:
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.replaceBox.isVisible():
2684
- if self.searchBox.hasFocus():
2685
- self.replaceBox.setFocus()
2686
- return True
2687
- elif self.replaceBox.hasFocus():
2688
- self.searchBox.setFocus()
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() | self.replaceBox.hasFocus()
2665
+ return self.searchBox.hasFocus() or self.replaceBox.hasFocus()
2695
2666
 
2696
2667
  ##
2697
- # Private Slots
2668
+ # Public Slots
2698
2669
  ##
2699
2670
 
2700
2671
  @pyqtSlot()
2701
- def _doClose(self) -> None:
2702
- """Hide the search/replace bar."""
2703
- self.closeSearch()
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
- modKey = qApp.keyboardModifiers()
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
- self.isCaseSense = state
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
- self.isWholeWord = state
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
- self.isRegEx = state
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
- self.doLoop = state
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
- self.doNextFile = state
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
- self.doMatchCap = state
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
- fPx = int(0.9*SHARED.theme.fontPixelSize)
2809
- hSp = CONFIG.pxInt(6)
2810
- iconSize = QSize(fPx, fPx)
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(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop)
2823
- self.itemTitle.setFixedHeight(fPx)
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 = QToolButton(self)
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.searchButton = QToolButton(self)
2840
- self.searchButton.setContentsMargins(0, 0, 0, 0)
2841
- self.searchButton.setIconSize(iconSize)
2842
- self.searchButton.setFixedSize(fPx, fPx)
2843
- self.searchButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
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 = QToolButton(self)
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 = QToolButton(self)
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.outerBox.setContentsMargins(cM, cM, cM, cM)
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.setIcon(SHARED.theme.getIcon("menu"))
2896
- self.searchButton.setIcon(SHARED.theme.getIcon("search"))
2897
- self.minmaxButton.setIcon(SHARED.theme.getIcon("maximise"))
2898
- self.closeButton.setIcon(SHARED.theme.getIcon("close"))
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 setTitleFromHandle(self, tHandle: str | None) -> bool:
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
- tTitle = []
2945
- tTree = pTree.getItemPath(tHandle)
2946
- for aHandle in reversed(tTree):
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
- nwItem = pTree[tHandle]
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
- self.tbButton.setVisible(False)
2985
- self.searchButton.setVisible(False)
2986
- self.closeButton.setVisible(False)
2987
- self.minmaxButton.setVisible(False)
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() == Qt.MouseButton.LeftButton:
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, docEditor: GuiDocEditor) -> None:
3013
- super().__init__(parent=docEditor)
2987
+ def __init__(self, parent: QWidget) -> None:
2988
+ super().__init__(parent=parent)
3014
2989
 
3015
2990
  logger.debug("Create: GuiDocEditFooter")
3016
2991
 
3017
- self.docEditor = docEditor
3018
-
3019
- self._tItem = None
2992
+ self._tItem = None
3020
2993
  self._docHandle = None
3021
2994
 
3022
- self._docSelection = False
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(self.sPx)
3042
- self.statusIcon.setAlignment(alLeftTop)
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(alLeftTop)
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(self.sPx)
3057
- self.linesIcon.setAlignment(alLeftTop)
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(alLeftTop)
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(self.sPx)
3072
- self.wordsIcon.setAlignment(alLeftTop)
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(alLeftTop)
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.outerBox.setContentsMargins(cM, cM, cM, cM)
3101
- self.setMinimumHeight(fPx + 2*cM)
3076
+ self.setMinimumHeight(fPx + 2*mPx)
3102
3077
 
3103
3078
  # Fix the Colours
3104
3079
  self.updateTheme()
3105
- self.updateLineCount()
3106
- self.updateCounts()
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
- self.linesIcon.setPixmap(SHARED.theme.getPixmap("status_lines", (self.sPx, self.sPx)))
3119
- self.wordsIcon.setPixmap(SHARED.theme.getPixmap("status_stats", (self.sPx, self.sPx)))
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.updateCounts()
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(self.sPx, self.sPx)
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
- if self._tItem is None:
3179
- iLine = 0
3180
- iDist = 0
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.tr("Line: {0} ({1})").format(f"{iLine:n}", f"{iDist:.0f} %")
3152
+ self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %")
3187
3153
  )
3188
3154
  return
3189
3155
 
3190
- def updateCounts(self, wCount: int | None = None, cCount: int | None = None) -> None:
3191
- """Select which word count display mode to use."""
3192
- if self._docSelection:
3193
- self._updateSelectionWordCounts(wCount, cCount)
3194
- else:
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
- self.wordsText.setText(
3212
- self.tr("Words: {0} ({1})").format(f"{wCount:n}", f"{wDiff:+n}")
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