novelWriter 2.7.4__py3-none-any.whl → 2.8b1__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 (196) hide show
  1. novelwriter/__init__.py +8 -7
  2. novelwriter/assets/icons/font_awesome.icons +22 -4
  3. novelwriter/assets/icons/material_filled_normal.icons +20 -2
  4. novelwriter/assets/icons/material_filled_thin.icons +20 -2
  5. novelwriter/assets/icons/material_rounded_normal.icons +20 -2
  6. novelwriter/assets/icons/material_rounded_thin.icons +20 -2
  7. novelwriter/assets/icons/material_sharp_normal.icons +20 -2
  8. novelwriter/assets/icons/material_sharp_thin.icons +20 -2
  9. novelwriter/assets/icons/remix_filled.icons +20 -2
  10. novelwriter/assets/icons/remix_outline.icons +20 -2
  11. novelwriter/assets/images/welcome.webp +0 -0
  12. novelwriter/assets/manual.pdf +0 -0
  13. novelwriter/assets/manual_fr.pdf +0 -0
  14. novelwriter/assets/sample.zip +0 -0
  15. novelwriter/assets/text/credits_en.htm +61 -11
  16. novelwriter/assets/themes/aura.conf +97 -0
  17. novelwriter/assets/themes/aura_bright.conf +95 -0
  18. novelwriter/assets/themes/aura_soft.conf +97 -0
  19. novelwriter/assets/themes/b2t_garden_dark.conf +97 -0
  20. novelwriter/assets/themes/b2t_garden_light.conf +97 -0
  21. novelwriter/assets/themes/b2t_suburb_dark.conf +97 -0
  22. novelwriter/assets/themes/b2t_suburb_light.conf +97 -0
  23. novelwriter/assets/themes/b4t_classic_o_dark.conf +97 -0
  24. novelwriter/assets/themes/b4t_classic_o_light.conf +97 -0
  25. novelwriter/assets/themes/b4t_modern_c_dark.conf +97 -0
  26. novelwriter/assets/themes/b4t_modern_c_light.conf +97 -0
  27. novelwriter/assets/themes/blue_streak_dark.conf +97 -0
  28. novelwriter/assets/themes/blue_streak_light.conf +97 -0
  29. novelwriter/assets/themes/castle_day.conf +95 -0
  30. novelwriter/assets/themes/castle_night.conf +95 -0
  31. novelwriter/assets/themes/catppuccin_latte.conf +97 -0
  32. novelwriter/assets/themes/catppuccin_mocha.conf +97 -0
  33. novelwriter/assets/themes/chalky_soil.conf +95 -0
  34. novelwriter/assets/themes/chernozem.conf +95 -0
  35. novelwriter/assets/themes/cyberpunk_night.conf +88 -40
  36. novelwriter/assets/themes/default_dark.conf +89 -41
  37. novelwriter/assets/themes/default_light.conf +89 -41
  38. novelwriter/assets/themes/dracula.conf +91 -42
  39. novelwriter/assets/themes/espresso.conf +97 -0
  40. novelwriter/assets/themes/everforest_dark.conf +97 -0
  41. novelwriter/assets/themes/everforest_light.conf +97 -0
  42. novelwriter/assets/themes/floral_daydream.conf +95 -0
  43. novelwriter/assets/themes/floral_midnight.conf +95 -0
  44. novelwriter/assets/themes/full_moon.conf +95 -0
  45. novelwriter/assets/themes/grey_dark.conf +97 -0
  46. novelwriter/assets/themes/grey_light.conf +97 -0
  47. novelwriter/assets/themes/horizon_dark.conf +97 -0
  48. novelwriter/assets/themes/horizon_light.conf +97 -0
  49. novelwriter/assets/themes/jewel_case_dark.conf +95 -0
  50. novelwriter/assets/themes/jewel_case_light.conf +95 -0
  51. novelwriter/assets/themes/lcars.conf +97 -0
  52. novelwriter/assets/themes/light_owl.conf +117 -0
  53. novelwriter/assets/themes/new_moon.conf +97 -0
  54. novelwriter/assets/themes/night_owl.conf +117 -0
  55. novelwriter/assets/themes/noctis.conf +129 -0
  56. novelwriter/assets/themes/noctis_lux.conf +129 -0
  57. novelwriter/assets/themes/nord.conf +97 -0
  58. novelwriter/assets/themes/nordlicht.conf +95 -0
  59. novelwriter/assets/themes/otium_dark.conf +95 -0
  60. novelwriter/assets/themes/otium_light.conf +95 -0
  61. novelwriter/assets/themes/paragon.conf +96 -0
  62. novelwriter/assets/themes/primer_light.conf +97 -0
  63. novelwriter/assets/themes/primer_night.conf +97 -0
  64. novelwriter/assets/themes/rose_pine.conf +97 -0
  65. novelwriter/assets/themes/rose_pine_dawn.conf +97 -0
  66. novelwriter/assets/themes/ruby_day.conf +95 -0
  67. novelwriter/assets/themes/ruby_night.conf +95 -0
  68. novelwriter/assets/themes/selenium_dark.conf +95 -0
  69. novelwriter/assets/themes/selenium_light.conf +95 -0
  70. novelwriter/assets/themes/sepia_dark.conf +95 -0
  71. novelwriter/assets/themes/sepia_light.conf +95 -0
  72. novelwriter/assets/themes/snazzy.conf +102 -40
  73. novelwriter/assets/themes/solarized_dark.conf +108 -40
  74. novelwriter/assets/themes/solarized_light.conf +108 -40
  75. novelwriter/assets/themes/sultana_light.conf +95 -0
  76. novelwriter/assets/themes/sultana_night.conf +95 -0
  77. novelwriter/assets/themes/tango_dark.conf +111 -0
  78. novelwriter/assets/themes/tango_light.conf +111 -0
  79. novelwriter/assets/themes/tomorrow.conf +117 -0
  80. novelwriter/assets/themes/tomorrow_night.conf +117 -0
  81. novelwriter/assets/themes/tomorrow_night_blue.conf +117 -0
  82. novelwriter/assets/themes/tomorrow_night_bright.conf +117 -0
  83. novelwriter/assets/themes/tomorrow_night_eighties.conf +117 -0
  84. novelwriter/assets/themes/vivid_black_green.conf +97 -0
  85. novelwriter/assets/themes/vivid_black_red.conf +97 -0
  86. novelwriter/assets/themes/vivid_white_green.conf +97 -0
  87. novelwriter/assets/themes/vivid_white_red.conf +97 -0
  88. novelwriter/assets/themes/warpgate.conf +96 -0
  89. novelwriter/assets/themes/waterlily_dark.conf +95 -0
  90. novelwriter/assets/themes/waterlily_light.conf +95 -0
  91. novelwriter/common.py +47 -17
  92. novelwriter/config.py +57 -62
  93. novelwriter/constants.py +32 -6
  94. novelwriter/core/buildsettings.py +3 -23
  95. novelwriter/core/coretools.py +21 -25
  96. novelwriter/core/docbuild.py +4 -9
  97. novelwriter/core/document.py +2 -6
  98. novelwriter/core/index.py +33 -53
  99. novelwriter/core/indexdata.py +17 -22
  100. novelwriter/core/item.py +11 -35
  101. novelwriter/core/itemmodel.py +5 -21
  102. novelwriter/core/novelmodel.py +3 -7
  103. novelwriter/core/options.py +3 -4
  104. novelwriter/core/project.py +31 -21
  105. novelwriter/core/projectdata.py +2 -21
  106. novelwriter/core/projectxml.py +13 -21
  107. novelwriter/core/sessions.py +2 -4
  108. novelwriter/core/spellcheck.py +12 -13
  109. novelwriter/core/status.py +27 -20
  110. novelwriter/core/storage.py +5 -10
  111. novelwriter/core/tree.py +6 -15
  112. novelwriter/dialogs/about.py +9 -10
  113. novelwriter/dialogs/docmerge.py +17 -14
  114. novelwriter/dialogs/docsplit.py +18 -14
  115. novelwriter/dialogs/editlabel.py +15 -9
  116. novelwriter/dialogs/preferences.py +69 -68
  117. novelwriter/dialogs/projectsettings.py +88 -67
  118. novelwriter/dialogs/quotes.py +15 -10
  119. novelwriter/dialogs/wordlist.py +18 -21
  120. novelwriter/enum.py +75 -30
  121. novelwriter/error.py +6 -11
  122. novelwriter/extensions/configlayout.py +8 -34
  123. novelwriter/extensions/eventfilters.py +3 -3
  124. novelwriter/extensions/modified.py +87 -32
  125. novelwriter/extensions/novelselector.py +13 -12
  126. novelwriter/extensions/pagedsidebar.py +10 -18
  127. novelwriter/extensions/progressbars.py +5 -11
  128. novelwriter/extensions/statusled.py +3 -6
  129. novelwriter/extensions/switch.py +8 -11
  130. novelwriter/extensions/switchbox.py +2 -11
  131. novelwriter/extensions/versioninfo.py +6 -7
  132. novelwriter/formats/shared.py +10 -2
  133. novelwriter/formats/todocx.py +15 -37
  134. novelwriter/formats/tohtml.py +52 -61
  135. novelwriter/formats/tokenizer.py +33 -64
  136. novelwriter/formats/tomarkdown.py +4 -11
  137. novelwriter/formats/toodt.py +12 -71
  138. novelwriter/formats/toqdoc.py +11 -21
  139. novelwriter/formats/toraw.py +2 -6
  140. novelwriter/gui/doceditor.py +207 -245
  141. novelwriter/gui/dochighlight.py +142 -101
  142. novelwriter/gui/docviewer.py +53 -84
  143. novelwriter/gui/docviewerpanel.py +18 -41
  144. novelwriter/gui/editordocument.py +12 -17
  145. novelwriter/gui/itemdetails.py +5 -14
  146. novelwriter/gui/mainmenu.py +24 -32
  147. novelwriter/gui/noveltree.py +13 -51
  148. novelwriter/gui/outline.py +20 -61
  149. novelwriter/gui/projtree.py +40 -96
  150. novelwriter/gui/search.py +9 -24
  151. novelwriter/gui/sidebar.py +54 -22
  152. novelwriter/gui/statusbar.py +7 -22
  153. novelwriter/gui/theme.py +482 -368
  154. novelwriter/guimain.py +87 -101
  155. novelwriter/shared.py +79 -48
  156. novelwriter/splash.py +9 -5
  157. novelwriter/text/comments.py +1 -1
  158. novelwriter/text/counting.py +9 -5
  159. novelwriter/text/patterns.py +20 -15
  160. novelwriter/tools/dictionaries.py +18 -16
  161. novelwriter/tools/lipsum.py +15 -17
  162. novelwriter/tools/manusbuild.py +25 -45
  163. novelwriter/tools/manuscript.py +94 -95
  164. novelwriter/tools/manussettings.py +149 -104
  165. novelwriter/tools/noveldetails.py +10 -24
  166. novelwriter/tools/welcome.py +24 -72
  167. novelwriter/tools/writingstats.py +17 -26
  168. novelwriter/types.py +25 -13
  169. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/METADATA +7 -7
  170. novelwriter-2.8b1.dist-info/RECORD +212 -0
  171. novelwriter/assets/images/welcome-dark.jpg +0 -0
  172. novelwriter/assets/images/welcome-light.jpg +0 -0
  173. novelwriter/assets/syntax/cyberpunk_night.conf +0 -28
  174. novelwriter/assets/syntax/default_dark.conf +0 -42
  175. novelwriter/assets/syntax/default_light.conf +0 -42
  176. novelwriter/assets/syntax/dracula.conf +0 -44
  177. novelwriter/assets/syntax/grey_dark.conf +0 -29
  178. novelwriter/assets/syntax/grey_light.conf +0 -29
  179. novelwriter/assets/syntax/light_owl.conf +0 -49
  180. novelwriter/assets/syntax/night_owl.conf +0 -49
  181. novelwriter/assets/syntax/snazzy.conf +0 -42
  182. novelwriter/assets/syntax/solarized_dark.conf +0 -29
  183. novelwriter/assets/syntax/solarized_light.conf +0 -29
  184. novelwriter/assets/syntax/tango.conf +0 -39
  185. novelwriter/assets/syntax/tomorrow.conf +0 -49
  186. novelwriter/assets/syntax/tomorrow_night.conf +0 -49
  187. novelwriter/assets/syntax/tomorrow_night_blue.conf +0 -49
  188. novelwriter/assets/syntax/tomorrow_night_bright.conf +0 -49
  189. novelwriter/assets/syntax/tomorrow_night_eighties.conf +0 -49
  190. novelwriter/assets/themes/default.conf +0 -3
  191. novelwriter-2.7.4.dist-info/RECORD +0 -163
  192. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/WHEEL +0 -0
  193. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/entry_points.txt +0 -0
  194. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/licenses/LICENSE.md +0 -0
  195. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/licenses/setup/LICENSE-Apache-2.0.txt +0 -0
  196. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/top_level.txt +0 -0
@@ -29,7 +29,7 @@ General Public License for more details.
29
29
 
30
30
  You should have received a copy of the GNU General Public License
31
31
  along with this program. If not, see <https://www.gnu.org/licenses/>.
32
- """
32
+ """ # noqa
33
33
  from __future__ import annotations
34
34
 
35
35
  import bisect
@@ -39,17 +39,18 @@ from enum import Enum, IntFlag
39
39
  from time import time
40
40
 
41
41
  from PyQt6.QtCore import (
42
- QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal,
43
- pyqtSlot
42
+ QObject, QPoint, QRect, QRegularExpression, QRunnable, Qt, QTimer,
43
+ QVariant, pyqtSignal, pyqtSlot
44
44
  )
45
45
  from PyQt6.QtGui import (
46
- QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent,
47
- QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QShortcut,
48
- QTextBlock, QTextCursor, QTextDocument, QTextOption
46
+ QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent,
47
+ QInputMethodEvent, QKeyEvent, QKeySequence, QMouseEvent, QPalette, QPixmap,
48
+ QResizeEvent, QShortcut, QTextBlock, QTextCursor, QTextDocument,
49
+ QTextFormat, QTextOption
49
50
  )
50
51
  from PyQt6.QtWidgets import (
51
52
  QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
52
- QPlainTextEdit, QToolBar, QVBoxLayout, QWidget
53
+ QPlainTextEdit, QTextEdit, QToolBar, QVBoxLayout, QWidget
53
54
  )
54
55
 
55
56
  from novelwriter import CONFIG, SHARED
@@ -74,8 +75,10 @@ from novelwriter.text.counting import standardCounter
74
75
  from novelwriter.tools.lipsum import GuiLipsum
75
76
  from novelwriter.types import (
76
77
  QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop,
77
- QtAlignRight, QtKeepAnchor, QtModCtrl, QtModNone, QtModShift, QtMouseLeft,
78
- QtMoveAnchor, QtMoveLeft, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded
78
+ QtAlignRight, QtImCursorRectangle, QtKeepAnchor, QtModCtrl, QtModNone,
79
+ QtModShift, QtMouseLeft, QtMoveAnchor, QtMoveLeft, QtMoveRight,
80
+ QtScrollAlwaysOff, QtScrollAsNeeded, QtSelectBlock, QtSelectDocument,
81
+ QtSelectWord, QtTransparent
79
82
  )
80
83
 
81
84
  logger = logging.getLogger(__name__)
@@ -97,7 +100,7 @@ class _TagAction(IntFlag):
97
100
 
98
101
 
99
102
  class GuiDocEditor(QPlainTextEdit):
100
- """Gui Widget: Main Document Editor"""
103
+ """Gui Widget: Main Document Editor."""
101
104
 
102
105
  __slots__ = (
103
106
  "_autoReplace", "_completer", "_doReplace", "_docChanged", "_docHandle", "_followTag1",
@@ -145,13 +148,15 @@ class GuiDocEditor(QPlainTextEdit):
145
148
  self._lastActive = 0.0 # Timestamp of last activity
146
149
  self._lastFind = None # Position of the last found search word
147
150
  self._doReplace = False # Switch to temporarily disable auto-replace
151
+ self._lineColor = QtTransparent
152
+ self._selection = QTextEdit.ExtraSelection()
148
153
 
149
154
  # Auto-Replace
150
155
  self._autoReplace = TextAutoReplace()
151
156
 
152
157
  # Completer
153
158
  self._completer = CommandCompleter(self)
154
- self._completer.complete.connect(self._insertCompletion)
159
+ self._completer.insertText.connect(self._insertCompletion)
155
160
 
156
161
  # Create Custom Document
157
162
  self._qDocument = GuiTextDocument(self)
@@ -233,8 +238,6 @@ class GuiDocEditor(QPlainTextEdit):
233
238
 
234
239
  logger.debug("Ready: GuiDocEditor")
235
240
 
236
- return
237
-
238
241
  ##
239
242
  # Properties
240
243
  ##
@@ -283,18 +286,18 @@ class GuiDocEditor(QPlainTextEdit):
283
286
  self.docHeader.clearHeader()
284
287
  self.docFooter.setHandle(self._docHandle)
285
288
  self.docToolBar.setVisible(False)
289
+ self.setExtraSelections([])
286
290
 
287
291
  self.itemHandleChanged.emit("")
288
292
 
289
- return
290
-
291
293
  def updateTheme(self) -> None:
292
294
  """Update theme elements."""
295
+ logger.debug("Theme Update: GuiDocEditor")
296
+
293
297
  self.docSearch.updateTheme()
294
298
  self.docHeader.updateTheme()
295
299
  self.docFooter.updateTheme()
296
300
  self.docToolBar.updateTheme()
297
- return
298
301
 
299
302
  def updateSyntaxColors(self) -> None:
300
303
  """Update the syntax highlighting theme."""
@@ -315,7 +318,9 @@ class GuiDocEditor(QPlainTextEdit):
315
318
  self.docHeader.matchColors()
316
319
  self.docFooter.matchColors()
317
320
 
318
- return
321
+ self._lineColor = syntax.line
322
+ self._selection.format.setBackground(self._lineColor)
323
+ self._selection.format.setProperty(QTextFormat.Property.FullWidthSelection, True)
319
324
 
320
325
  def initEditor(self) -> None:
321
326
  """Initialise or re-initialise the editor with the user's
@@ -372,6 +377,8 @@ class GuiDocEditor(QPlainTextEdit):
372
377
  # Refresh sizes
373
378
  self.setTabStopDistance(CONFIG.tabWidth)
374
379
  self.setCursorWidth(CONFIG.cursorWidth)
380
+ self.setExtraSelections([])
381
+ self._cursorMoved()
375
382
 
376
383
  # If we have a document open, we should refresh it in case the
377
384
  # font changed, otherwise we just clear the editor entirely,
@@ -382,8 +389,6 @@ class GuiDocEditor(QPlainTextEdit):
382
389
  else:
383
390
  self.clearEditor()
384
391
 
385
- return
386
-
387
392
  def loadText(self, tHandle: str, tLine: int | None = None) -> bool:
388
393
  """Load text from a document into the editor. If we have an I/O
389
394
  error, we must handle this and clear the editor so that we don't
@@ -461,7 +466,6 @@ class GuiDocEditor(QPlainTextEdit):
461
466
  self.updateDocMargins()
462
467
  self.setDocumentChanged(True)
463
468
  QApplication.restoreOverrideCursor()
464
- return
465
469
 
466
470
  def saveText(self) -> bool:
467
471
  """Save the text currently in the editor to the NWDocument
@@ -529,7 +533,6 @@ class GuiDocEditor(QPlainTextEdit):
529
533
  vBar.setValue(vBar.value() + 1)
530
534
  count += 1
531
535
  QApplication.processEvents()
532
- return
533
536
 
534
537
  def updateDocMargins(self) -> None:
535
538
  """Automatically adjust the margins so the text is centred if
@@ -570,8 +573,6 @@ class GuiDocEditor(QPlainTextEdit):
570
573
  lM = max(self._vpMargin, fH)
571
574
  self.setViewportMargins(tM, uM, tM, lM)
572
575
 
573
- return
574
-
575
576
  ##
576
577
  # Getters
577
578
  ##
@@ -581,20 +582,19 @@ class GuiDocEditor(QPlainTextEdit):
581
582
  QTextDocument->toRawText instead of toPlainText. The former preserves
582
583
  non-breaking spaces, the latter does not. We still want to get rid of
583
584
  paragraph and line separators though.
585
+
584
586
  See: https://doc.qt.io/qt-6/qtextdocument.html#toPlainText
585
587
  """
586
588
  text = self._qDocument.toRawText()
587
589
  text = text.replace(nwUnicode.U_LSEP, "\n") # Line separators
588
- text = text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
589
- return text
590
+ return text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
590
591
 
591
592
  def getSelectedText(self) -> str:
592
593
  """Get currently selected text."""
593
594
  if (cursor := self.textCursor()).hasSelection():
594
595
  text = cursor.selectedText()
595
596
  text = text.replace(nwUnicode.U_LSEP, "\n") # Line separators
596
- text = text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
597
- return text
597
+ return text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
598
598
  return ""
599
599
 
600
600
  def getCursorPosition(self) -> int:
@@ -615,7 +615,6 @@ class GuiDocEditor(QPlainTextEdit):
615
615
  logger.debug("Document changed status is '%s'", state)
616
616
  self._docChanged = state
617
617
  self.editedStatusChanged.emit(self._docChanged)
618
- return
619
618
 
620
619
  def setCursorPosition(self, position: int) -> None:
621
620
  """Move the cursor to a given position in the document."""
@@ -624,14 +623,12 @@ class GuiDocEditor(QPlainTextEdit):
624
623
  cursor.setPosition(minmax(position, 0, chars-1))
625
624
  self.setTextCursor(cursor)
626
625
  self.centerCursor()
627
- return
628
626
 
629
627
  def saveCursorPosition(self) -> None:
630
628
  """Save the cursor position to the current project item."""
631
629
  if self._nwItem is not None:
632
630
  cursPos = self.getCursorPosition()
633
631
  self._nwItem.setCursorPos(cursPos)
634
- return
635
632
 
636
633
  def setCursorLine(self, line: int | None) -> None:
637
634
  """Move the cursor to a given line in the document."""
@@ -640,7 +637,6 @@ class GuiDocEditor(QPlainTextEdit):
640
637
  if block:
641
638
  self.setCursorPosition(block.position())
642
639
  logger.debug("Cursor moved to line %d", line)
643
- return
644
640
 
645
641
  def setCursorSelection(self, start: int, length: int) -> None:
646
642
  """Make a text selection."""
@@ -649,14 +645,15 @@ class GuiDocEditor(QPlainTextEdit):
649
645
  cursor.setPosition(start, QtMoveAnchor)
650
646
  cursor.setPosition(start + length, QtKeepAnchor)
651
647
  self.setTextCursor(cursor)
652
- return
653
648
 
654
649
  ##
655
650
  # Spell Checking
656
651
  ##
657
652
 
658
653
  def toggleSpellCheck(self, state: bool | None) -> None:
659
- """This is the main spell check setting function, and this one
654
+ """Toggle spell checking.
655
+
656
+ This is the main spell check setting function, and this one
660
657
  should call all other setSpellCheck functions in other classes.
661
658
  If the spell check state is not defined (None), then toggle the
662
659
  current status saved in this class.
@@ -678,9 +675,7 @@ class GuiDocEditor(QPlainTextEdit):
678
675
  self.spellCheckStateChanged.emit(state)
679
676
  self.spellCheckDocument()
680
677
 
681
- logger.debug("Spell check is set to '%s'", str(state))
682
-
683
- return
678
+ logger.debug("Spell check is set to '%s'", state)
684
679
 
685
680
  def spellCheckDocument(self) -> None:
686
681
  """Rerun the highlighter to update spell checking status of the
@@ -693,7 +688,6 @@ class GuiDocEditor(QPlainTextEdit):
693
688
  QApplication.restoreOverrideCursor()
694
689
  logger.debug("Document highlighted in %.3f ms", 1000*(time() - start))
695
690
  self.updateStatusMessage.emit(self.tr("Spell check complete"))
696
- return
697
691
 
698
692
  ##
699
693
  # General Class Methods
@@ -733,14 +727,16 @@ class GuiDocEditor(QPlainTextEdit):
733
727
  self._toggleFormat(2, "*")
734
728
  elif action == nwDocAction.MD_STRIKE:
735
729
  self._toggleFormat(2, "~")
730
+ elif action == nwDocAction.MD_MARK:
731
+ self._toggleFormat(2, "=")
736
732
  elif action == nwDocAction.S_QUOTE:
737
733
  self._wrapSelection(CONFIG.fmtSQuoteOpen, CONFIG.fmtSQuoteClose)
738
734
  elif action == nwDocAction.D_QUOTE:
739
735
  self._wrapSelection(CONFIG.fmtDQuoteOpen, CONFIG.fmtDQuoteClose)
740
736
  elif action == nwDocAction.SEL_ALL:
741
- self._makeSelection(QTextCursor.SelectionType.Document)
737
+ self._makeSelection(QtSelectDocument)
742
738
  elif action == nwDocAction.SEL_PARA:
743
- self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor)
739
+ self._makeSelection(QtSelectBlock)
744
740
  elif action == nwDocAction.BLOCK_H1:
745
741
  self._formatBlock(nwDocAction.BLOCK_H1)
746
742
  elif action == nwDocAction.BLOCK_H2:
@@ -792,7 +788,7 @@ class GuiDocEditor(QPlainTextEdit):
792
788
  elif action == nwDocAction.SC_SUB:
793
789
  self._wrapSelection(nwShortcode.SUB_O, nwShortcode.SUB_C)
794
790
  else:
795
- logger.debug("Unknown or unsupported document action '%s'", str(action))
791
+ logger.debug("Unknown or unsupported document action '%s'", action)
796
792
  self._allowAutoReplace(True)
797
793
  return False
798
794
 
@@ -820,7 +816,6 @@ class GuiDocEditor(QPlainTextEdit):
820
816
  details=self.tr("File Location: {0}").format(self._nwDocument.fileLocation),
821
817
  log=False
822
818
  )
823
- return
824
819
 
825
820
  def insertText(self, insert: str | nwDocInsert) -> None:
826
821
  """Insert a specific type of text at the cursor position."""
@@ -914,7 +909,7 @@ class GuiDocEditor(QPlainTextEdit):
914
909
  return True
915
910
 
916
911
  ##
917
- # Document Events and Maintenance
912
+ # Events and Overloads
918
913
  ##
919
914
 
920
915
  def keyPressEvent(self, event: QKeyEvent) -> None:
@@ -962,7 +957,6 @@ class GuiDocEditor(QPlainTextEdit):
962
957
  event.acceptProposedAction()
963
958
  else:
964
959
  super().dragEnterEvent(event)
965
- return
966
960
 
967
961
  def dragMoveEvent(self, event: QDragMoveEvent) -> None:
968
962
  """Overload drag move event to handle dragged items."""
@@ -970,7 +964,6 @@ class GuiDocEditor(QPlainTextEdit):
970
964
  event.acceptProposedAction()
971
965
  else:
972
966
  super().dragMoveEvent(event)
973
- return
974
967
 
975
968
  def dropEvent(self, event: QDropEvent) -> None:
976
969
  """Overload drop event to handle dragged items."""
@@ -980,7 +973,6 @@ class GuiDocEditor(QPlainTextEdit):
980
973
  self.openDocumentRequest.emit(handles[0], nwDocMode.EDIT, "", True)
981
974
  else:
982
975
  super().dropEvent(event)
983
- return
984
976
 
985
977
  def focusNextPrevChild(self, _next: bool) -> bool:
986
978
  """Capture the focus request from the tab key on the text
@@ -1007,7 +999,6 @@ class GuiDocEditor(QPlainTextEdit):
1007
999
  else:
1008
1000
  self._processTag(cursor)
1009
1001
  super().mouseReleaseEvent(event)
1010
- return
1011
1002
 
1012
1003
  def resizeEvent(self, event: QResizeEvent) -> None:
1013
1004
  """If the text editor is resized, we must make sure the document
@@ -1015,7 +1006,26 @@ class GuiDocEditor(QPlainTextEdit):
1015
1006
  """
1016
1007
  self.updateDocMargins()
1017
1008
  super().resizeEvent(event)
1018
- return
1009
+
1010
+ def inputMethodEvent(self, event: QInputMethodEvent) -> None:
1011
+ """Handle text being input from CJK input methods."""
1012
+ super().inputMethodEvent(event)
1013
+ if event.commitString():
1014
+ # See issues #2267 and #2517
1015
+ self.ensureCursorVisible()
1016
+ self._completerToCursor()
1017
+
1018
+ def inputMethodQuery(self, query: Qt.InputMethodQuery) -> QRect | QVariant:
1019
+ """Adjust completion windows for CJK input methods to consider
1020
+ the viewport margins.
1021
+ """
1022
+ if query == QtImCursorRectangle:
1023
+ # See issues #2267 and #2517
1024
+ vM = self.viewportMargins()
1025
+ rect = self.cursorRect()
1026
+ rect.translate(vM.left(), vM.top())
1027
+ return rect
1028
+ return super().inputMethodQuery(query)
1019
1029
 
1020
1030
  ##
1021
1031
  # Public Slots
@@ -1023,14 +1033,13 @@ class GuiDocEditor(QPlainTextEdit):
1023
1033
 
1024
1034
  @pyqtSlot(str, Enum)
1025
1035
  def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None:
1026
- """Called when an item label is changed to check if the document
1027
- title bar needs updating,
1036
+ """Process project item change. Called when an item label is
1037
+ changed to check if the document title bar needs updating.
1028
1038
  """
1029
1039
  if tHandle == self._docHandle and change == nwChange.UPDATE:
1030
1040
  self.docHeader.setHandle(tHandle)
1031
1041
  self.docFooter.updateInfo()
1032
1042
  self.updateDocMargins()
1033
- return
1034
1043
 
1035
1044
  @pyqtSlot(str)
1036
1045
  def insertKeyWord(self, keyword: str) -> bool:
@@ -1041,8 +1050,7 @@ class GuiDocEditor(QPlainTextEdit):
1041
1050
  logger.error("Invalid keyword '%s'", keyword)
1042
1051
  return False
1043
1052
  logger.debug("Inserting keyword '%s'", keyword)
1044
- state = self.insertNewBlock(f"{keyword}: ")
1045
- return state
1053
+ return self.insertNewBlock(f"{keyword}: ")
1046
1054
 
1047
1055
  @pyqtSlot()
1048
1056
  def toggleSearch(self) -> None:
@@ -1051,14 +1059,12 @@ class GuiDocEditor(QPlainTextEdit):
1051
1059
  self.closeSearch()
1052
1060
  else:
1053
1061
  self.beginSearch()
1054
- return
1055
1062
 
1056
1063
  @pyqtSlot(list, list)
1057
1064
  def updateChangedTags(self, updated: list[str], deleted: list[str]) -> None:
1058
1065
  """Tags have changed, so just in case we rehighlight them."""
1059
1066
  if updated or deleted:
1060
1067
  self._qDocument.syntaxHighlighter.rehighlightByType(BLOCK_META)
1061
- return
1062
1068
 
1063
1069
  ##
1064
1070
  # Private Slots
@@ -1080,37 +1086,34 @@ class GuiDocEditor(QPlainTextEdit):
1080
1086
 
1081
1087
  if (block := self._qDocument.findBlock(pos)).isValid():
1082
1088
  text = block.text()
1089
+
1083
1090
  if text and text[0] in "@%" and added + removed == 1:
1084
1091
  # Only run on single character changes, or it will trigger
1085
1092
  # at unwanted times when other changes are made to the document
1086
1093
  cursor = self.textCursor()
1087
1094
  bPos = cursor.positionInBlock()
1088
- if bPos > 0 and (viewport := self.viewport()):
1095
+ if bPos > 0:
1089
1096
  if text[0] == "@":
1090
1097
  show = self._completer.updateMetaText(text, bPos)
1091
1098
  else:
1092
1099
  show = self._completer.updateCommentText(text, bPos)
1093
1100
  if show:
1094
- point = self.cursorRect().bottomRight()
1095
- self._completer.move(viewport.mapToGlobal(point))
1096
1101
  self._completer.show()
1097
- else:
1098
- self._completer.close()
1099
- else:
1100
- self._completer.close()
1102
+ self._completerToCursor()
1101
1103
 
1102
1104
  if self._doReplace and added == 1:
1103
1105
  cursor = self.textCursor()
1104
1106
  if self._autoReplace.process(text, cursor):
1105
1107
  self._qDocument.syntaxHighlighter.rehighlightBlock(cursor.block())
1106
1108
 
1107
- return
1108
-
1109
1109
  @pyqtSlot()
1110
1110
  def _cursorMoved(self) -> None:
1111
1111
  """Triggered when the cursor moved in the editor."""
1112
1112
  self.docFooter.updateLineCount(self.textCursor())
1113
- return
1113
+ if CONFIG.lineHighlight:
1114
+ self._selection.cursor = self.textCursor()
1115
+ self._selection.cursor.clearSelection()
1116
+ self.setExtraSelections([self._selection])
1114
1117
 
1115
1118
  @pyqtSlot(int, int, str)
1116
1119
  def _insertCompletion(self, pos: int, length: int, text: str) -> None:
@@ -1121,14 +1124,12 @@ class GuiDocEditor(QPlainTextEdit):
1121
1124
  cursor.setPosition(check, QtMoveAnchor)
1122
1125
  cursor.setPosition(check + length, QtKeepAnchor)
1123
1126
  cursor.insertText(text)
1124
- self._completer.hide()
1125
- return
1127
+ self._completer.close()
1126
1128
 
1127
1129
  @pyqtSlot()
1128
1130
  def _openContextFromCursor(self) -> None:
1129
1131
  """Open the spell check context menu at the cursor."""
1130
1132
  self._openContextMenu(self.cursorRect().center())
1131
- return
1132
1133
 
1133
1134
  @pyqtSlot("QPoint")
1134
1135
  def _openContextMenu(self, pos: QPoint) -> None:
@@ -1176,23 +1177,19 @@ class GuiDocEditor(QPlainTextEdit):
1176
1177
  action = qtAddAction(ctxMenu, self.tr("Select All"))
1177
1178
  action.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL))
1178
1179
  action = qtAddAction(ctxMenu, self.tr("Select Word"))
1179
- action.triggered.connect(qtLambda(
1180
- self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, pos,
1181
- ))
1180
+ action.triggered.connect(qtLambda(self._makePosSelection, QtSelectWord, pos))
1182
1181
  action = qtAddAction(ctxMenu, self.tr("Select Paragraph"))
1183
- action.triggered.connect(qtLambda(
1184
- self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, pos
1185
- ))
1182
+ action.triggered.connect(qtLambda(self._makePosSelection, QtSelectBlock, pos))
1186
1183
 
1187
1184
  # Spell Checking
1188
1185
  if SHARED.project.data.spellCheck:
1189
- word, cPos, cLen, suggest = self._qDocument.spellErrorAtPos(pCursor.position())
1190
- if word and cPos >= 0 and cLen > 0:
1186
+ word, offset, suggest = self._qDocument.spellErrorAtPos(pCursor.position())
1187
+ if word and offset >= 0:
1191
1188
  logger.debug("Word '%s' is misspelled", word)
1192
1189
  block = pCursor.block()
1193
1190
  sCursor = self.textCursor()
1194
- sCursor.setPosition(block.position() + cPos)
1195
- sCursor.movePosition(QtMoveRight, QtKeepAnchor, cLen)
1191
+ sCursor.setPosition(block.position() + offset)
1192
+ sCursor.movePosition(QtMoveRight, QtKeepAnchor, len(word))
1196
1193
  if suggest:
1197
1194
  ctxMenu.addSeparator()
1198
1195
  qtAddAction(ctxMenu, self.tr("Spelling Suggestion(s)"))
@@ -1215,8 +1212,6 @@ class GuiDocEditor(QPlainTextEdit):
1215
1212
 
1216
1213
  ctxMenu.setParent(None)
1217
1214
 
1218
- return
1219
-
1220
1215
  @pyqtSlot()
1221
1216
  def _runDocumentTasks(self) -> None:
1222
1217
  """Run timer document tasks."""
@@ -1253,11 +1248,10 @@ class GuiDocEditor(QPlainTextEdit):
1253
1248
  if not self.textCursor().hasSelection():
1254
1249
  # Selection counter should take precedence (#2155)
1255
1250
  self.docFooter.updateMainCount(mCount, False)
1256
- return
1257
1251
 
1258
1252
  @pyqtSlot()
1259
1253
  def _updateSelectedStatus(self) -> None:
1260
- """The user made a change in text selection. Forward this
1254
+ """Process user change in text selection. Forward this
1261
1255
  information to the footer, and start the selection word counter.
1262
1256
  """
1263
1257
  if self.textCursor().hasSelection():
@@ -1266,7 +1260,6 @@ class GuiDocEditor(QPlainTextEdit):
1266
1260
  else:
1267
1261
  self._timerSel.stop()
1268
1262
  self.docFooter.updateMainCount(0, False)
1269
- return
1270
1263
 
1271
1264
  @pyqtSlot()
1272
1265
  def _runSelCounter(self) -> None:
@@ -1284,14 +1277,12 @@ class GuiDocEditor(QPlainTextEdit):
1284
1277
  if self._docHandle and self._nwItem:
1285
1278
  self.docFooter.updateMainCount(cCount if CONFIG.useCharCount else wCount, True)
1286
1279
  self._timerSel.stop()
1287
- return
1288
1280
 
1289
1281
  @pyqtSlot()
1290
1282
  def _closeCurrentDocument(self) -> None:
1291
1283
  """Close the document. Forwarded to the main Gui."""
1292
1284
  self.closeEditorRequest.emit()
1293
1285
  self.docToolBar.setVisible(False)
1294
- return
1295
1286
 
1296
1287
  @pyqtSlot()
1297
1288
  def _toggleToolBarVisibility(self) -> None:
@@ -1299,7 +1290,6 @@ class GuiDocEditor(QPlainTextEdit):
1299
1290
  state = not self.docToolBar.isVisible()
1300
1291
  self.docToolBar.setVisible(state)
1301
1292
  CONFIG.showEditToolBar = state
1302
- return
1303
1293
 
1304
1294
  ##
1305
1295
  # Search & Replace
@@ -1310,14 +1300,12 @@ class GuiDocEditor(QPlainTextEdit):
1310
1300
  self.docSearch.setSearchText(self.getSelectedText() or None)
1311
1301
  resS, _ = self.findAllOccurences()
1312
1302
  self.docSearch.setResultCount(None, len(resS))
1313
- return
1314
1303
 
1315
1304
  def beginReplace(self) -> None:
1316
1305
  """Initialise the search box and reset the replace text box."""
1317
1306
  self.beginSearch()
1318
1307
  self.docSearch.setReplaceText("")
1319
1308
  self.updateDocMargins()
1320
- return
1321
1309
 
1322
1310
  def findNext(self, goBack: bool = False) -> None:
1323
1311
  """Search for the next or previous occurrence of the search bar
@@ -1605,8 +1593,6 @@ class GuiDocEditor(QPlainTextEdit):
1605
1593
 
1606
1594
  self.setTextCursor(cursor)
1607
1595
 
1608
- return
1609
-
1610
1596
  def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> None:
1611
1597
  """Replace all straight quotes in the selected text."""
1612
1598
  cursor = self.textCursor()
@@ -1763,7 +1749,7 @@ class GuiDocEditor(QPlainTextEdit):
1763
1749
  elif action == nwDocAction.BLOCK_TXT:
1764
1750
  text = temp
1765
1751
  else:
1766
- logger.error("Unknown or unsupported block format requested: '%s'", str(action))
1752
+ logger.error("Unknown or unsupported block format requested: '%s'", action)
1767
1753
  return nwDocAction.NO_ACTION, "", 0
1768
1754
 
1769
1755
  return action, text, offset
@@ -1773,7 +1759,7 @@ class GuiDocEditor(QPlainTextEdit):
1773
1759
  cursor = self.textCursor()
1774
1760
  block = cursor.block()
1775
1761
  if not block.isValid():
1776
- logger.debug("Invalid block selected for action '%s'", str(action))
1762
+ logger.debug("Invalid block selected for action '%s'", action)
1777
1763
  return False
1778
1764
 
1779
1765
  action, text, offset = self._processBlockFormat(action, block.text())
@@ -1783,7 +1769,7 @@ class GuiDocEditor(QPlainTextEdit):
1783
1769
  pos = cursor.position()
1784
1770
 
1785
1771
  cursor.beginEditBlock()
1786
- self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor, cursor)
1772
+ self._makeSelection(QtSelectBlock, cursor)
1787
1773
  cursor.insertText(text)
1788
1774
  cursor.endEditBlock()
1789
1775
 
@@ -1811,7 +1797,7 @@ class GuiDocEditor(QPlainTextEdit):
1811
1797
  if pAction != nwDocAction.NO_ACTION and blockText.strip():
1812
1798
  action = pAction # First block decides further actions
1813
1799
  cursor.setPosition(block.position())
1814
- self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor, cursor)
1800
+ self._makeSelection(QtSelectBlock, cursor)
1815
1801
  cursor.insertText(text)
1816
1802
  toggle = False
1817
1803
 
@@ -1831,7 +1817,7 @@ class GuiDocEditor(QPlainTextEdit):
1831
1817
  """Strip line breaks within paragraphs in the selected text."""
1832
1818
  cursor = self.textCursor()
1833
1819
  if not cursor.hasSelection():
1834
- cursor.select(QTextCursor.SelectionType.Document)
1820
+ cursor.select(QtSelectDocument)
1835
1821
 
1836
1822
  rS = 0
1837
1823
  rE = self._qDocument.characterCount()
@@ -1866,8 +1852,6 @@ class GuiDocEditor(QPlainTextEdit):
1866
1852
  cursor.insertText(cleanText.rstrip() + "\n")
1867
1853
  cursor.endEditBlock()
1868
1854
 
1869
- return
1870
-
1871
1855
  def _insertCommentStructure(self, style: nwComment) -> None:
1872
1856
  """Insert a shortcut/comment combo."""
1873
1857
  if self._docHandle and style == nwComment.FOOTNOTE:
@@ -1898,6 +1882,12 @@ class GuiDocEditor(QPlainTextEdit):
1898
1882
  # Internal Functions
1899
1883
  ##
1900
1884
 
1885
+ def _completerToCursor(self) -> None:
1886
+ """Make sure the completer menu is positioned by the cursor."""
1887
+ if self._completer.isVisible() and (viewport := self.viewport()):
1888
+ point = self.cursorRect().bottomLeft()
1889
+ self._completer.move(viewport.mapToGlobal(point))
1890
+
1901
1891
  def _correctWord(self, cursor: QTextCursor, word: str) -> None:
1902
1892
  """Slot for the spell check context menu triggering the
1903
1893
  replacement of a word with the word from the dictionary.
@@ -1909,7 +1899,6 @@ class GuiDocEditor(QPlainTextEdit):
1909
1899
  cursor.endEditBlock()
1910
1900
  cursor.setPosition(pos)
1911
1901
  self.setTextCursor(cursor)
1912
- return
1913
1902
 
1914
1903
  def _addWord(self, word: str, block: QTextBlock, save: bool) -> None:
1915
1904
  """Slot for the spell check context menu triggered when the user
@@ -1918,7 +1907,6 @@ class GuiDocEditor(QPlainTextEdit):
1918
1907
  logger.debug("Added '%s' to project dictionary, %s", word, "saved" if save else "unsaved")
1919
1908
  SHARED.spelling.addWord(word, save=save)
1920
1909
  self._qDocument.syntaxHighlighter.rehighlightBlock(block)
1921
- return
1922
1910
 
1923
1911
  def _processTag(
1924
1912
  self, cursor: QTextCursor | None = None, follow: bool = True, create: bool = False
@@ -1992,7 +1980,6 @@ class GuiDocEditor(QPlainTextEdit):
1992
1980
  if self._docHandle:
1993
1981
  text = block.text().lstrip("#").lstrip("!").strip()
1994
1982
  self.requestProjectItemRenamed.emit(self._docHandle, text)
1995
- return
1996
1983
 
1997
1984
  def _autoSelect(self) -> QTextCursor:
1998
1985
  """Return a cursor which may or may not have a selection based
@@ -2047,10 +2034,10 @@ class GuiDocEditor(QPlainTextEdit):
2047
2034
  cursor.clearSelection()
2048
2035
  cursor.select(mode)
2049
2036
 
2050
- if mode == QTextCursor.SelectionType.WordUnderCursor:
2037
+ if mode == QtSelectWord:
2051
2038
  cursor = self._autoSelect()
2052
2039
 
2053
- elif mode == QTextCursor.SelectionType.BlockUnderCursor:
2040
+ elif mode == QtSelectBlock:
2054
2041
  # This selection mode also selects the preceding paragraph
2055
2042
  # separator, which we want to avoid.
2056
2043
  posS = cursor.selectionStart()
@@ -2062,14 +2049,11 @@ class GuiDocEditor(QPlainTextEdit):
2062
2049
 
2063
2050
  self.setTextCursor(cursor)
2064
2051
 
2065
- return
2066
-
2067
2052
  def _makePosSelection(self, mode: QTextCursor.SelectionType, pos: QPoint) -> None:
2068
2053
  """Select text based on selection mode, but first move cursor."""
2069
2054
  cursor = self.cursorForPosition(pos)
2070
2055
  self.setTextCursor(cursor)
2071
2056
  self._makeSelection(mode)
2072
- return
2073
2057
 
2074
2058
  def _allowAutoReplace(self, state: bool) -> None:
2075
2059
  """Enable/disable the auto-replace feature temporarily."""
@@ -2077,11 +2061,10 @@ class GuiDocEditor(QPlainTextEdit):
2077
2061
  self._doReplace = CONFIG.doReplace
2078
2062
  else:
2079
2063
  self._doReplace = False
2080
- return
2081
2064
 
2082
2065
 
2083
2066
  class CommandCompleter(QMenu):
2084
- """GuiWidget: Command Completer Menu
2067
+ """GuiWidget: Command Completer Menu.
2085
2068
 
2086
2069
  This is a context menu with options populated from the user's
2087
2070
  defined tags and keys. It also helps to type the meta data keyword
@@ -2089,11 +2072,13 @@ class CommandCompleter(QMenu):
2089
2072
  called on every keystroke on a line starting with @ or %.
2090
2073
  """
2091
2074
 
2092
- complete = pyqtSignal(int, int, str)
2075
+ __slots__ = ("_parent",)
2076
+
2077
+ insertText = pyqtSignal(int, int, str)
2093
2078
 
2094
2079
  def __init__(self, parent: QWidget) -> None:
2095
2080
  super().__init__(parent=parent)
2096
- return
2081
+ self._parent = parent
2097
2082
 
2098
2083
  def updateMetaText(self, text: str, pos: int) -> bool:
2099
2084
  """Update the menu options based on the line of text."""
@@ -2179,15 +2164,14 @@ class CommandCompleter(QMenu):
2179
2164
 
2180
2165
  def keyPressEvent(self, event: QKeyEvent) -> None:
2181
2166
  """Capture keypresses and forward most of them to the editor."""
2182
- parent = self.parent()
2183
2167
  if event.key() in (
2184
2168
  Qt.Key.Key_Up, Qt.Key.Key_Down, Qt.Key.Key_Return,
2185
2169
  Qt.Key.Key_Enter, Qt.Key.Key_Escape
2186
2170
  ):
2187
2171
  super().keyPressEvent(event)
2188
- elif isinstance(parent, GuiDocEditor):
2189
- parent.keyPressEvent(event)
2190
- return
2172
+ else:
2173
+ self.close() # Close to release the event lock before forwarding the key press (#2510)
2174
+ self._parent.keyPressEvent(event)
2191
2175
 
2192
2176
  ##
2193
2177
  # Internal Functions
@@ -2195,12 +2179,11 @@ class CommandCompleter(QMenu):
2195
2179
 
2196
2180
  def _emitComplete(self, pos: int, length: int, value: str) -> None:
2197
2181
  """Emit the signal to indicate a selection has been made."""
2198
- self.complete.emit(pos, length, value)
2199
- return
2182
+ self.insertText.emit(pos, length, value)
2200
2183
 
2201
2184
 
2202
2185
  class BackgroundWordCounter(QRunnable):
2203
- """The Off-GUI Thread Word Counter
2186
+ """The Off-GUI Thread Word Counter.
2204
2187
 
2205
2188
  A runnable for the word counter to be run in the thread pool off the
2206
2189
  main GUI thread.
@@ -2212,9 +2195,9 @@ class BackgroundWordCounter(QRunnable):
2212
2195
  self._forSelection = forSelection
2213
2196
  self._isRunning = False
2214
2197
  self.signals = BackgroundWordCounterSignals()
2215
- return
2216
2198
 
2217
2199
  def isRunning(self) -> bool:
2200
+ """Return True if the word counter is already running."""
2218
2201
  return self._isRunning
2219
2202
 
2220
2203
  @pyqtSlot()
@@ -2232,17 +2215,17 @@ class BackgroundWordCounter(QRunnable):
2232
2215
  self.signals.countsReady.emit(cC, wC, pC)
2233
2216
  self._isRunning = False
2234
2217
 
2235
- return
2236
-
2237
2218
 
2238
2219
  class BackgroundWordCounterSignals(QObject):
2239
2220
  """The QRunnable cannot emit a signal, so we need a simple QObject
2240
2221
  to hold the word counter signal.
2241
2222
  """
2223
+
2242
2224
  countsReady = pyqtSignal(int, int, int)
2243
2225
 
2244
2226
 
2245
2227
  class TextAutoReplace:
2228
+ """Encapsulates the editor auto replace feature."""
2246
2229
 
2247
2230
  __slots__ = (
2248
2231
  "_doPadAfter", "_doPadBefore", "_padAfter", "_padBefore", "_padChar",
@@ -2252,7 +2235,6 @@ class TextAutoReplace:
2252
2235
 
2253
2236
  def __init__(self) -> None:
2254
2237
  self.initSettings()
2255
- return
2256
2238
 
2257
2239
  def initSettings(self) -> None:
2258
2240
  """Initialise the auto-replace settings from config."""
@@ -2271,27 +2253,28 @@ class TextAutoReplace:
2271
2253
  self._padAfter = CONFIG.fmtPadAfter
2272
2254
  self._doPadBefore = bool(CONFIG.fmtPadBefore)
2273
2255
  self._doPadAfter = bool(CONFIG.fmtPadAfter)
2274
- return
2275
2256
 
2276
2257
  def process(self, text: str, cursor: QTextCursor) -> bool:
2277
2258
  """Auto-replace text elements based on main configuration.
2278
2259
  Returns True if anything was changed.
2279
2260
  """
2280
- pos = cursor.positionInBlock()
2281
- length = len(text)
2282
- if length < 1 or pos-1 > length:
2261
+ aPos = cursor.position()
2262
+ bPos = cursor.positionInBlock()
2263
+ block = cursor.block()
2264
+ length = block.length() - 1
2265
+ if length < 1 or bPos-1 > length:
2283
2266
  return False
2284
2267
 
2285
- delete, insert = self._determine(text, pos)
2286
- if insert == "":
2287
- return False
2268
+ cursor.movePosition(QtMoveLeft, QtKeepAnchor, min(4, bPos))
2269
+ last = cursor.selectedText()
2270
+ delete, insert = self._determine(last, bPos)
2288
2271
 
2289
2272
  check = insert
2290
2273
  if self._doPadBefore and check in self._padBefore:
2291
2274
  if not (check == ":" and length > 1 and text[0] == "@"):
2292
2275
  delete = max(delete, 1)
2293
- chkPos = pos - delete - 1
2294
- if chkPos >= 0 and text[chkPos].isspace():
2276
+ chkPos = len(last) - delete - 1
2277
+ if chkPos >= 0 and last[chkPos].isspace():
2295
2278
  # Strip existing space before inserting a new (#1061)
2296
2279
  delete += 1
2297
2280
  insert = self._padChar + insert
@@ -2302,6 +2285,7 @@ class TextAutoReplace:
2302
2285
  insert = insert + self._padChar
2303
2286
 
2304
2287
  if delete > 0:
2288
+ cursor.setPosition(aPos)
2305
2289
  cursor.movePosition(QtMoveLeft, QtKeepAnchor, delete)
2306
2290
  cursor.insertText(insert)
2307
2291
  return True
@@ -2310,42 +2294,55 @@ class TextAutoReplace:
2310
2294
 
2311
2295
  def _determine(self, text: str, pos: int) -> tuple[int, str]:
2312
2296
  """Determine what to replace, if anything."""
2313
- t1 = text[pos-1:pos]
2314
- t2 = text[pos-2:pos]
2315
- t3 = text[pos-3:pos]
2316
- t4 = text[pos-4:pos]
2317
- if t1 == "":
2318
- # Return early if there is nothing to check
2319
- return 0, ""
2320
-
2321
- leading = t2[:1].isspace()
2322
- if self._replaceDQuote:
2323
- if leading and t2.endswith('"'):
2297
+ t1 = text[-1:]
2298
+ t2 = text[-2:]
2299
+ t3 = text[-3:]
2300
+ t4 = text[-4:]
2301
+
2302
+ if self._replaceDQuote and t1 == '"':
2303
+ # Process Double Quote
2304
+ if pos == 1:
2324
2305
  return 1, self._quoteDO
2325
- elif t1 == '"':
2326
- if pos == 1:
2327
- return 1, self._quoteDO
2328
- elif pos == 2 and t2 == '>"':
2329
- return 1, self._quoteDO
2330
- elif pos == 3 and t3 == '>>"':
2331
- return 1, self._quoteDO
2332
- else:
2333
- return 1, self._quoteDC
2306
+ elif t2[:1].isspace() and t2.endswith('"'):
2307
+ return 1, self._quoteDO
2308
+ elif pos == 2 and t2 == '>"':
2309
+ return 1, self._quoteDO
2310
+ elif pos == 3 and t3 == '>>"':
2311
+ return 1, self._quoteDO
2312
+ elif pos == 2 and t2 == '_"':
2313
+ return 1, self._quoteDO
2314
+ elif t3[:1].isspace() and t3.endswith('_"'):
2315
+ return 1, self._quoteDO
2316
+ elif pos == 3 and t3 in ('**"', '=="', '~~"'):
2317
+ return 1, self._quoteDO
2318
+ elif t4[:1].isspace() and t4.endswith(('**"', '=="', '~~"')):
2319
+ return 1, self._quoteDO
2320
+ else:
2321
+ return 1, self._quoteDC
2334
2322
 
2335
- if self._replaceSQuote:
2336
- if leading and t2.endswith("'"):
2323
+ if self._replaceSQuote and t1 == "'":
2324
+ # Process Single Quote
2325
+ if pos == 1:
2337
2326
  return 1, self._quoteSO
2338
- elif t1 == "'":
2339
- if pos == 1:
2340
- return 1, self._quoteSO
2341
- elif pos == 2 and t2 == ">'":
2342
- return 1, self._quoteSO
2343
- elif pos == 3 and t3 == ">>'":
2344
- return 1, self._quoteSO
2345
- else:
2346
- return 1, self._quoteSC
2327
+ elif t2[:1].isspace() and t2.endswith("'"):
2328
+ return 1, self._quoteSO
2329
+ elif pos == 2 and t2 == ">'":
2330
+ return 1, self._quoteSO
2331
+ elif pos == 3 and t3 == ">>'":
2332
+ return 1, self._quoteSO
2333
+ elif pos == 2 and t2 == "_'":
2334
+ return 1, self._quoteSO
2335
+ elif t3[:1].isspace() and t3.endswith("_'"):
2336
+ return 1, self._quoteSO
2337
+ elif pos == 3 and t3 in ("**'", "=='", "~~'"):
2338
+ return 1, self._quoteSO
2339
+ elif t4[:1].isspace() and t4.endswith(("**'", "=='", "~~'")):
2340
+ return 1, self._quoteSO
2341
+ else:
2342
+ return 1, self._quoteSC
2347
2343
 
2348
- if self._replaceDash:
2344
+ if self._replaceDash and t1 == "-":
2345
+ # Process Dashes
2349
2346
  if t4 == "----":
2350
2347
  return 4, "\u2015" # Horizontal bar
2351
2348
  elif t3 == "---":
@@ -2358,6 +2355,7 @@ class TextAutoReplace:
2358
2355
  return 2, "\u2015" # Horizontal bar
2359
2356
 
2360
2357
  if self._replaceDots and t3 == "...":
2358
+ # Process Dots
2361
2359
  return 3, "\u2026" # Ellipsis
2362
2360
 
2363
2361
  if t1 == "\u2028": # Line separator
@@ -2368,7 +2366,7 @@ class TextAutoReplace:
2368
2366
 
2369
2367
 
2370
2368
  class GuiDocToolBar(QWidget):
2371
- """The Formatting and Options Fold Out Menu
2369
+ """The Formatting and Options Fold Out Menu.
2372
2370
 
2373
2371
  Only used by DocEditor, and is opened by the first button in the
2374
2372
  header.
@@ -2405,6 +2403,12 @@ class GuiDocToolBar(QWidget):
2405
2403
  qtLambda(self.requestDocAction.emit, nwDocAction.MD_STRIKE)
2406
2404
  )
2407
2405
 
2406
+ self.tbMarkMD = NIconToolButton(self, iSz)
2407
+ self.tbMarkMD.setToolTip(self.tr("Markdown Highlight"))
2408
+ self.tbMarkMD.clicked.connect(
2409
+ qtLambda(self.requestDocAction.emit, nwDocAction.MD_MARK)
2410
+ )
2411
+
2408
2412
  self.tbBold = NIconToolButton(self, iSz)
2409
2413
  self.tbBold.setToolTip(self.tr("Shortcode Bold"))
2410
2414
  self.tbBold.clicked.connect(
@@ -2454,6 +2458,7 @@ class GuiDocToolBar(QWidget):
2454
2458
  self.outerBox.addWidget(self.tbBoldMD)
2455
2459
  self.outerBox.addWidget(self.tbItalicMD)
2456
2460
  self.outerBox.addWidget(self.tbStrikeMD)
2461
+ self.outerBox.addWidget(self.tbMarkMD)
2457
2462
  self.outerBox.addSpacing(4)
2458
2463
  self.outerBox.addWidget(self.tbBold)
2459
2464
  self.outerBox.addWidget(self.tbItalic)
@@ -2473,34 +2478,32 @@ class GuiDocToolBar(QWidget):
2473
2478
 
2474
2479
  logger.debug("Ready: GuiDocToolBar")
2475
2480
 
2476
- return
2477
-
2478
2481
  def updateTheme(self) -> None:
2479
2482
  """Initialise GUI elements that depend on specific settings."""
2480
- syntax = SHARED.theme.syntaxTheme
2483
+ logger.debug("Theme Update: GuiDocToolBar")
2481
2484
 
2485
+ syntax = SHARED.theme.syntaxTheme
2482
2486
  palette = self.palette()
2483
2487
  palette.setColor(QPalette.ColorRole.Window, syntax.back)
2484
2488
  palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
2485
2489
  palette.setColor(QPalette.ColorRole.Text, syntax.text)
2486
2490
  self.setPalette(palette)
2487
2491
 
2488
- self.tbBoldMD.setThemeIcon("fmt_bold", "orange")
2489
- self.tbItalicMD.setThemeIcon("fmt_italic", "orange")
2490
- self.tbStrikeMD.setThemeIcon("fmt_strike", "orange")
2491
- self.tbBold.setThemeIcon("fmt_bold")
2492
- self.tbItalic.setThemeIcon("fmt_italic")
2493
- self.tbStrike.setThemeIcon("fmt_strike")
2494
- self.tbUnderline.setThemeIcon("fmt_underline")
2495
- self.tbMark.setThemeIcon("fmt_mark")
2496
- self.tbSuperscript.setThemeIcon("fmt_superscript")
2497
- self.tbSubscript.setThemeIcon("fmt_subscript")
2498
-
2499
- return
2492
+ self.tbBoldMD.setThemeIcon("fmt_bold", "markdown")
2493
+ self.tbItalicMD.setThemeIcon("fmt_italic", "markdown")
2494
+ self.tbStrikeMD.setThemeIcon("fmt_strike", "markdown")
2495
+ self.tbMarkMD.setThemeIcon("fmt_mark", "markdown")
2496
+ self.tbBold.setThemeIcon("fmt_bold", "shortcode")
2497
+ self.tbItalic.setThemeIcon("fmt_italic", "shortcode")
2498
+ self.tbStrike.setThemeIcon("fmt_strike", "shortcode")
2499
+ self.tbUnderline.setThemeIcon("fmt_underline", "shortcode")
2500
+ self.tbMark.setThemeIcon("fmt_mark", "shortcode")
2501
+ self.tbSuperscript.setThemeIcon("fmt_superscript", "shortcode")
2502
+ self.tbSubscript.setThemeIcon("fmt_subscript", "shortcode")
2500
2503
 
2501
2504
 
2502
2505
  class GuiDocEditSearch(QFrame):
2503
- """The Embedded Document Search/Replace Feature
2506
+ """The Embedded Document Search/Replace Feature.
2504
2507
 
2505
2508
  Only used by DocEditor, and is at a fixed position in the
2506
2509
  QTextEdit's viewport.
@@ -2590,7 +2593,7 @@ class GuiDocEditSearch(QFrame):
2590
2593
  # Buttons
2591
2594
  # =======
2592
2595
 
2593
- self.showReplace = NIconToggleButton(self, iSz, "unfold")
2596
+ self.showReplace = NIconToggleButton(self, iSz)
2594
2597
  self.showReplace.toggled.connect(self._doToggleReplace)
2595
2598
 
2596
2599
  self.searchButton = NIconToolButton(self, iSz)
@@ -2630,8 +2633,6 @@ class GuiDocEditSearch(QFrame):
2630
2633
 
2631
2634
  logger.debug("Ready: GuiDocEditSearch")
2632
2635
 
2633
- return
2634
-
2635
2636
  ##
2636
2637
  # Properties
2637
2638
  ##
@@ -2680,14 +2681,12 @@ class GuiDocEditSearch(QFrame):
2680
2681
  self.searchBox.selectAll()
2681
2682
  if CONFIG.searchRegEx:
2682
2683
  self._alertSearchValid(True)
2683
- return
2684
2684
 
2685
2685
  def setReplaceText(self, text: str) -> None:
2686
2686
  """Set the replace text."""
2687
2687
  self.showReplace.setChecked(True)
2688
2688
  self.replaceBox.setFocus()
2689
2689
  self.replaceBox.setText(text)
2690
- return
2691
2690
 
2692
2691
  def setResultCount(self, currRes: int | None, resCount: int | None) -> None:
2693
2692
  """Set the count values for the current search."""
@@ -2702,7 +2701,6 @@ class GuiDocEditSearch(QFrame):
2702
2701
  self.resultLabel.setMinimumWidth(minWidth)
2703
2702
  self.adjustSize()
2704
2703
  self.docEditor.updateDocMargins()
2705
- return
2706
2704
 
2707
2705
  ##
2708
2706
  # Methods
@@ -2718,36 +2716,35 @@ class GuiDocEditSearch(QFrame):
2718
2716
  self.resultLabel.setMinimumWidth(
2719
2717
  SHARED.theme.getTextWidth("?/?", SHARED.theme.guiFontSmall)
2720
2718
  )
2721
- return
2722
2719
 
2723
2720
  def updateTheme(self) -> None:
2724
2721
  """Update theme elements."""
2725
- palette = QApplication.palette()
2722
+ logger.debug("Theme Update: GuiDocEditSearch")
2726
2723
 
2724
+ palette = QApplication.palette()
2727
2725
  self.setPalette(palette)
2728
2726
  self.searchBox.setPalette(palette)
2729
2727
  self.replaceBox.setPalette(palette)
2730
2728
 
2731
2729
  # Set icons
2732
- self.toggleCase.setIcon(SHARED.theme.getIcon("search_case"))
2733
- self.toggleWord.setIcon(SHARED.theme.getIcon("search_word"))
2734
- self.toggleRegEx.setIcon(SHARED.theme.getIcon("search_regex"))
2735
- self.toggleLoop.setIcon(SHARED.theme.getIcon("search_loop"))
2736
- self.toggleProject.setIcon(SHARED.theme.getIcon("search_project"))
2737
- self.toggleMatchCap.setIcon(SHARED.theme.getIcon("search_preserve"))
2738
- self.cancelSearch.setIcon(SHARED.theme.getIcon("search_cancel"))
2739
- self.searchButton.setThemeIcon("search", "green")
2740
- self.replaceButton.setThemeIcon("search_replace", "green")
2730
+ self.toggleCase.setIcon(SHARED.theme.getIcon("search_case", "tool"))
2731
+ self.toggleWord.setIcon(SHARED.theme.getIcon("search_word", "tool"))
2732
+ self.toggleRegEx.setIcon(SHARED.theme.getIcon("search_regex", "tool"))
2733
+ self.toggleLoop.setIcon(SHARED.theme.getIcon("search_loop", "tool"))
2734
+ self.toggleProject.setIcon(SHARED.theme.getIcon("search_project", "tool"))
2735
+ self.toggleMatchCap.setIcon(SHARED.theme.getIcon("search_preserve", "tool"))
2736
+ self.cancelSearch.setIcon(SHARED.theme.getIcon("search_cancel", "tool"))
2737
+ self.searchButton.setThemeIcon("search", "action")
2738
+ self.replaceButton.setThemeIcon("search_replace", "apply")
2739
+ self.showReplace.setThemeIcon("unfold", "default")
2741
2740
 
2742
2741
  # Set stylesheets
2743
2742
  self.searchOpt.setStyleSheet("QToolBar {padding: 0;}")
2744
2743
  self.showReplace.setStyleSheet("QToolButton {border: none; background: transparent;}")
2745
2744
 
2746
- return
2747
-
2748
2745
  def cycleFocus(self) -> bool:
2749
- """The tab key just alternates focus between the two input
2750
- boxes, if the replace box is visible.
2746
+ """Cycle focus on tab key press. This just alternates focus
2747
+ between the two input boxes, if the replace box is visible.
2751
2748
  """
2752
2749
  if self.searchBox.hasFocus():
2753
2750
  self.replaceBox.setFocus()
@@ -2772,7 +2769,6 @@ class GuiDocEditSearch(QFrame):
2772
2769
  self.setVisible(False)
2773
2770
  self.docEditor.updateDocMargins()
2774
2771
  self.docEditor.setFocus()
2775
- return
2776
2772
 
2777
2773
  ##
2778
2774
  # Private Slots
@@ -2782,13 +2778,11 @@ class GuiDocEditSearch(QFrame):
2782
2778
  def _doSearch(self) -> None:
2783
2779
  """Call the search action function for the document editor."""
2784
2780
  self.docEditor.findNext(goBack=(QApplication.keyboardModifiers() == QtModShift))
2785
- return
2786
2781
 
2787
2782
  @pyqtSlot()
2788
2783
  def _doReplace(self) -> None:
2789
2784
  """Call the replace action function for the document editor."""
2790
2785
  self.docEditor.replaceNext()
2791
- return
2792
2786
 
2793
2787
  @pyqtSlot(bool)
2794
2788
  def _doToggleReplace(self, state: bool) -> None:
@@ -2797,43 +2791,36 @@ class GuiDocEditSearch(QFrame):
2797
2791
  self.replaceButton.setVisible(state)
2798
2792
  self.adjustSize()
2799
2793
  self.docEditor.updateDocMargins()
2800
- return
2801
2794
 
2802
2795
  @pyqtSlot(bool)
2803
2796
  def _doToggleCase(self, state: bool) -> None:
2804
2797
  """Enable/disable case sensitive mode."""
2805
2798
  CONFIG.searchCase = state
2806
- return
2807
2799
 
2808
2800
  @pyqtSlot(bool)
2809
2801
  def _doToggleWord(self, state: bool) -> None:
2810
2802
  """Enable/disable whole word search mode."""
2811
2803
  CONFIG.searchWord = state
2812
- return
2813
2804
 
2814
2805
  @pyqtSlot(bool)
2815
2806
  def _doToggleRegEx(self, state: bool) -> None:
2816
2807
  """Enable/disable regular expression search mode."""
2817
2808
  CONFIG.searchRegEx = state
2818
- return
2819
2809
 
2820
2810
  @pyqtSlot(bool)
2821
2811
  def _doToggleLoop(self, state: bool) -> None:
2822
2812
  """Enable/disable looping the search."""
2823
2813
  CONFIG.searchLoop = state
2824
- return
2825
2814
 
2826
2815
  @pyqtSlot(bool)
2827
2816
  def _doToggleProject(self, state: bool) -> None:
2828
2817
  """Enable/disable continuing search in next project file."""
2829
2818
  CONFIG.searchNextFile = state
2830
- return
2831
2819
 
2832
2820
  @pyqtSlot(bool)
2833
2821
  def _doToggleMatchCap(self, state: bool) -> None:
2834
2822
  """Enable/disable preserving capitalisation when replacing."""
2835
2823
  CONFIG.searchMatchCap = state
2836
- return
2837
2824
 
2838
2825
  ##
2839
2826
  # Internal Functions
@@ -2849,11 +2836,10 @@ class GuiDocEditSearch(QFrame):
2849
2836
  palette.text().color() if isValid else SHARED.theme.errorText
2850
2837
  )
2851
2838
  self.searchBox.setPalette(palette)
2852
- return
2853
2839
 
2854
2840
 
2855
2841
  class GuiDocEditHeader(QWidget):
2856
- """The Embedded Document Header
2842
+ """The Embedded Document Header.
2857
2843
 
2858
2844
  Only used by DocEditor, and is at a fixed position in the
2859
2845
  QTextEdit's viewport.
@@ -2944,8 +2930,6 @@ class GuiDocEditHeader(QWidget):
2944
2930
 
2945
2931
  logger.debug("Ready: GuiDocEditHeader")
2946
2932
 
2947
- return
2948
-
2949
2933
  ##
2950
2934
  # Methods
2951
2935
  ##
@@ -2962,7 +2946,6 @@ class GuiDocEditHeader(QWidget):
2962
2946
  self.searchButton.setVisible(False)
2963
2947
  self.closeButton.setVisible(False)
2964
2948
  self.minmaxButton.setVisible(False)
2965
- return
2966
2949
 
2967
2950
  def setOutline(self, data: dict[int, str]) -> None:
2968
2951
  """Set the document outline dataset."""
@@ -2974,21 +2957,21 @@ class GuiDocEditHeader(QWidget):
2974
2957
  action.triggered.connect(qtLambda(self._gotoBlock, number))
2975
2958
  self._docOutline = data
2976
2959
  logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart))
2977
- return
2978
2960
 
2979
2961
  def updateFont(self) -> None:
2980
2962
  """Update the font settings."""
2981
2963
  self.setFont(SHARED.theme.guiFont)
2982
2964
  self.itemTitle.setFont(SHARED.theme.guiFontSmall)
2983
- return
2984
2965
 
2985
2966
  def updateTheme(self) -> None:
2986
2967
  """Update theme elements."""
2987
- self.tbButton.setThemeIcon("fmt_toolbar", "blue")
2988
- self.outlineButton.setThemeIcon("list", "blue")
2989
- self.searchButton.setThemeIcon("search", "blue")
2990
- self.minmaxButton.setThemeIcon("maximise", "blue")
2991
- self.closeButton.setThemeIcon("close", "red")
2968
+ logger.debug("Theme Update: GuiDocEditHeader")
2969
+
2970
+ self.tbButton.setThemeIcon("fmt_toolbar", "action")
2971
+ self.outlineButton.setThemeIcon("list", "action")
2972
+ self.searchButton.setThemeIcon("search", "action")
2973
+ self.minmaxButton.setThemeIcon("maximise", "action")
2974
+ self.closeButton.setThemeIcon("close", "reject")
2992
2975
 
2993
2976
  buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
2994
2977
  self.tbButton.setStyleSheet(buttonStyle)
@@ -2999,8 +2982,6 @@ class GuiDocEditHeader(QWidget):
2999
2982
 
3000
2983
  self.matchColors()
3001
2984
 
3002
- return
3003
-
3004
2985
  def matchColors(self) -> None:
3005
2986
  """Update the colours of the widget to match those of the syntax
3006
2987
  theme rather than the main GUI.
@@ -3014,12 +2995,10 @@ class GuiDocEditHeader(QWidget):
3014
2995
  self.itemTitle.setTextColors(
3015
2996
  color=palette.windowText().color(), faded=SHARED.theme.fadedText
3016
2997
  )
3017
- return
3018
2998
 
3019
2999
  def changeFocusState(self, state: bool) -> None:
3020
3000
  """Toggle focus state."""
3021
3001
  self.itemTitle.setColorState(state)
3022
- return
3023
3002
 
3024
3003
  def setHandle(self, tHandle: str) -> None:
3025
3004
  """Set the document title from the handle, or alternatively, set
@@ -3040,8 +3019,6 @@ class GuiDocEditHeader(QWidget):
3040
3019
  self.closeButton.setVisible(True)
3041
3020
  self.minmaxButton.setVisible(True)
3042
3021
 
3043
- return
3044
-
3045
3022
  ##
3046
3023
  # Private Slots
3047
3024
  ##
@@ -3051,19 +3028,16 @@ class GuiDocEditHeader(QWidget):
3051
3028
  """Trigger the close editor on the main window."""
3052
3029
  self.clearHeader()
3053
3030
  self.closeDocumentRequest.emit()
3054
- return
3055
3031
 
3056
3032
  @pyqtSlot(int)
3057
3033
  def _gotoBlock(self, blockNumber: int) -> None:
3058
3034
  """Move cursor to a specific heading."""
3059
3035
  self.docEditor.setCursorLine(blockNumber + 1)
3060
- return
3061
3036
 
3062
3037
  @pyqtSlot(bool)
3063
3038
  def _focusModeChanged(self, focusMode: bool) -> None:
3064
3039
  """Update minimise/maximise icon of the Focus Mode button."""
3065
- self.minmaxButton.setThemeIcon("minimise" if focusMode else "maximise", "blue")
3066
- return
3040
+ self.minmaxButton.setThemeIcon("minimise" if focusMode else "maximise", "action")
3067
3041
 
3068
3042
  ##
3069
3043
  # Events
@@ -3075,11 +3049,10 @@ class GuiDocEditHeader(QWidget):
3075
3049
  """
3076
3050
  if event.button() == QtMouseLeft:
3077
3051
  self.docEditor.requestProjectItemSelected.emit(self._docHandle or "", True)
3078
- return
3079
3052
 
3080
3053
 
3081
3054
  class GuiDocEditFooter(QWidget):
3082
- """The Embedded Document Footer
3055
+ """The Embedded Document Footer.
3083
3056
 
3084
3057
  Only used by DocEditor, and is at a fixed position in the
3085
3058
  QTextEdit's viewport.
@@ -3164,8 +3137,6 @@ class GuiDocEditFooter(QWidget):
3164
3137
 
3165
3138
  logger.debug("Ready: GuiDocEditFooter")
3166
3139
 
3167
- return
3168
-
3169
3140
  ##
3170
3141
  # Methods
3171
3142
  ##
@@ -3175,7 +3146,6 @@ class GuiDocEditFooter(QWidget):
3175
3146
  self._trMainCount = trStats(nwLabels.STATS_DISPLAY[
3176
3147
  nwStats.CHARS if CONFIG.useCharCount else nwStats.WORDS
3177
3148
  ])
3178
- return
3179
3149
 
3180
3150
  def updateFont(self) -> None:
3181
3151
  """Update the font settings."""
@@ -3183,15 +3153,15 @@ class GuiDocEditFooter(QWidget):
3183
3153
  self.statusText.setFont(SHARED.theme.guiFontSmall)
3184
3154
  self.linesText.setFont(SHARED.theme.guiFontSmall)
3185
3155
  self.wordsText.setFont(SHARED.theme.guiFontSmall)
3186
- return
3187
3156
 
3188
3157
  def updateTheme(self) -> None:
3189
3158
  """Update theme elements."""
3159
+ logger.debug("Theme Update: GuiDocEditFooter")
3160
+
3190
3161
  iPx = round(0.9*SHARED.theme.baseIconHeight)
3191
3162
  self.linesIcon.setPixmap(SHARED.theme.getPixmap("lines", (iPx, iPx)))
3192
3163
  self.wordsIcon.setPixmap(SHARED.theme.getPixmap("stats", (iPx, iPx)))
3193
3164
  self.matchColors()
3194
- return
3195
3165
 
3196
3166
  def matchColors(self) -> None:
3197
3167
  """Update the colours of the widget to match those of the syntax
@@ -3209,8 +3179,6 @@ class GuiDocEditFooter(QWidget):
3209
3179
  self.linesText.setPalette(palette)
3210
3180
  self.wordsText.setPalette(palette)
3211
3181
 
3212
- return
3213
-
3214
3182
  def setHandle(self, tHandle: str | None) -> None:
3215
3183
  """Set the handle that will populate the footer's data."""
3216
3184
  self._docHandle = tHandle
@@ -3223,8 +3191,6 @@ class GuiDocEditFooter(QWidget):
3223
3191
  self.updateInfo()
3224
3192
  self.updateMainCount(0, False)
3225
3193
 
3226
- return
3227
-
3228
3194
  def updateInfo(self) -> None:
3229
3195
  """Update the content of text labels."""
3230
3196
  if self._tItem is None:
@@ -3239,8 +3205,6 @@ class GuiDocEditFooter(QWidget):
3239
3205
  self.statusIcon.setPixmap(sIcon)
3240
3206
  self.statusText.setText(sText)
3241
3207
 
3242
- return
3243
-
3244
3208
  def updateLineCount(self, cursor: QTextCursor) -> None:
3245
3209
  """Update the line and document position counter."""
3246
3210
  if document := cursor.document():
@@ -3250,7 +3214,6 @@ class GuiDocEditFooter(QWidget):
3250
3214
  self.linesText.setText(
3251
3215
  self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %")
3252
3216
  )
3253
- return
3254
3217
 
3255
3218
  def updateMainCount(self, count: int, selection: bool) -> None:
3256
3219
  """Update main counter information."""
@@ -3263,4 +3226,3 @@ class GuiDocEditFooter(QWidget):
3263
3226
  else:
3264
3227
  text = self._trMainCount.format("0", "+0")
3265
3228
  self.wordsText.setText(text)
3266
- return