novelWriter 2.5b1__py3-none-any.whl → 2.5.1__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 (78) hide show
  1. {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/RECORD +77 -75
  3. {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +3 -3
  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_pl_PL.json +116 -0
  17. novelwriter/assets/i18n/project_pt_BR.json +74 -74
  18. novelwriter/assets/manual.pdf +0 -0
  19. novelwriter/assets/sample.zip +0 -0
  20. novelwriter/assets/text/credits_en.htm +52 -44
  21. novelwriter/assets/themes/cyberpunk_night.conf +1 -0
  22. novelwriter/assets/themes/default_dark.conf +1 -0
  23. novelwriter/assets/themes/default_light.conf +1 -0
  24. novelwriter/assets/themes/dracula.conf +1 -0
  25. novelwriter/assets/themes/solarized_dark.conf +1 -0
  26. novelwriter/assets/themes/solarized_light.conf +1 -0
  27. novelwriter/common.py +12 -3
  28. novelwriter/config.py +67 -15
  29. novelwriter/constants.py +8 -10
  30. novelwriter/core/buildsettings.py +5 -3
  31. novelwriter/core/coretools.py +3 -1
  32. novelwriter/core/docbuild.py +1 -0
  33. novelwriter/core/project.py +15 -4
  34. novelwriter/core/status.py +4 -1
  35. novelwriter/core/storage.py +6 -1
  36. novelwriter/core/tohtml.py +69 -29
  37. novelwriter/core/tokenizer.py +83 -14
  38. novelwriter/core/toodt.py +48 -21
  39. novelwriter/core/toqdoc.py +37 -21
  40. novelwriter/dialogs/about.py +10 -15
  41. novelwriter/dialogs/docmerge.py +16 -16
  42. novelwriter/dialogs/docsplit.py +16 -16
  43. novelwriter/dialogs/editlabel.py +6 -8
  44. novelwriter/dialogs/preferences.py +106 -93
  45. novelwriter/dialogs/projectsettings.py +16 -20
  46. novelwriter/dialogs/quotes.py +9 -5
  47. novelwriter/dialogs/wordlist.py +6 -6
  48. novelwriter/enum.py +4 -5
  49. novelwriter/extensions/configlayout.py +38 -4
  50. novelwriter/extensions/modified.py +22 -3
  51. novelwriter/extensions/{circularprogress.py → progressbars.py} +26 -3
  52. novelwriter/extensions/statusled.py +39 -23
  53. novelwriter/gui/doceditor.py +22 -13
  54. novelwriter/gui/dochighlight.py +30 -39
  55. novelwriter/gui/docviewer.py +24 -15
  56. novelwriter/gui/docviewerpanel.py +7 -0
  57. novelwriter/gui/mainmenu.py +11 -11
  58. novelwriter/gui/outline.py +4 -3
  59. novelwriter/gui/projtree.py +85 -77
  60. novelwriter/gui/search.py +10 -1
  61. novelwriter/gui/statusbar.py +25 -29
  62. novelwriter/gui/theme.py +3 -0
  63. novelwriter/guimain.py +139 -124
  64. novelwriter/shared.py +19 -8
  65. novelwriter/text/patterns.py +113 -0
  66. novelwriter/tools/dictionaries.py +2 -8
  67. novelwriter/tools/lipsum.py +8 -12
  68. novelwriter/tools/manusbuild.py +9 -9
  69. novelwriter/tools/manuscript.py +10 -5
  70. novelwriter/tools/manussettings.py +7 -3
  71. novelwriter/tools/noveldetails.py +10 -10
  72. novelwriter/tools/welcome.py +19 -10
  73. novelwriter/tools/writingstats.py +3 -3
  74. novelwriter/types.py +5 -2
  75. novelwriter/extensions/simpleprogress.py +0 -53
  76. {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/LICENSE.md +0 -0
  77. {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/entry_points.txt +0 -0
  78. {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/top_level.txt +0 -0
@@ -34,9 +34,8 @@ from time import time
34
34
  from PyQt5.QtCore import QPoint, Qt, QTimer, pyqtSignal, pyqtSlot
35
35
  from PyQt5.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
36
36
  from PyQt5.QtWidgets import (
37
- QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
38
- QLabel, QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
39
- QWidget
37
+ QAbstractItemView, QAction, QFrame, QHBoxLayout, QHeaderView, QLabel,
38
+ QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
40
39
  )
41
40
 
42
41
  from novelwriter import CONFIG, SHARED
@@ -167,6 +166,7 @@ class GuiProjectView(QWidget):
167
166
 
168
167
  def openProjectTasks(self) -> None:
169
168
  """Run open project tasks."""
169
+ self.populateTree()
170
170
  self.projBar.buildQuickLinksMenu()
171
171
  self.projBar.setEnabled(True)
172
172
  return
@@ -214,7 +214,8 @@ class GuiProjectView(QWidget):
214
214
  @pyqtSlot(str)
215
215
  def updateItemValues(self, tHandle: str) -> None:
216
216
  """Update tree item."""
217
- self.projTree.setTreeItemValues(tHandle)
217
+ if nwItem := SHARED.project.tree[tHandle]:
218
+ self.projTree.setTreeItemValues(nwItem)
218
219
  return
219
220
 
220
221
  @pyqtSlot(str)
@@ -243,6 +244,12 @@ class GuiProjectView(QWidget):
243
244
  self.projTree.createNewNote(tag, itemClass)
244
245
  return
245
246
 
247
+ @pyqtSlot(str)
248
+ def refreshUserLabels(self, kind: str) -> None:
249
+ """Refresh status or importance labels."""
250
+ self.projTree.refreshUserLabels(kind)
251
+ return
252
+
246
253
 
247
254
  class GuiProjectToolBar(QWidget):
248
255
 
@@ -793,11 +800,11 @@ class GuiProjectTree(QTreeWidget):
793
800
 
794
801
  def renameTreeItem(self, tHandle: str, name: str = "") -> None:
795
802
  """Open a dialog to edit the label of an item."""
796
- if tItem := SHARED.project.tree[tHandle]:
797
- newLabel, dlgOk = GuiEditLabel.getLabel(self, text=name or tItem.itemName)
803
+ if nwItem := SHARED.project.tree[tHandle]:
804
+ newLabel, dlgOk = GuiEditLabel.getLabel(self, text=name or nwItem.itemName)
798
805
  if dlgOk:
799
- tItem.setName(newLabel)
800
- self.setTreeItemValues(tHandle)
806
+ nwItem.setName(newLabel)
807
+ self.setTreeItemValues(nwItem)
801
808
  self._alertTreeChange(tHandle, flush=False)
802
809
  return
803
810
 
@@ -998,7 +1005,7 @@ class GuiProjectTree(QTreeWidget):
998
1005
  trItemP.takeChild(tIndex)
999
1006
 
1000
1007
  for dHandle in reversed(self.getTreeFromHandle(tHandle)):
1001
- SHARED.closeDocument(dHandle)
1008
+ SHARED.closeEditor(dHandle)
1002
1009
  SHARED.project.removeItem(dHandle)
1003
1010
  self._treeMap.pop(dHandle, None)
1004
1011
 
@@ -1011,44 +1018,52 @@ class GuiProjectTree(QTreeWidget):
1011
1018
 
1012
1019
  return True
1013
1020
 
1014
- def setTreeItemValues(self, tHandle: str) -> None:
1015
- """Set the name and flag values for a tree item from a handle in
1016
- the project tree. Does not trigger a tree change as the data is
1017
- already coming from the project tree.
1021
+ def refreshUserLabels(self, kind: str) -> None:
1022
+ """Refresh status or importance labels."""
1023
+ if kind == "s":
1024
+ for nwItem in SHARED.project.tree:
1025
+ if nwItem.isNovelLike():
1026
+ self.setTreeItemValues(nwItem)
1027
+ elif kind == "i":
1028
+ for nwItem in SHARED.project.tree:
1029
+ if not nwItem.isNovelLike():
1030
+ self.setTreeItemValues(nwItem)
1031
+ return
1032
+
1033
+ def setTreeItemValues(self, nwItem: NWItem | None) -> None:
1034
+ """Set the name and flag values for a tree item in the project
1035
+ tree. Does not trigger a tree change as the data is already
1036
+ coming from project data.
1018
1037
  """
1019
- trItem = self._getTreeItem(tHandle)
1020
- nwItem = SHARED.project.tree[tHandle]
1021
- if trItem is None or nwItem is None:
1022
- return
1023
-
1024
- itemStatus, statusIcon = nwItem.getImportStatus()
1025
- hLevel = nwItem.mainHeading
1026
- itemIcon = SHARED.theme.getItemIcon(
1027
- nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
1028
- )
1038
+ if isinstance(nwItem, NWItem) and (trItem := self._getTreeItem(nwItem.itemHandle)):
1039
+ itemStatus, statusIcon = nwItem.getImportStatus()
1040
+ hLevel = nwItem.mainHeading
1041
+ itemIcon = SHARED.theme.getItemIcon(
1042
+ nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
1043
+ )
1029
1044
 
1030
- trItem.setIcon(self.C_NAME, itemIcon)
1031
- trItem.setText(self.C_NAME, nwItem.itemName)
1032
- trItem.setIcon(self.C_STATUS, statusIcon)
1033
- trItem.setToolTip(self.C_STATUS, itemStatus)
1045
+ trItem.setIcon(self.C_NAME, itemIcon)
1046
+ trItem.setText(self.C_NAME, nwItem.itemName)
1047
+ trItem.setIcon(self.C_STATUS, statusIcon)
1048
+ trItem.setToolTip(self.C_STATUS, itemStatus)
1034
1049
 
1035
- if nwItem.isFileType():
1036
- iconName = "checked" if nwItem.isActive else "unchecked"
1037
- toolTip = self.trActive if nwItem.isActive else self.trInactive
1038
- trItem.setToolTip(self.C_ACTIVE, toolTip)
1039
- else:
1040
- iconName = "noncheckable"
1050
+ if nwItem.isFileType():
1051
+ iconName = "checked" if nwItem.isActive else "unchecked"
1052
+ toolTip = self.trActive if nwItem.isActive else self.trInactive
1053
+ trItem.setToolTip(self.C_ACTIVE, toolTip)
1054
+ else:
1055
+ iconName = "noncheckable"
1041
1056
 
1042
- trItem.setIcon(self.C_ACTIVE, SHARED.theme.getIcon(iconName))
1057
+ trItem.setIcon(self.C_ACTIVE, SHARED.theme.getIcon(iconName))
1043
1058
 
1044
- if CONFIG.emphLabels and nwItem.isDocumentLayout():
1045
- trFont = trItem.font(self.C_NAME)
1046
- trFont.setBold(hLevel == "H1" or hLevel == "H2")
1047
- trFont.setUnderline(hLevel == "H1")
1048
- trItem.setFont(self.C_NAME, trFont)
1059
+ if CONFIG.emphLabels and nwItem.isDocumentLayout():
1060
+ trFont = trItem.font(self.C_NAME)
1061
+ trFont.setBold(hLevel == "H1" or hLevel == "H2")
1062
+ trFont.setUnderline(hLevel == "H1")
1063
+ trItem.setFont(self.C_NAME, trFont)
1049
1064
 
1050
- # Emit Refresh Signal
1051
- self.itemRefreshed.emit(tHandle, nwItem, itemIcon)
1065
+ # Emit Refresh Signal
1066
+ self.itemRefreshed.emit(nwItem.itemHandle, nwItem, itemIcon)
1052
1067
 
1053
1068
  return
1054
1069
 
@@ -1354,7 +1369,8 @@ class GuiProjectTree(QTreeWidget):
1354
1369
  SHARED.project.index.deleteHandle(mHandle)
1355
1370
  else:
1356
1371
  SHARED.project.index.reIndexHandle(mHandle)
1357
- self.setTreeItemValues(mHandle)
1372
+ if mItem := SHARED.project.tree[mHandle]:
1373
+ self.setTreeItemValues(mItem)
1358
1374
 
1359
1375
  # Update word count
1360
1376
  self.propagateCount(tHandle, nwItemS.wordCount, countChildren=True)
@@ -1397,19 +1413,15 @@ class GuiProjectTree(QTreeWidget):
1397
1413
  if not newFile:
1398
1414
  itemList.remove(tHandle)
1399
1415
 
1400
- dlgMerge = GuiDocMerge(SHARED.mainGui, tHandle, itemList)
1401
- dlgMerge.exec()
1402
-
1403
- if dlgMerge.result() == QDialog.DialogCode.Accepted:
1404
-
1405
- mrgData = dlgMerge.getData()
1406
- mrgList = mrgData.get("finalItems", [])
1407
- if not mrgList:
1416
+ data, status = GuiDocMerge.getData(SHARED.mainGui, tHandle, itemList)
1417
+ if status:
1418
+ items = data.get("finalItems", [])
1419
+ if not items:
1408
1420
  SHARED.info(self.tr("No documents selected for merging."))
1409
1421
  return False
1410
1422
 
1411
1423
  # Save the open document first, in case it's part of merge
1412
- SHARED.saveDocument()
1424
+ SHARED.saveEditor()
1413
1425
 
1414
1426
  # Create merge object, and append docs
1415
1427
  docMerger = DocMerger(SHARED.project)
@@ -1424,7 +1436,7 @@ class GuiProjectTree(QTreeWidget):
1424
1436
  else:
1425
1437
  return False
1426
1438
 
1427
- for sHandle in mrgList:
1439
+ for sHandle in items:
1428
1440
  docMerger.appendText(sHandle, True, mLabel)
1429
1441
 
1430
1442
  if not docMerger.writeTargetDoc():
@@ -1441,8 +1453,8 @@ class GuiProjectTree(QTreeWidget):
1441
1453
  self.projView.openDocumentRequest.emit(mHandle, nwDocMode.EDIT, "", False)
1442
1454
  self.projView.setSelectedHandle(mHandle, doScroll=True)
1443
1455
 
1444
- if mrgData.get("moveToTrash", False):
1445
- for sHandle in reversed(mrgData.get("finalItems", [])):
1456
+ if data.get("moveToTrash", False):
1457
+ for sHandle in reversed(data.get("finalItems", [])):
1446
1458
  trItem = self._getTreeItem(sHandle)
1447
1459
  if isinstance(trItem, QTreeWidgetItem) and trItem.childCount() == 0:
1448
1460
  self.moveItemToTrash(sHandle, askFirst=False, flush=False)
@@ -1468,16 +1480,11 @@ class GuiProjectTree(QTreeWidget):
1468
1480
  logger.error("Only valid document items can be split")
1469
1481
  return False
1470
1482
 
1471
- dlgSplit = GuiDocSplit(SHARED.mainGui, tHandle)
1472
- dlgSplit.exec()
1473
-
1474
- if dlgSplit.result() == QDialog.DialogCode.Accepted:
1475
-
1476
- splitData, splitText = dlgSplit.getData()
1477
-
1478
- headerList = splitData.get("headerList", [])
1479
- intoFolder = splitData.get("intoFolder", False)
1480
- docHierarchy = splitData.get("docHierarchy", False)
1483
+ data, text, status = GuiDocSplit.getData(SHARED.mainGui, tHandle)
1484
+ if status:
1485
+ headerList = data.get("headerList", [])
1486
+ intoFolder = data.get("intoFolder", False)
1487
+ docHierarchy = data.get("docHierarchy", False)
1481
1488
 
1482
1489
  docSplit = DocSplitter(SHARED.project, tHandle)
1483
1490
  if intoFolder:
@@ -1487,7 +1494,7 @@ class GuiProjectTree(QTreeWidget):
1487
1494
  else:
1488
1495
  docSplit.setParentItem(tItem.itemParent)
1489
1496
 
1490
- docSplit.splitDocument(headerList, splitText)
1497
+ docSplit.splitDocument(headerList, text)
1491
1498
  for writeOk, dHandle, nHandle in docSplit.writeDocuments(docHierarchy):
1492
1499
  SHARED.project.index.reIndexHandle(dHandle)
1493
1500
  self.revealNewTreeItem(dHandle, nHandle=nHandle, wordCount=True)
@@ -1498,7 +1505,7 @@ class GuiProjectTree(QTreeWidget):
1498
1505
  info=docSplit.getError()
1499
1506
  )
1500
1507
 
1501
- if splitData.get("moveToTrash", False):
1508
+ if data.get("moveToTrash", False):
1502
1509
  self.moveItemToTrash(tHandle, askFirst=False, flush=True)
1503
1510
 
1504
1511
  self.saveTreeOrder()
@@ -1604,7 +1611,7 @@ class GuiProjectTree(QTreeWidget):
1604
1611
 
1605
1612
  self._treeMap[tHandle] = newItem
1606
1613
  self.propagateCount(tHandle, nwItem.wordCount, countChildren=True)
1607
- self.setTreeItemValues(tHandle)
1614
+ self.setTreeItemValues(nwItem)
1608
1615
  newItem.setExpanded(nwItem.isExpanded)
1609
1616
 
1610
1617
  return newItem
@@ -1815,6 +1822,7 @@ class _TreeContextMenu(QMenu):
1815
1822
 
1816
1823
  def _itemHeader(self) -> None:
1817
1824
  """Check if there is a header that can be used for rename."""
1825
+ SHARED.saveEditor()
1818
1826
  if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"):
1819
1827
  action = self.addAction(self.tr("Rename to Heading"))
1820
1828
  action.triggered.connect(
@@ -1980,7 +1988,7 @@ class _TreeContextMenu(QMenu):
1980
1988
  def _toggleItemActive(self) -> None:
1981
1989
  """Toggle the active status of an item."""
1982
1990
  self._item.setActive(not self._item.isActive)
1983
- self.projTree.setTreeItemValues(self._handle)
1991
+ self.projTree.setTreeItemValues(self._item)
1984
1992
  self.projTree._alertTreeChange(self._handle, flush=False)
1985
1993
  return
1986
1994
 
@@ -1993,14 +2001,14 @@ class _TreeContextMenu(QMenu):
1993
2001
  for tItem in self._items:
1994
2002
  if tItem and tItem.isFileType():
1995
2003
  tItem.setActive(isActive)
1996
- self.projTree.setTreeItemValues(tItem.itemHandle)
2004
+ self.projTree.setTreeItemValues(tItem)
1997
2005
  self.projTree._alertTreeChange(tItem.itemHandle, flush=False)
1998
2006
  return
1999
2007
 
2000
2008
  def _changeItemStatus(self, key: str) -> None:
2001
2009
  """Set a new status value of an item."""
2002
2010
  self._item.setStatus(key)
2003
- self.projTree.setTreeItemValues(self._handle)
2011
+ self.projTree.setTreeItemValues(self._item)
2004
2012
  self.projTree._alertTreeChange(self._handle, flush=False)
2005
2013
  return
2006
2014
 
@@ -2009,14 +2017,14 @@ class _TreeContextMenu(QMenu):
2009
2017
  for tItem in self._items:
2010
2018
  if tItem and tItem.isNovelLike():
2011
2019
  tItem.setStatus(key)
2012
- self.projTree.setTreeItemValues(tItem.itemHandle)
2020
+ self.projTree.setTreeItemValues(tItem)
2013
2021
  self.projTree._alertTreeChange(tItem.itemHandle, flush=False)
2014
2022
  return
2015
2023
 
2016
2024
  def _changeItemImport(self, key: str) -> None:
2017
2025
  """Set a new importance value of an item."""
2018
2026
  self._item.setImport(key)
2019
- self.projTree.setTreeItemValues(self._handle)
2027
+ self.projTree.setTreeItemValues(self._item)
2020
2028
  self.projTree._alertTreeChange(self._handle, flush=False)
2021
2029
  return
2022
2030
 
@@ -2025,7 +2033,7 @@ class _TreeContextMenu(QMenu):
2025
2033
  for tItem in self._items:
2026
2034
  if tItem and not tItem.isNovelLike():
2027
2035
  tItem.setImport(key)
2028
- self.projTree.setTreeItemValues(tItem.itemHandle)
2036
+ self.projTree.setTreeItemValues(tItem)
2029
2037
  self.projTree._alertTreeChange(tItem.itemHandle, flush=False)
2030
2038
  return
2031
2039
 
@@ -2033,11 +2041,11 @@ class _TreeContextMenu(QMenu):
2033
2041
  """Set a new item layout value of an item."""
2034
2042
  if itemLayout == nwItemLayout.DOCUMENT and self._item.documentAllowed():
2035
2043
  self._item.setLayout(nwItemLayout.DOCUMENT)
2036
- self.projTree.setTreeItemValues(self._handle)
2044
+ self.projTree.setTreeItemValues(self._item)
2037
2045
  self.projTree._alertTreeChange(self._handle, flush=False)
2038
2046
  elif itemLayout == nwItemLayout.NOTE:
2039
2047
  self._item.setLayout(nwItemLayout.NOTE)
2040
- self.projTree.setTreeItemValues(self._handle)
2048
+ self.projTree.setTreeItemValues(self._item)
2041
2049
  self.projTree._alertTreeChange(self._handle, flush=False)
2042
2050
  return
2043
2051
 
@@ -2051,12 +2059,12 @@ class _TreeContextMenu(QMenu):
2051
2059
  if msgYes and itemLayout == nwItemLayout.DOCUMENT and self._item.documentAllowed():
2052
2060
  self._item.setType(nwItemType.FILE)
2053
2061
  self._item.setLayout(nwItemLayout.DOCUMENT)
2054
- self.projTree.setTreeItemValues(self._handle)
2062
+ self.projTree.setTreeItemValues(self._item)
2055
2063
  self.projTree._alertTreeChange(self._handle, flush=False)
2056
2064
  elif msgYes and itemLayout == nwItemLayout.NOTE:
2057
2065
  self._item.setType(nwItemType.FILE)
2058
2066
  self._item.setLayout(nwItemLayout.NOTE)
2059
- self.projTree.setTreeItemValues(self._handle)
2067
+ self.projTree.setTreeItemValues(self._item)
2060
2068
  self.projTree._alertTreeChange(self._handle, flush=False)
2061
2069
  else:
2062
2070
  logger.info("Folder conversion cancelled")
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.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
  ##
@@ -27,7 +27,6 @@ import logging
27
27
 
28
28
  from datetime import datetime
29
29
  from time import time
30
- from typing import Literal
31
30
 
32
31
  from PyQt5.QtCore import QLocale, pyqtSlot
33
32
  from PyQt5.QtWidgets import QApplication, QLabel, QStatusBar, QWidget
@@ -35,6 +34,7 @@ from PyQt5.QtWidgets import QApplication, QLabel, QStatusBar, QWidget
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
40
  logger = logging.getLogger(__name__)
@@ -51,10 +51,6 @@ class GuiMainStatus(QStatusBar):
51
51
  self._userIdle = False
52
52
  self._debugInfo = False
53
53
 
54
- colNone = SHARED.theme.statNone
55
- colSaved = SHARED.theme.statSaved
56
- colUnsaved = SHARED.theme.statUnsaved
57
-
58
54
  iPx = SHARED.theme.baseIconHeight
59
55
 
60
56
  # Permanent Widgets
@@ -71,7 +67,7 @@ class GuiMainStatus(QStatusBar):
71
67
  self.addPermanentWidget(self.langText)
72
68
 
73
69
  # The Editor Status
74
- self.docIcon = StatusLED(colNone, colSaved, colUnsaved, iPx, iPx, self)
70
+ self.docIcon = StatusLED(iPx, iPx, self)
75
71
  self.docText = QLabel(self.tr("Editor"), self)
76
72
  self.docIcon.setContentsMargins(0, 0, 0, 0)
77
73
  self.docText.setContentsMargins(0, 0, xM, 0)
@@ -79,7 +75,7 @@ class GuiMainStatus(QStatusBar):
79
75
  self.addPermanentWidget(self.docText)
80
76
 
81
77
  # The Project Status
82
- self.projIcon = StatusLED(colNone, colSaved, colUnsaved, iPx, iPx, self)
78
+ self.projIcon = StatusLED(iPx, iPx, self)
83
79
  self.projText = QLabel(self.tr("Project"), self)
84
80
  self.projIcon.setContentsMargins(0, 0, 0, 0)
85
81
  self.projText.setContentsMargins(0, 0, xM, 0)
@@ -120,8 +116,8 @@ class GuiMainStatus(QStatusBar):
120
116
  self.setRefTime(-1.0)
121
117
  self.setLanguage(*SHARED.spelling.describeDict())
122
118
  self.setProjectStats(0, 0)
123
- self.setProjectStatus(StatusLED.S_NONE)
124
- self.setDocumentStatus(StatusLED.S_NONE)
119
+ self.setProjectStatus(nwTrinary.NEUTRAL)
120
+ self.setDocumentStatus(nwTrinary.NEUTRAL)
125
121
  self.updateTime()
126
122
  return
127
123
 
@@ -133,6 +129,13 @@ class GuiMainStatus(QStatusBar):
133
129
  self.timePixmap = SHARED.theme.getPixmap("status_time", (iPx, iPx))
134
130
  self.idlePixmap = SHARED.theme.getPixmap("status_idle", (iPx, iPx))
135
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
+
136
139
  return
137
140
 
138
141
  ##
@@ -144,12 +147,12 @@ class GuiMainStatus(QStatusBar):
144
147
  self._refTime = refTime
145
148
  return
146
149
 
147
- def setProjectStatus(self, state: Literal[0, 1, 2]) -> None:
150
+ def setProjectStatus(self, state: nwTrinary) -> None:
148
151
  """Set the project status colour icon."""
149
152
  self.projIcon.setState(state)
150
153
  return
151
154
 
152
- def setDocumentStatus(self, state: Literal[0, 1, 2]) -> None:
155
+ def setDocumentStatus(self, state: nwTrinary) -> None:
153
156
  """Set the document status colour icon."""
154
157
  self.docIcon.setState(state)
155
158
  return
@@ -212,13 +215,13 @@ class GuiMainStatus(QStatusBar):
212
215
  @pyqtSlot(bool)
213
216
  def updateProjectStatus(self, status: bool) -> None:
214
217
  """Update the project status."""
215
- self.setProjectStatus(StatusLED.S_BAD if status else StatusLED.S_GOOD)
218
+ self.setProjectStatus(nwTrinary.NEGATIVE if status else nwTrinary.POSITIVE)
216
219
  return
217
220
 
218
221
  @pyqtSlot(bool)
219
222
  def updateDocumentStatus(self, status: bool) -> None:
220
223
  """Update the document status."""
221
- self.setDocumentStatus(StatusLED.S_BAD if status else StatusLED.S_GOOD)
224
+ self.setDocumentStatus(nwTrinary.NEGATIVE if status else nwTrinary.POSITIVE)
222
225
  return
223
226
 
224
227
  ##
@@ -236,9 +239,7 @@ class GuiMainStatus(QStatusBar):
236
239
  """
237
240
  import tracemalloc
238
241
 
239
- from collections import Counter
240
-
241
- widgets = QApplication.allWidgets()
242
+ count = len(QApplication.allWidgets())
242
243
  if not self._debugInfo:
243
244
  if tracemalloc.is_tracing():
244
245
  self._traceMallocRef = "Total"
@@ -246,19 +247,14 @@ class GuiMainStatus(QStatusBar):
246
247
  self._traceMallocRef = "Relative"
247
248
  tracemalloc.start()
248
249
  self._debugInfo = True
249
- self._wCounts = Counter([type(x).__name__ for x in widgets])
250
-
251
- if hasattr(self, "_wCounts"):
252
- diff = Counter([type(x).__name__ for x in widgets]) - self._wCounts
253
- for name, count in diff.items():
254
- logger.debug("Widget '%s': +%d", name, count)
255
250
 
256
- mem = tracemalloc.get_traced_memory()
251
+ current, peak = tracemalloc.get_traced_memory()
257
252
  stamp = datetime.now().strftime("%H:%M:%S")
258
- self.showMessage((
259
- f"Debug [{stamp}]"
260
- f" \u2013 Widgets: {len(widgets)}"
261
- f" \u2013 {self._traceMallocRef} Memory: {mem[0]:n}"
262
- f" \u2013 Peak: {mem[1]:n}"
263
- ), 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)
264
260
  return
novelwriter/gui/theme.py CHANGED
@@ -75,6 +75,7 @@ class GuiTheme:
75
75
  self.statUnsaved = QColor(0, 0, 0)
76
76
  self.statSaved = QColor(0, 0, 0)
77
77
  self.helpText = QColor(0, 0, 0)
78
+ self.fadedText = QColor(0, 0, 0)
78
79
  self.errorText = QColor(255, 0, 0)
79
80
 
80
81
  # Loaded Syntax Settings
@@ -263,6 +264,7 @@ class GuiTheme:
263
264
  sec = "GUI"
264
265
  if parser.has_section(sec):
265
266
  self.helpText = self._parseColour(parser, sec, "helptext")
267
+ self.fadedText = self._parseColour(parser, sec, "fadedtext")
266
268
  self.errorText = self._parseColour(parser, sec, "errortext")
267
269
  self.statNone = self._parseColour(parser, sec, "statusnone")
268
270
  self.statUnsaved = self._parseColour(parser, sec, "statusunsaved")
@@ -405,6 +407,7 @@ class GuiTheme:
405
407
  self.statUnsaved = QColor(200, 15, 39)
406
408
  self.statSaved = QColor(2, 133, 37)
407
409
  self.helpText = QColor(0, 0, 0)
410
+ self.fadedText = QColor(128, 128, 128)
408
411
  self.errorText = QColor(255, 0, 0)
409
412
  return
410
413