novelWriter 2.5b1__py3-none-any.whl → 2.5rc1__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 (62) hide show
  1. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/RECORD +61 -61
  3. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +3 -3
  5. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  6. novelwriter/assets/i18n/project_pt_BR.json +74 -74
  7. novelwriter/assets/manual.pdf +0 -0
  8. novelwriter/assets/sample.zip +0 -0
  9. novelwriter/assets/themes/cyberpunk_night.conf +1 -0
  10. novelwriter/assets/themes/default_dark.conf +1 -0
  11. novelwriter/assets/themes/default_light.conf +1 -0
  12. novelwriter/assets/themes/dracula.conf +1 -0
  13. novelwriter/assets/themes/solarized_dark.conf +1 -0
  14. novelwriter/assets/themes/solarized_light.conf +1 -0
  15. novelwriter/common.py +2 -3
  16. novelwriter/config.py +67 -15
  17. novelwriter/constants.py +8 -10
  18. novelwriter/core/buildsettings.py +5 -3
  19. novelwriter/core/coretools.py +3 -1
  20. novelwriter/core/docbuild.py +1 -0
  21. novelwriter/core/tohtml.py +69 -29
  22. novelwriter/core/tokenizer.py +83 -14
  23. novelwriter/core/toodt.py +48 -21
  24. novelwriter/core/toqdoc.py +25 -9
  25. novelwriter/dialogs/about.py +10 -15
  26. novelwriter/dialogs/docmerge.py +16 -16
  27. novelwriter/dialogs/docsplit.py +16 -16
  28. novelwriter/dialogs/editlabel.py +6 -8
  29. novelwriter/dialogs/preferences.py +94 -68
  30. novelwriter/dialogs/projectsettings.py +10 -10
  31. novelwriter/dialogs/quotes.py +9 -5
  32. novelwriter/dialogs/wordlist.py +6 -6
  33. novelwriter/enum.py +4 -5
  34. novelwriter/extensions/configlayout.py +23 -4
  35. novelwriter/extensions/modified.py +22 -3
  36. novelwriter/extensions/{circularprogress.py → progressbars.py} +26 -3
  37. novelwriter/extensions/statusled.py +28 -22
  38. novelwriter/gui/doceditor.py +20 -11
  39. novelwriter/gui/dochighlight.py +30 -39
  40. novelwriter/gui/docviewer.py +21 -14
  41. novelwriter/gui/mainmenu.py +11 -11
  42. novelwriter/gui/outline.py +3 -3
  43. novelwriter/gui/projtree.py +19 -28
  44. novelwriter/gui/search.py +10 -1
  45. novelwriter/gui/statusbar.py +25 -29
  46. novelwriter/gui/theme.py +3 -0
  47. novelwriter/guimain.py +91 -84
  48. novelwriter/shared.py +10 -8
  49. novelwriter/text/patterns.py +113 -0
  50. novelwriter/tools/dictionaries.py +2 -8
  51. novelwriter/tools/lipsum.py +8 -12
  52. novelwriter/tools/manusbuild.py +9 -9
  53. novelwriter/tools/manuscript.py +10 -5
  54. novelwriter/tools/manussettings.py +7 -3
  55. novelwriter/tools/noveldetails.py +10 -10
  56. novelwriter/tools/welcome.py +10 -10
  57. novelwriter/tools/writingstats.py +3 -3
  58. novelwriter/types.py +5 -2
  59. novelwriter/extensions/simpleprogress.py +0 -53
  60. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/LICENSE.md +0 -0
  61. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/entry_points.txt +0 -0
  62. {novelWriter-2.5b1.dist-info → novelWriter-2.5rc1.dist-info}/top_level.txt +0 -0
@@ -33,7 +33,7 @@ from enum import Enum
33
33
  from PyQt5.QtCore import QPoint, Qt, QUrl, pyqtSignal, pyqtSlot
34
34
  from PyQt5.QtGui import QCursor, QMouseEvent, QPalette, QResizeEvent, QTextCursor
35
35
  from PyQt5.QtWidgets import (
36
- QAction, QApplication, QFrame, QHBoxLayout, QLabel, QMenu, QTextBrowser,
36
+ QAction, QApplication, QFrame, QHBoxLayout, QMenu, QTextBrowser,
37
37
  QToolButton, QWidget
38
38
  )
39
39
 
@@ -42,6 +42,7 @@ from novelwriter.constants import nwHeaders, nwUnicode
42
42
  from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument
43
43
  from novelwriter.enum import nwDocAction, nwDocMode, nwItemType
44
44
  from novelwriter.error import logException
45
+ from novelwriter.extensions.configlayout import NColourLabel
45
46
  from novelwriter.extensions.eventfilters import WheelEventFilter
46
47
  from novelwriter.extensions.modified import NIconToolButton
47
48
  from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
@@ -92,6 +93,9 @@ class GuiDocViewer(QTextBrowser):
92
93
  self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
93
94
  self.customContextMenuRequested.connect(self._openContextMenu)
94
95
 
96
+ # Function Mapping
97
+ self.changeFocusState = self.docHeader.changeFocusState
98
+
95
99
  self.initViewer()
96
100
 
97
101
  logger.debug("Ready: GuiDocViewer")
@@ -165,6 +169,8 @@ class GuiDocViewer(QTextBrowser):
165
169
  self._docTheme.keyword = SHARED.theme.colKey
166
170
  self._docTheme.tag = SHARED.theme.colTag
167
171
  self._docTheme.optional = SHARED.theme.colOpt
172
+ self._docTheme.dialog = SHARED.theme.colDialN
173
+ self._docTheme.altdialog = SHARED.theme.colDialA
168
174
 
169
175
  # Set default text margins
170
176
  self.document().setDocumentMargin(0)
@@ -201,6 +207,7 @@ class GuiDocViewer(QTextBrowser):
201
207
  sPos = self.verticalScrollBar().value()
202
208
  qDoc = ToQTextDocument(SHARED.project)
203
209
  qDoc.setJustify(CONFIG.doJustify)
210
+ qDoc.setDialogueHighlight(True)
204
211
  qDoc.initDocument(CONFIG.textFont, self._docTheme)
205
212
  qDoc.setKeywords(True)
206
213
  qDoc.setComments(CONFIG.viewComments)
@@ -245,9 +252,6 @@ class GuiDocViewer(QTextBrowser):
245
252
  })
246
253
  self.updateDocMargins()
247
254
 
248
- # Since we change the content while it may still be rendering, we mark
249
- # the document dirty again to make sure it's re-rendered properly.
250
- self.redrawText()
251
255
  QApplication.restoreOverrideCursor()
252
256
  self.documentLoaded.emit(tHandle)
253
257
 
@@ -259,12 +263,6 @@ class GuiDocViewer(QTextBrowser):
259
263
  self.loadText(self._docHandle, updateHistory=False)
260
264
  return
261
265
 
262
- def redrawText(self) -> None:
263
- """Redraw the text by marking the content as "dirty"."""
264
- self.document().markContentsDirty(0, self.document().characterCount())
265
- self.updateDocMargins()
266
- return
267
-
268
266
  def docAction(self, action: nwDocAction) -> bool:
269
267
  """Process document actions on the current document."""
270
268
  logger.debug("Requesting action: '%s'", action.name)
@@ -284,6 +282,10 @@ class GuiDocViewer(QTextBrowser):
284
282
  return False
285
283
  return True
286
284
 
285
+ def anyFocus(self) -> bool:
286
+ """Check if any widget or child widget has focus."""
287
+ return self.hasFocus() or self.isAncestorOf(QApplication.focusWidget())
288
+
287
289
  def clearNavHistory(self) -> None:
288
290
  """Clear the navigation history."""
289
291
  self.docHistory.clear()
@@ -370,8 +372,10 @@ class GuiDocViewer(QTextBrowser):
370
372
  """Process a clicked link in the document."""
371
373
  if link := url.url():
372
374
  logger.debug("Clicked link: '%s'", link)
373
- if (bits := link.partition("_")) and bits[2]:
375
+ if (bits := link.partition("_")) and bits[0] == "#tag" and bits[2]:
374
376
  self.loadDocumentTagRequest.emit(bits[2], nwDocMode.VIEW)
377
+ else:
378
+ self.navigateTo(link)
375
379
  return
376
380
 
377
381
  @pyqtSlot("QPoint")
@@ -601,9 +605,7 @@ class GuiDocViewHeader(QWidget):
601
605
  self.setAutoFillBackground(True)
602
606
 
603
607
  # Title Label
604
- self.itemTitle = QLabel(self)
605
- self.itemTitle.setText("")
606
- self.itemTitle.setIndent(0)
608
+ self.itemTitle = NColourLabel("", self, faded=SHARED.theme.fadedText)
607
609
  self.itemTitle.setMargin(0)
608
610
  self.itemTitle.setContentsMargins(0, 0, 0, 0)
609
611
  self.itemTitle.setAutoFillBackground(True)
@@ -742,6 +744,11 @@ class GuiDocViewHeader(QWidget):
742
744
  self.itemTitle.setPalette(palette)
743
745
  return
744
746
 
747
+ def changeFocusState(self, state: bool) -> None:
748
+ """Toggle focus state."""
749
+ self.itemTitle.setColorState(state)
750
+ return
751
+
745
752
  def setHandle(self, tHandle: str) -> None:
746
753
  """Sets the document title from the handle, or alternatively,
747
754
  set the whole document path.
@@ -35,7 +35,7 @@ from PyQt5.QtWidgets import QAction, QMenuBar
35
35
  from novelwriter import CONFIG, SHARED
36
36
  from novelwriter.common import openExternalPath
37
37
  from novelwriter.constants import nwConst, nwKeyWords, nwLabels, nwUnicode, trConst
38
- from novelwriter.enum import nwDocAction, nwDocInsert, nwView, nwWidget
38
+ from novelwriter.enum import nwDocAction, nwDocInsert, nwFocus, nwView
39
39
  from novelwriter.extensions.eventfilters import StatusTipFilter
40
40
 
41
41
  if TYPE_CHECKING: # pragma: no cover
@@ -54,7 +54,7 @@ class GuiMainMenu(QMenuBar):
54
54
  requestDocInsert = pyqtSignal(nwDocInsert)
55
55
  requestDocInsertText = pyqtSignal(str)
56
56
  requestDocKeyWordInsert = pyqtSignal(str)
57
- requestFocusChange = pyqtSignal(nwWidget)
57
+ requestFocusChange = pyqtSignal(nwFocus)
58
58
  requestViewChange = pyqtSignal(nwView)
59
59
 
60
60
  def __init__(self, mainGui: GuiMain) -> None:
@@ -167,7 +167,7 @@ class GuiMainMenu(QMenuBar):
167
167
  # Project > Edit
168
168
  self.aEditItem = self.projMenu.addAction(self.tr("Rename Item"))
169
169
  self.aEditItem.setShortcut("F2")
170
- self.aEditItem.triggered.connect(lambda: self.mainGui.editItemLabel(None))
170
+ self.aEditItem.triggered.connect(lambda: self.mainGui.projView.renameTreeItem(None))
171
171
 
172
172
  # Project > Delete
173
173
  self.aDeleteItem = self.projMenu.addAction(self.tr("Delete Item"))
@@ -303,24 +303,24 @@ class GuiMainMenu(QMenuBar):
303
303
  self.viewMenu = self.addMenu(self.tr("&View"))
304
304
 
305
305
  # View > TreeView
306
- self.aFocusTree = self.viewMenu.addAction(self.tr("Go to Project Tree"))
306
+ self.aFocusTree = self.viewMenu.addAction(self.tr("Go to Tree View"))
307
307
  self.aFocusTree.setShortcut("Ctrl+T")
308
308
  self.aFocusTree.triggered.connect(
309
- lambda: self.requestFocusChange.emit(nwWidget.TREE)
309
+ lambda: self.requestFocusChange.emit(nwFocus.TREE)
310
310
  )
311
311
 
312
312
  # View > Document Editor
313
- self.aFocusEditor = self.viewMenu.addAction(self.tr("Go to Document Editor"))
314
- self.aFocusEditor.setShortcut("Ctrl+E")
315
- self.aFocusEditor.triggered.connect(
316
- lambda: self.requestFocusChange.emit(nwWidget.EDITOR)
313
+ self.aFocusDocument = self.viewMenu.addAction(self.tr("Go to Document"))
314
+ self.aFocusDocument.setShortcut("Ctrl+E")
315
+ self.aFocusDocument.triggered.connect(
316
+ lambda: self.requestFocusChange.emit(nwFocus.DOCUMENT)
317
317
  )
318
318
 
319
319
  # View > Outline
320
320
  self.aFocusOutline = self.viewMenu.addAction(self.tr("Go to Outline"))
321
321
  self.aFocusOutline.setShortcut("Ctrl+Shift+T")
322
322
  self.aFocusOutline.triggered.connect(
323
- lambda: self.requestFocusChange.emit(nwWidget.OUTLINE)
323
+ lambda: self.requestFocusChange.emit(nwFocus.OUTLINE)
324
324
  )
325
325
 
326
326
  # View > Separator
@@ -422,7 +422,7 @@ class GuiMainMenu(QMenuBar):
422
422
  self.aInsMSApos = self.mInsQuotes.addAction(self.tr("Alternative Apostrophe"))
423
423
  self.aInsMSApos.setShortcut("Ctrl+K, '")
424
424
  self.aInsMSApos.triggered.connect(
425
- lambda: self.requestDocInsertText.emit(nwUnicode.U_MAPOSS)
425
+ lambda: self.requestDocInsertText.emit(nwUnicode.U_MAPOS)
426
426
  )
427
427
 
428
428
  # Insert > Symbols
@@ -215,7 +215,7 @@ class GuiOutlineToolBar(QToolBar):
215
215
 
216
216
  # Novel Selector
217
217
  self.novelLabel = NColourLabel(
218
- self.tr("Outline of"), parent=self, scale=NColourLabel.HEADER_SCALE, bold=True
218
+ self.tr("Outline of"), self, scale=NColourLabel.HEADER_SCALE, bold=True
219
219
  )
220
220
  self.novelLabel.setContentsMargins(0, 0, CONFIG.pxInt(12), 0)
221
221
 
@@ -523,12 +523,12 @@ class GuiOutlineTree(QTreeWidget):
523
523
  @pyqtSlot()
524
524
  def exportOutline(self) -> None:
525
525
  """Export the outline as a CSV file."""
526
- path = CONFIG.lastPath() / f"{makeFileNameSafe(SHARED.project.data.name)}.csv"
526
+ path = CONFIG.lastPath("outline") / f"{makeFileNameSafe(SHARED.project.data.name)}.csv"
527
527
  path, _ = QFileDialog.getSaveFileName(
528
528
  self, self.tr("Save Outline As"), str(path), formatFileFilter(["*.csv", "*"])
529
529
  )
530
530
  if path:
531
- CONFIG.setLastPath(path)
531
+ CONFIG.setLastPath("outline", path)
532
532
  logger.info("Writing CSV file: %s", path)
533
533
  cols = [col for col in self._treeOrder if not self._colHidden[col]]
534
534
  order = [self._colIdx[col] for col in cols]
@@ -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
@@ -998,7 +997,7 @@ class GuiProjectTree(QTreeWidget):
998
997
  trItemP.takeChild(tIndex)
999
998
 
1000
999
  for dHandle in reversed(self.getTreeFromHandle(tHandle)):
1001
- SHARED.closeDocument(dHandle)
1000
+ SHARED.closeEditor(dHandle)
1002
1001
  SHARED.project.removeItem(dHandle)
1003
1002
  self._treeMap.pop(dHandle, None)
1004
1003
 
@@ -1397,19 +1396,15 @@ class GuiProjectTree(QTreeWidget):
1397
1396
  if not newFile:
1398
1397
  itemList.remove(tHandle)
1399
1398
 
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:
1399
+ data, status = GuiDocMerge.getData(SHARED.mainGui, tHandle, itemList)
1400
+ if status:
1401
+ items = data.get("finalItems", [])
1402
+ if not items:
1408
1403
  SHARED.info(self.tr("No documents selected for merging."))
1409
1404
  return False
1410
1405
 
1411
1406
  # Save the open document first, in case it's part of merge
1412
- SHARED.saveDocument()
1407
+ SHARED.saveEditor()
1413
1408
 
1414
1409
  # Create merge object, and append docs
1415
1410
  docMerger = DocMerger(SHARED.project)
@@ -1424,7 +1419,7 @@ class GuiProjectTree(QTreeWidget):
1424
1419
  else:
1425
1420
  return False
1426
1421
 
1427
- for sHandle in mrgList:
1422
+ for sHandle in items:
1428
1423
  docMerger.appendText(sHandle, True, mLabel)
1429
1424
 
1430
1425
  if not docMerger.writeTargetDoc():
@@ -1441,8 +1436,8 @@ class GuiProjectTree(QTreeWidget):
1441
1436
  self.projView.openDocumentRequest.emit(mHandle, nwDocMode.EDIT, "", False)
1442
1437
  self.projView.setSelectedHandle(mHandle, doScroll=True)
1443
1438
 
1444
- if mrgData.get("moveToTrash", False):
1445
- for sHandle in reversed(mrgData.get("finalItems", [])):
1439
+ if data.get("moveToTrash", False):
1440
+ for sHandle in reversed(data.get("finalItems", [])):
1446
1441
  trItem = self._getTreeItem(sHandle)
1447
1442
  if isinstance(trItem, QTreeWidgetItem) and trItem.childCount() == 0:
1448
1443
  self.moveItemToTrash(sHandle, askFirst=False, flush=False)
@@ -1468,16 +1463,11 @@ class GuiProjectTree(QTreeWidget):
1468
1463
  logger.error("Only valid document items can be split")
1469
1464
  return False
1470
1465
 
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)
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)
1481
1471
 
1482
1472
  docSplit = DocSplitter(SHARED.project, tHandle)
1483
1473
  if intoFolder:
@@ -1487,7 +1477,7 @@ class GuiProjectTree(QTreeWidget):
1487
1477
  else:
1488
1478
  docSplit.setParentItem(tItem.itemParent)
1489
1479
 
1490
- docSplit.splitDocument(headerList, splitText)
1480
+ docSplit.splitDocument(headerList, text)
1491
1481
  for writeOk, dHandle, nHandle in docSplit.writeDocuments(docHierarchy):
1492
1482
  SHARED.project.index.reIndexHandle(dHandle)
1493
1483
  self.revealNewTreeItem(dHandle, nHandle=nHandle, wordCount=True)
@@ -1498,7 +1488,7 @@ class GuiProjectTree(QTreeWidget):
1498
1488
  info=docSplit.getError()
1499
1489
  )
1500
1490
 
1501
- if splitData.get("moveToTrash", False):
1491
+ if data.get("moveToTrash", False):
1502
1492
  self.moveItemToTrash(tHandle, askFirst=False, flush=True)
1503
1493
 
1504
1494
  self.saveTreeOrder()
@@ -1815,6 +1805,7 @@ class _TreeContextMenu(QMenu):
1815
1805
 
1816
1806
  def _itemHeader(self) -> None:
1817
1807
  """Check if there is a header that can be used for rename."""
1808
+ SHARED.saveEditor()
1818
1809
  if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"):
1819
1810
  action = self.addAction(self.tr("Rename to Heading"))
1820
1811
  action.triggered.connect(
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