novelWriter 2.4.4__py3-none-any.whl → 2.5__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 (122) hide show
  1. {novelWriter-2.4.4.dist-info → novelWriter-2.5.dist-info}/METADATA +4 -5
  2. {novelWriter-2.4.4.dist-info → novelWriter-2.5.dist-info}/RECORD +121 -111
  3. {novelWriter-2.4.4.dist-info → novelWriter-2.5.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +33 -39
  5. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  6. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  7. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  8. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  9. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  10. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  11. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  12. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  13. novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
  14. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  15. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  16. novelwriter/assets/i18n/project_en_GB.json +1 -0
  17. novelwriter/assets/i18n/project_pl_PL.json +116 -0
  18. novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
  19. novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
  20. novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
  21. novelwriter/assets/icons/typicons_light/icons.conf +2 -0
  22. novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
  23. novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
  24. novelwriter/assets/manual.pdf +0 -0
  25. novelwriter/assets/sample.zip +0 -0
  26. novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
  27. novelwriter/assets/syntax/default_dark.conf +32 -18
  28. novelwriter/assets/syntax/default_light.conf +24 -10
  29. novelwriter/assets/syntax/dracula.conf +44 -0
  30. novelwriter/assets/syntax/grey_dark.conf +5 -4
  31. novelwriter/assets/syntax/grey_light.conf +5 -4
  32. novelwriter/assets/syntax/light_owl.conf +7 -6
  33. novelwriter/assets/syntax/night_owl.conf +7 -6
  34. novelwriter/assets/syntax/snazzy.conf +42 -0
  35. novelwriter/assets/syntax/solarized_dark.conf +4 -3
  36. novelwriter/assets/syntax/solarized_light.conf +4 -3
  37. novelwriter/assets/syntax/tango.conf +27 -11
  38. novelwriter/assets/syntax/tomorrow.conf +6 -5
  39. novelwriter/assets/syntax/tomorrow_night.conf +7 -6
  40. novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
  41. novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
  42. novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
  43. novelwriter/assets/text/credits_en.htm +52 -41
  44. novelwriter/assets/themes/cyberpunk_night.conf +3 -0
  45. novelwriter/assets/themes/default_dark.conf +2 -0
  46. novelwriter/assets/themes/default_light.conf +2 -0
  47. novelwriter/assets/themes/dracula.conf +48 -0
  48. novelwriter/assets/themes/solarized_dark.conf +2 -0
  49. novelwriter/assets/themes/solarized_light.conf +2 -0
  50. novelwriter/common.py +33 -12
  51. novelwriter/config.py +184 -98
  52. novelwriter/constants.py +47 -35
  53. novelwriter/core/buildsettings.py +68 -69
  54. novelwriter/core/coretools.py +5 -23
  55. novelwriter/core/docbuild.py +52 -40
  56. novelwriter/core/document.py +3 -5
  57. novelwriter/core/index.py +115 -45
  58. novelwriter/core/item.py +8 -19
  59. novelwriter/core/options.py +2 -4
  60. novelwriter/core/project.py +37 -61
  61. novelwriter/core/projectdata.py +1 -3
  62. novelwriter/core/projectxml.py +12 -15
  63. novelwriter/core/sessions.py +3 -5
  64. novelwriter/core/spellcheck.py +4 -9
  65. novelwriter/core/status.py +211 -164
  66. novelwriter/core/storage.py +0 -8
  67. novelwriter/core/tohtml.py +139 -105
  68. novelwriter/core/tokenizer.py +278 -122
  69. novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
  70. novelwriter/core/toodt.py +257 -166
  71. novelwriter/core/toqdoc.py +419 -0
  72. novelwriter/core/tree.py +5 -7
  73. novelwriter/dialogs/about.py +11 -18
  74. novelwriter/dialogs/docmerge.py +17 -19
  75. novelwriter/dialogs/docsplit.py +17 -19
  76. novelwriter/dialogs/editlabel.py +6 -10
  77. novelwriter/dialogs/preferences.py +200 -164
  78. novelwriter/dialogs/projectsettings.py +225 -189
  79. novelwriter/dialogs/quotes.py +12 -9
  80. novelwriter/dialogs/wordlist.py +9 -15
  81. novelwriter/enum.py +35 -30
  82. novelwriter/error.py +8 -15
  83. novelwriter/extensions/configlayout.py +55 -21
  84. novelwriter/extensions/eventfilters.py +1 -5
  85. novelwriter/extensions/modified.py +58 -14
  86. novelwriter/extensions/novelselector.py +1 -3
  87. novelwriter/extensions/pagedsidebar.py +9 -12
  88. novelwriter/extensions/{circularprogress.py → progressbars.py} +30 -8
  89. novelwriter/extensions/statusled.py +40 -26
  90. novelwriter/extensions/switch.py +4 -6
  91. novelwriter/extensions/switchbox.py +7 -6
  92. novelwriter/extensions/versioninfo.py +3 -9
  93. novelwriter/gui/doceditor.py +120 -139
  94. novelwriter/gui/dochighlight.py +231 -186
  95. novelwriter/gui/docviewer.py +69 -108
  96. novelwriter/gui/docviewerpanel.py +3 -10
  97. novelwriter/gui/editordocument.py +1 -3
  98. novelwriter/gui/itemdetails.py +7 -11
  99. novelwriter/gui/mainmenu.py +22 -18
  100. novelwriter/gui/noveltree.py +11 -24
  101. novelwriter/gui/outline.py +15 -26
  102. novelwriter/gui/projtree.py +35 -60
  103. novelwriter/gui/search.py +10 -3
  104. novelwriter/gui/sidebar.py +2 -6
  105. novelwriter/gui/statusbar.py +29 -37
  106. novelwriter/gui/theme.py +26 -48
  107. novelwriter/guimain.py +162 -160
  108. novelwriter/shared.py +36 -32
  109. novelwriter/text/patterns.py +113 -0
  110. novelwriter/tools/dictionaries.py +10 -20
  111. novelwriter/tools/lipsum.py +10 -16
  112. novelwriter/tools/manusbuild.py +9 -11
  113. novelwriter/tools/manuscript.py +71 -145
  114. novelwriter/tools/manussettings.py +71 -75
  115. novelwriter/tools/noveldetails.py +16 -21
  116. novelwriter/tools/welcome.py +21 -26
  117. novelwriter/tools/writingstats.py +9 -12
  118. novelwriter/types.py +49 -4
  119. novelwriter/extensions/simpleprogress.py +0 -55
  120. {novelWriter-2.4.4.dist-info → novelWriter-2.5.dist-info}/LICENSE.md +0 -0
  121. {novelWriter-2.4.4.dist-info → novelWriter-2.5.dist-info}/entry_points.txt +0 -0
  122. {novelWriter-2.4.4.dist-info → novelWriter-2.5.dist-info}/top_level.txt +0 -0
@@ -30,30 +30,28 @@ from __future__ import annotations
30
30
  import csv
31
31
  import logging
32
32
 
33
- from time import time
34
33
  from enum import Enum
34
+ from time import time
35
35
 
36
- from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QT_TRANSLATE_NOOP
36
+ from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, pyqtSignal, pyqtSlot
37
37
  from PyQt5.QtWidgets import (
38
38
  QAbstractItemView, QAction, QFileDialog, QFrame, QGridLayout, QGroupBox,
39
- QHBoxLayout, QLabel, QMenu, QScrollArea, QSizePolicy, QSplitter, QToolBar,
40
- QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
39
+ QHBoxLayout, QLabel, QMenu, QScrollArea, QSplitter, QToolBar, QToolButton,
40
+ QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
41
41
  )
42
42
 
43
43
  from novelwriter import CONFIG, SHARED
44
- from novelwriter.enum import (
45
- nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
46
- )
47
- from novelwriter.error import logException
48
44
  from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe
49
- from novelwriter.constants import nwHeaders, trConst, nwKeyWords, nwLabels
45
+ from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
46
+ from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
47
+ from novelwriter.error import logException
50
48
  from novelwriter.extensions.configlayout import NColourLabel
51
49
  from novelwriter.extensions.novelselector import NovelSelector
52
50
  from novelwriter.types import (
53
- QtAlignLeftTop, QtAlignRight, QtAlignRightTop, QtDecoration, QtUserRole
51
+ QtAlignLeftTop, QtAlignRight, QtAlignRightTop, QtDecoration,
52
+ QtSizeExpanding, QtUserRole
54
53
  )
55
54
 
56
-
57
55
  logger = logging.getLogger(__name__)
58
56
 
59
57
 
@@ -196,8 +194,6 @@ class GuiOutlineView(QWidget):
196
194
  self.outlineTree.refreshTree(rootHandle=(tHandle or None), overRide=True)
197
195
  return
198
196
 
199
- # END Class GuiOutlineView
200
-
201
197
 
202
198
  class GuiOutlineToolBar(QToolBar):
203
199
 
@@ -215,11 +211,11 @@ class GuiOutlineToolBar(QToolBar):
215
211
  self.setContentsMargins(0, 0, 0, 0)
216
212
 
217
213
  stretch = QWidget(self)
218
- stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
214
+ stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
219
215
 
220
216
  # Novel Selector
221
217
  self.novelLabel = NColourLabel(
222
- self.tr("Outline of"), parent=self, scale=NColourLabel.HEADER_SCALE, bold=True
218
+ self.tr("Outline of"), self, scale=NColourLabel.HEADER_SCALE, bold=True
223
219
  )
224
220
  self.novelLabel.setContentsMargins(0, 0, CONFIG.pxInt(12), 0)
225
221
 
@@ -272,6 +268,7 @@ class GuiOutlineToolBar(QToolBar):
272
268
  self.aExport.setIcon(SHARED.theme.getIcon("export"))
273
269
  self.tbColumns.setIcon(SHARED.theme.getIcon("menu"))
274
270
  self.tbColumns.setStyleSheet("QToolButton::menu-indicator {image: none;}")
271
+ self.novelLabel.setTextColors(color=self.palette().windowText().color())
275
272
  return
276
273
 
277
274
  def populateNovelList(self) -> None:
@@ -311,8 +308,6 @@ class GuiOutlineToolBar(QToolBar):
311
308
  self.outlineExportRequest.emit()
312
309
  return
313
310
 
314
- # END Class GuiOutlineToolBar
315
-
316
311
 
317
312
  class GuiOutlineTree(QTreeWidget):
318
313
 
@@ -529,12 +524,12 @@ class GuiOutlineTree(QTreeWidget):
529
524
  @pyqtSlot()
530
525
  def exportOutline(self) -> None:
531
526
  """Export the outline as a CSV file."""
532
- path = CONFIG.lastPath() / f"{makeFileNameSafe(SHARED.project.data.name)}.csv"
527
+ path = CONFIG.lastPath("outline") / f"{makeFileNameSafe(SHARED.project.data.name)}.csv"
533
528
  path, _ = QFileDialog.getSaveFileName(
534
529
  self, self.tr("Save Outline As"), str(path), formatFileFilter(["*.csv", "*"])
535
530
  )
536
531
  if path:
537
- CONFIG.setLastPath(path)
532
+ CONFIG.setLastPath("outline", path)
538
533
  logger.info("Writing CSV file: %s", path)
539
534
  cols = [col for col in self._treeOrder if not self._colHidden[col]]
540
535
  order = [self._colIdx[col] for col in cols]
@@ -728,8 +723,6 @@ class GuiOutlineTree(QTreeWidget):
728
723
 
729
724
  return
730
725
 
731
- # END Class GuiOutlineTree
732
-
733
726
 
734
727
  class GuiOutlineHeaderMenu(QMenu):
735
728
 
@@ -772,8 +765,6 @@ class GuiOutlineHeaderMenu(QMenu):
772
765
 
773
766
  return
774
767
 
775
- # END Class GuiOutlineHeaderMenu
776
-
777
768
 
778
769
  class GuiOutlineDetails(QScrollArea):
779
770
 
@@ -1048,7 +1039,7 @@ class GuiOutlineDetails(QScrollArea):
1048
1039
  self.titleLabel.setText(self.tr(self.LVL_MAP.get(novIdx.level, "H1")))
1049
1040
  self.titleValue.setText(novIdx.title)
1050
1041
 
1051
- itemStatus, _ = nwItem.getImportStatus(incIcon=False)
1042
+ itemStatus, _ = nwItem.getImportStatus()
1052
1043
 
1053
1044
  self.fileValue.setText(nwItem.itemName)
1054
1045
  self.itemValue.setText(itemStatus)
@@ -1104,5 +1095,3 @@ class GuiOutlineDetails(QScrollArea):
1104
1095
  return ", ".join(
1105
1096
  [f"<a href='{tag}'>{tag}</a>" for tag in refs.get(key, [])]
1106
1097
  )
1107
-
1108
- # END Class GuiOutlineDetails
@@ -30,14 +30,12 @@ import logging
30
30
 
31
31
  from enum import Enum
32
32
  from time import time
33
- from typing import TYPE_CHECKING
34
33
 
35
34
  from PyQt5.QtCore import QPoint, Qt, QTimer, pyqtSignal, pyqtSlot
36
35
  from PyQt5.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
37
36
  from PyQt5.QtWidgets import (
38
- QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
39
- QLabel, QMenu, QShortcut, QSizePolicy, QTreeWidget, QTreeWidgetItem,
40
- QVBoxLayout, QWidget
37
+ QAbstractItemView, QAction, QFrame, QHBoxLayout, QHeaderView, QLabel,
38
+ QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
41
39
  )
42
40
 
43
41
  from novelwriter import CONFIG, SHARED
@@ -52,10 +50,10 @@ from novelwriter.dialogs.projectsettings import GuiProjectSettings
52
50
  from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType
53
51
  from novelwriter.extensions.modified import NIconToolButton
54
52
  from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
55
- from novelwriter.types import QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtUserRole
56
-
57
- if TYPE_CHECKING: # pragma: no cover
58
- from novelwriter.guimain import GuiMain
53
+ from novelwriter.types import (
54
+ QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtSizeExpanding,
55
+ QtUserRole
56
+ )
59
57
 
60
58
  logger = logging.getLogger(__name__)
61
59
 
@@ -78,10 +76,8 @@ class GuiProjectView(QWidget):
78
76
  # Requests for the main GUI
79
77
  projectSettingsRequest = pyqtSignal(int)
80
78
 
81
- def __init__(self, mainGui: GuiMain) -> None:
82
- super().__init__(parent=mainGui)
83
-
84
- self.mainGui = mainGui
79
+ def __init__(self, parent: QWidget) -> None:
80
+ super().__init__(parent=parent)
85
81
 
86
82
  # Build GUI
87
83
  self.projTree = GuiProjectTree(self)
@@ -246,8 +242,6 @@ class GuiProjectView(QWidget):
246
242
  self.projTree.createNewNote(tag, itemClass)
247
243
  return
248
244
 
249
- # END Class GuiProjectView
250
-
251
245
 
252
246
  class GuiProjectToolBar(QWidget):
253
247
 
@@ -260,7 +254,6 @@ class GuiProjectToolBar(QWidget):
260
254
 
261
255
  self.projView = projView
262
256
  self.projTree = projView.projTree
263
- self.mainGui = projView.mainGui
264
257
 
265
258
  iSz = SHARED.theme.baseIconSize
266
259
  mPx = CONFIG.pxInt(2)
@@ -272,7 +265,7 @@ class GuiProjectToolBar(QWidget):
272
265
  self.viewLabel = QLabel(self.tr("Project Content"), self)
273
266
  self.viewLabel.setFont(SHARED.theme.guiFontB)
274
267
  self.viewLabel.setContentsMargins(0, 0, 0, 0)
275
- self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
268
+ self.viewLabel.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
276
269
 
277
270
  # Quick Links
278
271
  self.mQuick = QMenu(self)
@@ -474,8 +467,6 @@ class GuiProjectToolBar(QWidget):
474
467
 
475
468
  return
476
469
 
477
- # END Class GuiProjectToolBar
478
-
479
470
 
480
471
  class GuiProjectTree(QTreeWidget):
481
472
 
@@ -496,7 +487,6 @@ class GuiProjectTree(QTreeWidget):
496
487
  logger.debug("Create: GuiProjectTree")
497
488
 
498
489
  self.projView = projView
499
- self.mainGui = projView.mainGui
500
490
 
501
491
  # Internal Variables
502
492
  self._treeMap: dict[str, QTreeWidgetItem] = {}
@@ -1007,8 +997,7 @@ class GuiProjectTree(QTreeWidget):
1007
997
  trItemP.takeChild(tIndex)
1008
998
 
1009
999
  for dHandle in reversed(self.getTreeFromHandle(tHandle)):
1010
- if self.mainGui.docEditor.docHandle == dHandle:
1011
- self.mainGui.closeDocument()
1000
+ SHARED.closeEditor(dHandle)
1012
1001
  SHARED.project.removeItem(dHandle)
1013
1002
  self._treeMap.pop(dHandle, None)
1014
1003
 
@@ -1031,7 +1020,7 @@ class GuiProjectTree(QTreeWidget):
1031
1020
  if trItem is None or nwItem is None:
1032
1021
  return
1033
1022
 
1034
- itemStatus, statusIcon = nwItem.getImportStatus(incIcon=True)
1023
+ itemStatus, statusIcon = nwItem.getImportStatus()
1035
1024
  hLevel = nwItem.mainHeading
1036
1025
  itemIcon = SHARED.theme.getItemIcon(
1037
1026
  nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
@@ -1407,19 +1396,15 @@ class GuiProjectTree(QTreeWidget):
1407
1396
  if not newFile:
1408
1397
  itemList.remove(tHandle)
1409
1398
 
1410
- dlgMerge = GuiDocMerge(self.mainGui, tHandle, itemList)
1411
- dlgMerge.exec()
1412
-
1413
- if dlgMerge.result() == QDialog.DialogCode.Accepted:
1414
-
1415
- mrgData = dlgMerge.getData()
1416
- mrgList = mrgData.get("finalItems", [])
1417
- if not mrgList:
1399
+ data, status = GuiDocMerge.getData(SHARED.mainGui, tHandle, itemList)
1400
+ if status:
1401
+ items = data.get("finalItems", [])
1402
+ if not items:
1418
1403
  SHARED.info(self.tr("No documents selected for merging."))
1419
1404
  return False
1420
1405
 
1421
1406
  # Save the open document first, in case it's part of merge
1422
- self.mainGui.saveDocument()
1407
+ SHARED.saveEditor()
1423
1408
 
1424
1409
  # Create merge object, and append docs
1425
1410
  docMerger = DocMerger(SHARED.project)
@@ -1434,7 +1419,7 @@ class GuiProjectTree(QTreeWidget):
1434
1419
  else:
1435
1420
  return False
1436
1421
 
1437
- for sHandle in mrgList:
1422
+ for sHandle in items:
1438
1423
  docMerger.appendText(sHandle, True, mLabel)
1439
1424
 
1440
1425
  if not docMerger.writeTargetDoc():
@@ -1448,10 +1433,11 @@ class GuiProjectTree(QTreeWidget):
1448
1433
  if newFile:
1449
1434
  self.revealNewTreeItem(mHandle, nHandle=tHandle, wordCount=True)
1450
1435
 
1451
- self.mainGui.openDocument(mHandle, doScroll=True)
1436
+ self.projView.openDocumentRequest.emit(mHandle, nwDocMode.EDIT, "", False)
1437
+ self.projView.setSelectedHandle(mHandle, doScroll=True)
1452
1438
 
1453
- if mrgData.get("moveToTrash", False):
1454
- for sHandle in reversed(mrgData.get("finalItems", [])):
1439
+ if data.get("moveToTrash", False):
1440
+ for sHandle in reversed(data.get("finalItems", [])):
1455
1441
  trItem = self._getTreeItem(sHandle)
1456
1442
  if isinstance(trItem, QTreeWidgetItem) and trItem.childCount() == 0:
1457
1443
  self.moveItemToTrash(sHandle, askFirst=False, flush=False)
@@ -1477,16 +1463,11 @@ class GuiProjectTree(QTreeWidget):
1477
1463
  logger.error("Only valid document items can be split")
1478
1464
  return False
1479
1465
 
1480
- dlgSplit = GuiDocSplit(self.mainGui, tHandle)
1481
- dlgSplit.exec()
1482
-
1483
- if dlgSplit.result() == QDialog.DialogCode.Accepted:
1484
-
1485
- splitData, splitText = dlgSplit.getData()
1486
-
1487
- headerList = splitData.get("headerList", [])
1488
- intoFolder = splitData.get("intoFolder", False)
1489
- docHierarchy = splitData.get("docHierarchy", False)
1466
+ data, text, status = GuiDocSplit.getData(SHARED.mainGui, tHandle)
1467
+ if status:
1468
+ headerList = data.get("headerList", [])
1469
+ intoFolder = data.get("intoFolder", False)
1470
+ docHierarchy = data.get("docHierarchy", False)
1490
1471
 
1491
1472
  docSplit = DocSplitter(SHARED.project, tHandle)
1492
1473
  if intoFolder:
@@ -1496,7 +1477,7 @@ class GuiProjectTree(QTreeWidget):
1496
1477
  else:
1497
1478
  docSplit.setParentItem(tItem.itemParent)
1498
1479
 
1499
- docSplit.splitDocument(headerList, splitText)
1480
+ docSplit.splitDocument(headerList, text)
1500
1481
  for writeOk, dHandle, nHandle in docSplit.writeDocuments(docHierarchy):
1501
1482
  SHARED.project.index.reIndexHandle(dHandle)
1502
1483
  self.revealNewTreeItem(dHandle, nHandle=nHandle, wordCount=True)
@@ -1507,7 +1488,7 @@ class GuiProjectTree(QTreeWidget):
1507
1488
  info=docSplit.getError()
1508
1489
  )
1509
1490
 
1510
- if splitData.get("moveToTrash", False):
1491
+ if data.get("moveToTrash", False):
1511
1492
  self.moveItemToTrash(tHandle, askFirst=False, flush=True)
1512
1493
 
1513
1494
  self.saveTreeOrder()
@@ -1656,8 +1637,6 @@ class GuiProjectTree(QTreeWidget):
1656
1637
 
1657
1638
  return
1658
1639
 
1659
- # END Class GuiProjectTree
1660
-
1661
1640
 
1662
1641
  class _UpdatableMenu(QMenu):
1663
1642
 
@@ -1721,8 +1700,6 @@ class _UpdatableMenu(QMenu):
1721
1700
  self.menuItemTriggered.emit(str(action.data()))
1722
1701
  return
1723
1702
 
1724
- # END Class _UpdatableMenu
1725
-
1726
1703
 
1727
1704
  class _TreeContextMenu(QMenu):
1728
1705
 
@@ -1828,7 +1805,7 @@ class _TreeContextMenu(QMenu):
1828
1805
 
1829
1806
  def _itemHeader(self) -> None:
1830
1807
  """Check if there is a header that can be used for rename."""
1831
- SHARED.ensureEditorSaved(self._handle)
1808
+ SHARED.saveEditor()
1832
1809
  if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"):
1833
1810
  action = self.addAction(self.tr("Rename to Heading"))
1834
1811
  action.triggered.connect(
@@ -1854,11 +1831,11 @@ class _TreeContextMenu(QMenu):
1854
1831
  if self._item.isNovelLike():
1855
1832
  menu = self.addMenu(self.tr("Set Status to ..."))
1856
1833
  current = self._item.itemStatus
1857
- for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.items()):
1858
- name = entry["name"]
1834
+ for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.iterItems()):
1835
+ name = entry.name
1859
1836
  if not multi and current == key:
1860
1837
  name += f" ({nwUnicode.U_CHECK})"
1861
- action = menu.addAction(entry["icon"], name)
1838
+ action = menu.addAction(entry.icon, name)
1862
1839
  if multi:
1863
1840
  action.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key))
1864
1841
  else:
@@ -1871,11 +1848,11 @@ class _TreeContextMenu(QMenu):
1871
1848
  else:
1872
1849
  menu = self.addMenu(self.tr("Set Importance to ..."))
1873
1850
  current = self._item.itemImport
1874
- for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()):
1875
- name = entry["name"]
1851
+ for n, (key, entry) in enumerate(SHARED.project.data.itemImport.iterItems()):
1852
+ name = entry.name
1876
1853
  if not multi and current == key:
1877
1854
  name += f" ({nwUnicode.U_CHECK})"
1878
- action = menu.addAction(entry["icon"], name)
1855
+ action = menu.addAction(entry.icon, name)
1879
1856
  if multi:
1880
1857
  action.triggered.connect(lambda n, key=key: self._iterSetItemImport(key))
1881
1858
  else:
@@ -2075,5 +2052,3 @@ class _TreeContextMenu(QMenu):
2075
2052
  else:
2076
2053
  logger.info("Folder conversion cancelled")
2077
2054
  return
2078
-
2079
- # END Class _TreeContextMenu
novelwriter/gui/search.py CHANGED
@@ -208,6 +208,12 @@ class GuiProjectSearch(QWidget):
208
208
  self.searchResult.clear()
209
209
  return
210
210
 
211
+ def refreshCurrentSearch(self) -> None:
212
+ """Refresh the search if there is one."""
213
+ if self.searchResult.topLevelItemCount() > 0:
214
+ self._processSearch()
215
+ return
216
+
211
217
  ##
212
218
  # Events
213
219
  ##
@@ -259,7 +265,7 @@ class GuiProjectSearch(QWidget):
259
265
  if not self._blocked:
260
266
  QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
261
267
  start = time()
262
- SHARED.mainGui.saveDocument()
268
+ SHARED.saveEditor()
263
269
  self._blocked = True
264
270
  self._map = {}
265
271
  self.searchResult.clear()
@@ -298,18 +304,21 @@ class GuiProjectSearch(QWidget):
298
304
  def _toggleCase(self, state: bool) -> None:
299
305
  """Enable/disable case sensitive mode."""
300
306
  CONFIG.searchProjCase = state
307
+ self.refreshCurrentSearch()
301
308
  return
302
309
 
303
310
  @pyqtSlot(bool)
304
311
  def _toggleWord(self, state: bool) -> None:
305
312
  """Enable/disable whole word search mode."""
306
313
  CONFIG.searchProjWord = state
314
+ self.refreshCurrentSearch()
307
315
  return
308
316
 
309
317
  @pyqtSlot(bool)
310
318
  def _toggleRegEx(self, state: bool) -> None:
311
319
  """Enable/disable regular expression search mode."""
312
320
  CONFIG.searchProjRegEx = state
321
+ self.refreshCurrentSearch()
313
322
  return
314
323
 
315
324
  ##
@@ -358,5 +367,3 @@ class GuiProjectSearch(QWidget):
358
367
  QApplication.processEvents()
359
368
 
360
369
  return
361
-
362
- # END Class GuiProjectSearch
@@ -27,8 +27,8 @@ import logging
27
27
 
28
28
  from typing import TYPE_CHECKING
29
29
 
30
- from PyQt5.QtGui import QPalette
31
30
  from PyQt5.QtCore import QEvent, QPoint, QSize, pyqtSignal
31
+ from PyQt5.QtGui import QPalette
32
32
  from PyQt5.QtWidgets import QMenu, QVBoxLayout, QWidget
33
33
 
34
34
  from novelwriter import CONFIG, SHARED
@@ -58,7 +58,7 @@ class GuiSideBar(QWidget):
58
58
  iSz = QSize(iPx, iPx)
59
59
 
60
60
  self.setContentsMargins(0, 0, 0, 0)
61
- self.installEventFilter(StatusTipFilter(mainGui))
61
+ self.installEventFilter(StatusTipFilter(self.mainGui))
62
62
 
63
63
  # Buttons
64
64
  self.tbProject = NIconToolButton(self, iSz)
@@ -150,8 +150,6 @@ class GuiSideBar(QWidget):
150
150
 
151
151
  return
152
152
 
153
- # END Class GuiSideBar
154
-
155
153
 
156
154
  class _PopRightMenu(QMenu):
157
155
 
@@ -162,5 +160,3 @@ class _PopRightMenu(QMenu):
162
160
  offset = QPoint(parent.width(), parent.height() - self.height())
163
161
  self.move(parent.mapToGlobal(offset))
164
162
  return super(_PopRightMenu, self).event(event)
165
-
166
- # END Class _PopRightMenu
@@ -27,26 +27,23 @@ import logging
27
27
 
28
28
  from datetime import datetime
29
29
  from time import time
30
- from typing import TYPE_CHECKING, Literal
31
30
 
32
- from PyQt5.QtCore import pyqtSlot, QLocale
33
- from PyQt5.QtWidgets import QApplication, QStatusBar, QLabel
31
+ from PyQt5.QtCore import QLocale, pyqtSlot
32
+ from PyQt5.QtWidgets import QApplication, QLabel, QStatusBar, QWidget
34
33
 
35
34
  from novelwriter import CONFIG, SHARED
36
35
  from novelwriter.common import formatTime
37
36
  from novelwriter.constants import nwConst
37
+ from novelwriter.enum import nwTrinary
38
38
  from novelwriter.extensions.statusled import StatusLED
39
39
 
40
- if TYPE_CHECKING: # pragma: no cover
41
- from novelwriter.guimain import GuiMain
42
-
43
40
  logger = logging.getLogger(__name__)
44
41
 
45
42
 
46
43
  class GuiMainStatus(QStatusBar):
47
44
 
48
- def __init__(self, mainGui: GuiMain) -> None:
49
- super().__init__(parent=mainGui)
45
+ def __init__(self, parent: QWidget) -> None:
46
+ super().__init__(parent=parent)
50
47
 
51
48
  logger.debug("Create: GuiMainStatus")
52
49
 
@@ -54,10 +51,6 @@ class GuiMainStatus(QStatusBar):
54
51
  self._userIdle = False
55
52
  self._debugInfo = False
56
53
 
57
- colNone = SHARED.theme.statNone
58
- colSaved = SHARED.theme.statSaved
59
- colUnsaved = SHARED.theme.statUnsaved
60
-
61
54
  iPx = SHARED.theme.baseIconHeight
62
55
 
63
56
  # Permanent Widgets
@@ -74,7 +67,7 @@ class GuiMainStatus(QStatusBar):
74
67
  self.addPermanentWidget(self.langText)
75
68
 
76
69
  # The Editor Status
77
- self.docIcon = StatusLED(colNone, colSaved, colUnsaved, iPx, iPx, self)
70
+ self.docIcon = StatusLED(iPx, iPx, self)
78
71
  self.docText = QLabel(self.tr("Editor"), self)
79
72
  self.docIcon.setContentsMargins(0, 0, 0, 0)
80
73
  self.docText.setContentsMargins(0, 0, xM, 0)
@@ -82,7 +75,7 @@ class GuiMainStatus(QStatusBar):
82
75
  self.addPermanentWidget(self.docText)
83
76
 
84
77
  # The Project Status
85
- self.projIcon = StatusLED(colNone, colSaved, colUnsaved, iPx, iPx, self)
78
+ self.projIcon = StatusLED(iPx, iPx, self)
86
79
  self.projText = QLabel(self.tr("Project"), self)
87
80
  self.projIcon.setContentsMargins(0, 0, 0, 0)
88
81
  self.projText.setContentsMargins(0, 0, xM, 0)
@@ -123,8 +116,8 @@ class GuiMainStatus(QStatusBar):
123
116
  self.setRefTime(-1.0)
124
117
  self.setLanguage(*SHARED.spelling.describeDict())
125
118
  self.setProjectStats(0, 0)
126
- self.setProjectStatus(StatusLED.S_NONE)
127
- self.setDocumentStatus(StatusLED.S_NONE)
119
+ self.setProjectStatus(nwTrinary.NEUTRAL)
120
+ self.setDocumentStatus(nwTrinary.NEUTRAL)
128
121
  self.updateTime()
129
122
  return
130
123
 
@@ -136,6 +129,13 @@ class GuiMainStatus(QStatusBar):
136
129
  self.timePixmap = SHARED.theme.getPixmap("status_time", (iPx, iPx))
137
130
  self.idlePixmap = SHARED.theme.getPixmap("status_idle", (iPx, iPx))
138
131
  self.timeIcon.setPixmap(self.timePixmap)
132
+
133
+ colNone = SHARED.theme.statNone
134
+ colSaved = SHARED.theme.statSaved
135
+ colUnsaved = SHARED.theme.statUnsaved
136
+ self.docIcon.setColors(colNone, colSaved, colUnsaved)
137
+ self.projIcon.setColors(colNone, colSaved, colUnsaved)
138
+
139
139
  return
140
140
 
141
141
  ##
@@ -147,12 +147,12 @@ class GuiMainStatus(QStatusBar):
147
147
  self._refTime = refTime
148
148
  return
149
149
 
150
- def setProjectStatus(self, state: Literal[0, 1, 2]) -> None:
150
+ def setProjectStatus(self, state: nwTrinary) -> None:
151
151
  """Set the project status colour icon."""
152
152
  self.projIcon.setState(state)
153
153
  return
154
154
 
155
- def setDocumentStatus(self, state: Literal[0, 1, 2]) -> None:
155
+ def setDocumentStatus(self, state: nwTrinary) -> None:
156
156
  """Set the document status colour icon."""
157
157
  self.docIcon.setState(state)
158
158
  return
@@ -215,13 +215,13 @@ class GuiMainStatus(QStatusBar):
215
215
  @pyqtSlot(bool)
216
216
  def updateProjectStatus(self, status: bool) -> None:
217
217
  """Update the project status."""
218
- self.setProjectStatus(StatusLED.S_BAD if status else StatusLED.S_GOOD)
218
+ self.setProjectStatus(nwTrinary.NEGATIVE if status else nwTrinary.POSITIVE)
219
219
  return
220
220
 
221
221
  @pyqtSlot(bool)
222
222
  def updateDocumentStatus(self, status: bool) -> None:
223
223
  """Update the document status."""
224
- self.setDocumentStatus(StatusLED.S_BAD if status else StatusLED.S_GOOD)
224
+ self.setDocumentStatus(nwTrinary.NEGATIVE if status else nwTrinary.POSITIVE)
225
225
  return
226
226
 
227
227
  ##
@@ -238,9 +238,8 @@ class GuiMainStatus(QStatusBar):
238
238
  before starting novelWriter.
239
239
  """
240
240
  import tracemalloc
241
- from collections import Counter
242
241
 
243
- widgets = QApplication.allWidgets()
242
+ count = len(QApplication.allWidgets())
244
243
  if not self._debugInfo:
245
244
  if tracemalloc.is_tracing():
246
245
  self._traceMallocRef = "Total"
@@ -248,21 +247,14 @@ class GuiMainStatus(QStatusBar):
248
247
  self._traceMallocRef = "Relative"
249
248
  tracemalloc.start()
250
249
  self._debugInfo = True
251
- self._wCounts = Counter([type(x).__name__ for x in widgets])
252
-
253
- if hasattr(self, "_wCounts"):
254
- diff = Counter([type(x).__name__ for x in widgets]) - self._wCounts
255
- for name, count in diff.items():
256
- logger.debug("Widget '%s': +%d", name, count)
257
250
 
258
- mem = tracemalloc.get_traced_memory()
251
+ current, peak = tracemalloc.get_traced_memory()
259
252
  stamp = datetime.now().strftime("%H:%M:%S")
260
- self.showMessage((
261
- f"Debug [{stamp}]"
262
- f" \u2013 Widgets: {len(widgets)}"
263
- f" \u2013 {self._traceMallocRef} Memory: {mem[0]:n}"
264
- f" \u2013 Peak: {mem[1]:n}"
265
- ), 6000)
253
+ message = (
254
+ f"Widgets: {count} \u2013 "
255
+ f"{self._traceMallocRef} Memory: {current/1024:,.2f} kiB \u2013 "
256
+ f"Peak: {peak/1024:,.2f} kiB"
257
+ )
258
+ self.showMessage(f"Debug [{stamp}] {message}", 6000)
259
+ logger.debug("[MEMINFO] %s", message)
266
260
  return
267
-
268
- # END Class GuiMainStatus