novelWriter 2.4.3__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 (123) hide show
  1. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/METADATA +4 -5
  2. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/RECORD +122 -112
  3. {novelWriter-2.4.3.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/i18n/project_pt_BR.json +74 -74
  19. novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
  20. novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
  21. novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
  22. novelwriter/assets/icons/typicons_light/icons.conf +2 -0
  23. novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
  24. novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
  25. novelwriter/assets/manual.pdf +0 -0
  26. novelwriter/assets/sample.zip +0 -0
  27. novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
  28. novelwriter/assets/syntax/default_dark.conf +32 -18
  29. novelwriter/assets/syntax/default_light.conf +24 -10
  30. novelwriter/assets/syntax/dracula.conf +44 -0
  31. novelwriter/assets/syntax/grey_dark.conf +5 -4
  32. novelwriter/assets/syntax/grey_light.conf +5 -4
  33. novelwriter/assets/syntax/light_owl.conf +7 -6
  34. novelwriter/assets/syntax/night_owl.conf +7 -6
  35. novelwriter/assets/syntax/snazzy.conf +42 -0
  36. novelwriter/assets/syntax/solarized_dark.conf +4 -3
  37. novelwriter/assets/syntax/solarized_light.conf +4 -3
  38. novelwriter/assets/syntax/tango.conf +27 -11
  39. novelwriter/assets/syntax/tomorrow.conf +6 -5
  40. novelwriter/assets/syntax/tomorrow_night.conf +7 -6
  41. novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
  42. novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
  43. novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
  44. novelwriter/assets/text/credits_en.htm +52 -41
  45. novelwriter/assets/themes/cyberpunk_night.conf +3 -0
  46. novelwriter/assets/themes/default_dark.conf +2 -0
  47. novelwriter/assets/themes/default_light.conf +2 -0
  48. novelwriter/assets/themes/dracula.conf +48 -0
  49. novelwriter/assets/themes/solarized_dark.conf +2 -0
  50. novelwriter/assets/themes/solarized_light.conf +2 -0
  51. novelwriter/common.py +33 -12
  52. novelwriter/config.py +184 -98
  53. novelwriter/constants.py +47 -35
  54. novelwriter/core/buildsettings.py +68 -69
  55. novelwriter/core/coretools.py +5 -23
  56. novelwriter/core/docbuild.py +52 -40
  57. novelwriter/core/document.py +3 -5
  58. novelwriter/core/index.py +115 -45
  59. novelwriter/core/item.py +8 -19
  60. novelwriter/core/options.py +2 -4
  61. novelwriter/core/project.py +37 -61
  62. novelwriter/core/projectdata.py +1 -3
  63. novelwriter/core/projectxml.py +12 -15
  64. novelwriter/core/sessions.py +3 -5
  65. novelwriter/core/spellcheck.py +4 -9
  66. novelwriter/core/status.py +211 -164
  67. novelwriter/core/storage.py +0 -8
  68. novelwriter/core/tohtml.py +139 -105
  69. novelwriter/core/tokenizer.py +278 -122
  70. novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
  71. novelwriter/core/toodt.py +257 -166
  72. novelwriter/core/toqdoc.py +419 -0
  73. novelwriter/core/tree.py +5 -7
  74. novelwriter/dialogs/about.py +11 -18
  75. novelwriter/dialogs/docmerge.py +17 -19
  76. novelwriter/dialogs/docsplit.py +17 -19
  77. novelwriter/dialogs/editlabel.py +6 -10
  78. novelwriter/dialogs/preferences.py +200 -164
  79. novelwriter/dialogs/projectsettings.py +225 -189
  80. novelwriter/dialogs/quotes.py +12 -9
  81. novelwriter/dialogs/wordlist.py +9 -15
  82. novelwriter/enum.py +35 -30
  83. novelwriter/error.py +8 -15
  84. novelwriter/extensions/configlayout.py +55 -21
  85. novelwriter/extensions/eventfilters.py +1 -5
  86. novelwriter/extensions/modified.py +70 -14
  87. novelwriter/extensions/novelselector.py +1 -3
  88. novelwriter/extensions/pagedsidebar.py +9 -12
  89. novelwriter/extensions/{circularprogress.py → progressbars.py} +30 -8
  90. novelwriter/extensions/statusled.py +40 -26
  91. novelwriter/extensions/switch.py +4 -6
  92. novelwriter/extensions/switchbox.py +7 -6
  93. novelwriter/extensions/versioninfo.py +3 -9
  94. novelwriter/gui/doceditor.py +120 -139
  95. novelwriter/gui/dochighlight.py +231 -186
  96. novelwriter/gui/docviewer.py +69 -108
  97. novelwriter/gui/docviewerpanel.py +3 -10
  98. novelwriter/gui/editordocument.py +1 -3
  99. novelwriter/gui/itemdetails.py +7 -11
  100. novelwriter/gui/mainmenu.py +22 -18
  101. novelwriter/gui/noveltree.py +11 -24
  102. novelwriter/gui/outline.py +15 -26
  103. novelwriter/gui/projtree.py +39 -65
  104. novelwriter/gui/search.py +10 -3
  105. novelwriter/gui/sidebar.py +2 -6
  106. novelwriter/gui/statusbar.py +29 -37
  107. novelwriter/gui/theme.py +26 -48
  108. novelwriter/guimain.py +162 -160
  109. novelwriter/shared.py +36 -19
  110. novelwriter/text/patterns.py +113 -0
  111. novelwriter/tools/dictionaries.py +10 -20
  112. novelwriter/tools/lipsum.py +10 -16
  113. novelwriter/tools/manusbuild.py +9 -11
  114. novelwriter/tools/manuscript.py +75 -149
  115. novelwriter/tools/manussettings.py +74 -76
  116. novelwriter/tools/noveldetails.py +16 -21
  117. novelwriter/tools/welcome.py +21 -26
  118. novelwriter/tools/writingstats.py +9 -12
  119. novelwriter/types.py +49 -4
  120. novelwriter/extensions/simpleprogress.py +0 -55
  121. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/LICENSE.md +0 -0
  122. {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/entry_points.txt +0 -0
  123. {novelWriter-2.4.3.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,34 +30,30 @@ 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
- from PyQt5.QtCore import QPoint, QTimer, Qt, pyqtSignal, pyqtSlot
36
- from PyQt5.QtGui import (
37
- QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
38
- )
34
+ from PyQt5.QtCore import QPoint, Qt, QTimer, pyqtSignal, pyqtSlot
35
+ from PyQt5.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
39
36
  from PyQt5.QtWidgets import (
40
- QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
41
- QLabel, QMenu, QShortcut, QSizePolicy, QTreeWidget, QTreeWidgetItem,
42
- QVBoxLayout, QWidget
37
+ QAbstractItemView, QAction, QFrame, QHBoxLayout, QHeaderView, QLabel,
38
+ QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
43
39
  )
44
40
 
45
41
  from novelwriter import CONFIG, SHARED
46
42
  from novelwriter.common import minmax
47
- from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
43
+ from novelwriter.constants import nwHeaders, nwLabels, nwUnicode, trConst
48
44
  from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
49
45
  from novelwriter.core.item import NWItem
50
46
  from novelwriter.dialogs.docmerge import GuiDocMerge
51
47
  from novelwriter.dialogs.docsplit import GuiDocSplit
52
48
  from novelwriter.dialogs.editlabel import GuiEditLabel
53
49
  from novelwriter.dialogs.projectsettings import GuiProjectSettings
54
- from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
50
+ from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType
55
51
  from novelwriter.extensions.modified import NIconToolButton
56
52
  from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
57
- from novelwriter.types import QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtUserRole
58
-
59
- if TYPE_CHECKING: # pragma: no cover
60
- from novelwriter.guimain import GuiMain
53
+ from novelwriter.types import (
54
+ QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtSizeExpanding,
55
+ QtUserRole
56
+ )
61
57
 
62
58
  logger = logging.getLogger(__name__)
63
59
 
@@ -80,10 +76,8 @@ class GuiProjectView(QWidget):
80
76
  # Requests for the main GUI
81
77
  projectSettingsRequest = pyqtSignal(int)
82
78
 
83
- def __init__(self, mainGui: GuiMain) -> None:
84
- super().__init__(parent=mainGui)
85
-
86
- self.mainGui = mainGui
79
+ def __init__(self, parent: QWidget) -> None:
80
+ super().__init__(parent=parent)
87
81
 
88
82
  # Build GUI
89
83
  self.projTree = GuiProjectTree(self)
@@ -248,8 +242,6 @@ class GuiProjectView(QWidget):
248
242
  self.projTree.createNewNote(tag, itemClass)
249
243
  return
250
244
 
251
- # END Class GuiProjectView
252
-
253
245
 
254
246
  class GuiProjectToolBar(QWidget):
255
247
 
@@ -262,7 +254,6 @@ class GuiProjectToolBar(QWidget):
262
254
 
263
255
  self.projView = projView
264
256
  self.projTree = projView.projTree
265
- self.mainGui = projView.mainGui
266
257
 
267
258
  iSz = SHARED.theme.baseIconSize
268
259
  mPx = CONFIG.pxInt(2)
@@ -274,7 +265,7 @@ class GuiProjectToolBar(QWidget):
274
265
  self.viewLabel = QLabel(self.tr("Project Content"), self)
275
266
  self.viewLabel.setFont(SHARED.theme.guiFontB)
276
267
  self.viewLabel.setContentsMargins(0, 0, 0, 0)
277
- self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
268
+ self.viewLabel.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
278
269
 
279
270
  # Quick Links
280
271
  self.mQuick = QMenu(self)
@@ -476,8 +467,6 @@ class GuiProjectToolBar(QWidget):
476
467
 
477
468
  return
478
469
 
479
- # END Class GuiProjectToolBar
480
-
481
470
 
482
471
  class GuiProjectTree(QTreeWidget):
483
472
 
@@ -498,7 +487,6 @@ class GuiProjectTree(QTreeWidget):
498
487
  logger.debug("Create: GuiProjectTree")
499
488
 
500
489
  self.projView = projView
501
- self.mainGui = projView.mainGui
502
490
 
503
491
  # Internal Variables
504
492
  self._treeMap: dict[str, QTreeWidgetItem] = {}
@@ -1009,8 +997,7 @@ class GuiProjectTree(QTreeWidget):
1009
997
  trItemP.takeChild(tIndex)
1010
998
 
1011
999
  for dHandle in reversed(self.getTreeFromHandle(tHandle)):
1012
- if self.mainGui.docEditor.docHandle == dHandle:
1013
- self.mainGui.closeDocument()
1000
+ SHARED.closeEditor(dHandle)
1014
1001
  SHARED.project.removeItem(dHandle)
1015
1002
  self._treeMap.pop(dHandle, None)
1016
1003
 
@@ -1033,7 +1020,7 @@ class GuiProjectTree(QTreeWidget):
1033
1020
  if trItem is None or nwItem is None:
1034
1021
  return
1035
1022
 
1036
- itemStatus, statusIcon = nwItem.getImportStatus(incIcon=True)
1023
+ itemStatus, statusIcon = nwItem.getImportStatus()
1037
1024
  hLevel = nwItem.mainHeading
1038
1025
  itemIcon = SHARED.theme.getItemIcon(
1039
1026
  nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
@@ -1409,19 +1396,15 @@ class GuiProjectTree(QTreeWidget):
1409
1396
  if not newFile:
1410
1397
  itemList.remove(tHandle)
1411
1398
 
1412
- dlgMerge = GuiDocMerge(self.mainGui, tHandle, itemList)
1413
- dlgMerge.exec()
1414
-
1415
- if dlgMerge.result() == QDialog.DialogCode.Accepted:
1416
-
1417
- mrgData = dlgMerge.getData()
1418
- mrgList = mrgData.get("finalItems", [])
1419
- if not mrgList:
1399
+ data, status = GuiDocMerge.getData(SHARED.mainGui, tHandle, itemList)
1400
+ if status:
1401
+ items = data.get("finalItems", [])
1402
+ if not items:
1420
1403
  SHARED.info(self.tr("No documents selected for merging."))
1421
1404
  return False
1422
1405
 
1423
1406
  # Save the open document first, in case it's part of merge
1424
- self.mainGui.saveDocument()
1407
+ SHARED.saveEditor()
1425
1408
 
1426
1409
  # Create merge object, and append docs
1427
1410
  docMerger = DocMerger(SHARED.project)
@@ -1436,7 +1419,7 @@ class GuiProjectTree(QTreeWidget):
1436
1419
  else:
1437
1420
  return False
1438
1421
 
1439
- for sHandle in mrgList:
1422
+ for sHandle in items:
1440
1423
  docMerger.appendText(sHandle, True, mLabel)
1441
1424
 
1442
1425
  if not docMerger.writeTargetDoc():
@@ -1450,10 +1433,11 @@ class GuiProjectTree(QTreeWidget):
1450
1433
  if newFile:
1451
1434
  self.revealNewTreeItem(mHandle, nHandle=tHandle, wordCount=True)
1452
1435
 
1453
- self.mainGui.openDocument(mHandle, doScroll=True)
1436
+ self.projView.openDocumentRequest.emit(mHandle, nwDocMode.EDIT, "", False)
1437
+ self.projView.setSelectedHandle(mHandle, doScroll=True)
1454
1438
 
1455
- if mrgData.get("moveToTrash", False):
1456
- for sHandle in reversed(mrgData.get("finalItems", [])):
1439
+ if data.get("moveToTrash", False):
1440
+ for sHandle in reversed(data.get("finalItems", [])):
1457
1441
  trItem = self._getTreeItem(sHandle)
1458
1442
  if isinstance(trItem, QTreeWidgetItem) and trItem.childCount() == 0:
1459
1443
  self.moveItemToTrash(sHandle, askFirst=False, flush=False)
@@ -1479,16 +1463,11 @@ class GuiProjectTree(QTreeWidget):
1479
1463
  logger.error("Only valid document items can be split")
1480
1464
  return False
1481
1465
 
1482
- dlgSplit = GuiDocSplit(self.mainGui, tHandle)
1483
- dlgSplit.exec()
1484
-
1485
- if dlgSplit.result() == QDialog.DialogCode.Accepted:
1486
-
1487
- splitData, splitText = dlgSplit.getData()
1488
-
1489
- headerList = splitData.get("headerList", [])
1490
- intoFolder = splitData.get("intoFolder", False)
1491
- 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)
1492
1471
 
1493
1472
  docSplit = DocSplitter(SHARED.project, tHandle)
1494
1473
  if intoFolder:
@@ -1498,7 +1477,7 @@ class GuiProjectTree(QTreeWidget):
1498
1477
  else:
1499
1478
  docSplit.setParentItem(tItem.itemParent)
1500
1479
 
1501
- docSplit.splitDocument(headerList, splitText)
1480
+ docSplit.splitDocument(headerList, text)
1502
1481
  for writeOk, dHandle, nHandle in docSplit.writeDocuments(docHierarchy):
1503
1482
  SHARED.project.index.reIndexHandle(dHandle)
1504
1483
  self.revealNewTreeItem(dHandle, nHandle=nHandle, wordCount=True)
@@ -1509,7 +1488,7 @@ class GuiProjectTree(QTreeWidget):
1509
1488
  info=docSplit.getError()
1510
1489
  )
1511
1490
 
1512
- if splitData.get("moveToTrash", False):
1491
+ if data.get("moveToTrash", False):
1513
1492
  self.moveItemToTrash(tHandle, askFirst=False, flush=True)
1514
1493
 
1515
1494
  self.saveTreeOrder()
@@ -1658,8 +1637,6 @@ class GuiProjectTree(QTreeWidget):
1658
1637
 
1659
1638
  return
1660
1639
 
1661
- # END Class GuiProjectTree
1662
-
1663
1640
 
1664
1641
  class _UpdatableMenu(QMenu):
1665
1642
 
@@ -1723,8 +1700,6 @@ class _UpdatableMenu(QMenu):
1723
1700
  self.menuItemTriggered.emit(str(action.data()))
1724
1701
  return
1725
1702
 
1726
- # END Class _UpdatableMenu
1727
-
1728
1703
 
1729
1704
  class _TreeContextMenu(QMenu):
1730
1705
 
@@ -1830,6 +1805,7 @@ class _TreeContextMenu(QMenu):
1830
1805
 
1831
1806
  def _itemHeader(self) -> None:
1832
1807
  """Check if there is a header that can be used for rename."""
1808
+ SHARED.saveEditor()
1833
1809
  if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"):
1834
1810
  action = self.addAction(self.tr("Rename to Heading"))
1835
1811
  action.triggered.connect(
@@ -1855,11 +1831,11 @@ class _TreeContextMenu(QMenu):
1855
1831
  if self._item.isNovelLike():
1856
1832
  menu = self.addMenu(self.tr("Set Status to ..."))
1857
1833
  current = self._item.itemStatus
1858
- for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.items()):
1859
- name = entry["name"]
1834
+ for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.iterItems()):
1835
+ name = entry.name
1860
1836
  if not multi and current == key:
1861
1837
  name += f" ({nwUnicode.U_CHECK})"
1862
- action = menu.addAction(entry["icon"], name)
1838
+ action = menu.addAction(entry.icon, name)
1863
1839
  if multi:
1864
1840
  action.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key))
1865
1841
  else:
@@ -1872,11 +1848,11 @@ class _TreeContextMenu(QMenu):
1872
1848
  else:
1873
1849
  menu = self.addMenu(self.tr("Set Importance to ..."))
1874
1850
  current = self._item.itemImport
1875
- for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()):
1876
- name = entry["name"]
1851
+ for n, (key, entry) in enumerate(SHARED.project.data.itemImport.iterItems()):
1852
+ name = entry.name
1877
1853
  if not multi and current == key:
1878
1854
  name += f" ({nwUnicode.U_CHECK})"
1879
- action = menu.addAction(entry["icon"], name)
1855
+ action = menu.addAction(entry.icon, name)
1880
1856
  if multi:
1881
1857
  action.triggered.connect(lambda n, key=key: self._iterSetItemImport(key))
1882
1858
  else:
@@ -2076,5 +2052,3 @@ class _TreeContextMenu(QMenu):
2076
2052
  else:
2077
2053
  logger.info("Folder conversion cancelled")
2078
2054
  return
2079
-
2080
- # 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