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
novelwriter/config.py CHANGED
@@ -5,6 +5,7 @@ novelWriter – Config Class
5
5
  File History:
6
6
  Created: 2018-09-22 [0.0.1] Config
7
7
  Created: 2022-11-09 [2.0rc2] RecentProjects
8
+ Created: 2024-06-16 [2.5rc1] RecentPaths
8
9
 
9
10
  This file is a part of novelWriter
10
11
  Copyright 2018–2024, Veronica Berglyd Olsen
@@ -24,24 +25,24 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
24
25
  """
25
26
  from __future__ import annotations
26
27
 
27
- import sys
28
28
  import json
29
29
  import logging
30
+ import sys
30
31
 
31
- from time import time
32
- from pathlib import Path
33
32
  from datetime import datetime
33
+ from pathlib import Path
34
+ from time import time
34
35
 
35
- from PyQt5.QtGui import QFontDatabase
36
36
  from PyQt5.QtCore import (
37
37
  PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo,
38
38
  QLocale, QStandardPaths, QSysInfo, QTranslator
39
39
  )
40
+ from PyQt5.QtGui import QFont, QFontDatabase
40
41
  from PyQt5.QtWidgets import QApplication
41
42
 
42
- from novelwriter.error import formatException, logException
43
- from novelwriter.common import NWConfigParser, checkInt, checkPath, formatTimeStamp
43
+ from novelwriter.common import NWConfigParser, checkInt, checkPath, describeFont, formatTimeStamp
44
44
  from novelwriter.constants import nwFiles, nwUnicode
45
+ from novelwriter.error import formatException, logException
45
46
 
46
47
  logger = logging.getLogger(__name__)
47
48
 
@@ -103,18 +104,19 @@ class Config:
103
104
  # User Settings
104
105
  # =============
105
106
 
106
- self._recentObj = RecentProjects(self)
107
+ self._recentProjects = RecentProjects(self)
108
+ self._recentPaths = RecentPaths(self)
107
109
 
108
110
  # General GUI Settings
109
- self.guiLocale = self._qLocale.name()
110
- self.guiTheme = "default" # GUI theme
111
- self.guiSyntax = "default_light" # Syntax theme
112
- self.guiFont = "" # Defaults to system default font in theme class
113
- self.guiFontSize = 11 # Is overridden if system default is loaded
114
- self.guiScale = 1.0 # Set automatically by Theme class
115
- self.hideVScroll = False # Hide vertical scroll bars on main widgets
116
- self.hideHScroll = False # Hide horizontal scroll bars on main widgets
117
- self.lastNotes = "0x0" # The latest release notes that have been shown
111
+ self.guiLocale = self._qLocale.name()
112
+ self.guiTheme = "default" # GUI theme
113
+ self.guiSyntax = "default_light" # Syntax theme
114
+ self.guiFont = QFont() # Main GUI font
115
+ self.guiScale = 1.0 # Set automatically by Theme class
116
+ self.hideVScroll = False # Hide vertical scroll bars on main widgets
117
+ self.hideHScroll = False # Hide horizontal scroll bars on main widgets
118
+ self.lastNotes = "0x0" # The latest release notes that have been shown
119
+ self.nativeFont = True # Use native font dialog
118
120
 
119
121
  # Size Settings
120
122
  self._mainWinSize = [1200, 650] # Last size of the main GUI window
@@ -132,40 +134,42 @@ class Config:
132
134
  self.askBeforeBackup = True # Flag for asking before running automatic backup
133
135
 
134
136
  # Text Editor Settings
135
- self.textFont = "" # Editor font
136
- self.textSize = 12 # Editor font size
137
- self.textWidth = 700 # Editor text width
138
- self.textMargin = 40 # Editor/viewer text margin
139
- self.tabWidth = 40 # Editor tabulator width
140
-
141
- self.focusWidth = 800 # Focus Mode text width
142
- self.hideFocusFooter = False # Hide document footer in Focus Mode
143
- self.showFullPath = True # Show full document path in editor header
144
- self.autoSelect = True # Auto-select word when applying format with no selection
145
-
146
- self.doJustify = False # Justify text
147
- self.showTabsNSpaces = False # Show tabs and spaces in editor
148
- self.showLineEndings = False # Show line endings in editor
149
- self.showMultiSpaces = True # Highlight multiple spaces in the text
150
-
151
- self.doReplace = True # Enable auto-replace as you type
152
- self.doReplaceSQuote = True # Smart single quotes
153
- self.doReplaceDQuote = True # Smart double quotes
154
- self.doReplaceDash = True # Replace multiple hyphens with dashes
155
- self.doReplaceDots = True # Replace three dots with ellipsis
156
-
157
- self.autoScroll = False # Typewriter-like scrolling
158
- self.autoScrollPos = 30 # Start point for typewriter-like scrolling
159
- self.scrollPastEnd = True # Scroll past end of document, and centre cursor
160
-
161
- self.highlightQuotes = True # Highlight text in quotes
162
- self.allowOpenSQuote = False # Allow open-ended single quotes
163
- self.allowOpenDQuote = True # Allow open-ended double quotes
164
- self.highlightEmph = True # Add colour to text emphasis
165
-
166
- self.stopWhenIdle = True # Stop the status bar clock when the user is idle
167
- self.userIdleTime = 300 # Time of inactivity to consider user idle
168
- self.incNotesWCount = True # The status bar word count includes notes
137
+ self.textFont = QFont() # Editor font
138
+ self.textWidth = 700 # Editor text width
139
+ self.textMargin = 40 # Editor/viewer text margin
140
+ self.tabWidth = 40 # Editor tabulator width
141
+
142
+ self.focusWidth = 800 # Focus Mode text width
143
+ self.hideFocusFooter = False # Hide document footer in Focus Mode
144
+ self.showFullPath = True # Show full document path in editor header
145
+ self.autoSelect = True # Auto-select word when applying format with no selection
146
+
147
+ self.doJustify = False # Justify text
148
+ self.showTabsNSpaces = False # Show tabs and spaces in editor
149
+ self.showLineEndings = False # Show line endings in editor
150
+ self.showMultiSpaces = True # Highlight multiple spaces in the text
151
+
152
+ self.doReplace = True # Enable auto-replace as you type
153
+ self.doReplaceSQuote = True # Smart single quotes
154
+ self.doReplaceDQuote = True # Smart double quotes
155
+ self.doReplaceDash = True # Replace multiple hyphens with dashes
156
+ self.doReplaceDots = True # Replace three dots with ellipsis
157
+
158
+ self.autoScroll = False # Typewriter-like scrolling
159
+ self.autoScrollPos = 30 # Start point for typewriter-like scrolling
160
+ self.scrollPastEnd = True # Scroll past end of document, and centre cursor
161
+
162
+ self.dialogStyle = 2 # Quote type to use for dialogue
163
+ self.allowOpenDial = True # Allow open-ended dialogue quotes
164
+ self.narratorBreak = "" # Symbol to use for narrator break
165
+ self.dialogLine = "" # Symbol to use for dialogue line
166
+ self.altDialogOpen = "" # Alternative dialog symbol, open
167
+ self.altDialogClose = "" # Alternative dialog symbol, close
168
+ self.highlightEmph = True # Add colour to text emphasis
169
+
170
+ self.stopWhenIdle = True # Stop the status bar clock when the user is idle
171
+ self.userIdleTime = 300 # Time of inactivity to consider user idle
172
+ self.incNotesWCount = True # The status bar word count includes notes
169
173
 
170
174
  # User-Selected Symbol Settings
171
175
  self.fmtApostrophe = nwUnicode.U_RSQUO
@@ -178,7 +182,6 @@ class Config:
178
182
  self.fmtPadThin = False
179
183
 
180
184
  # User Paths
181
- self._lastPath = self._homePath # The user's last used path
182
185
  self._backupPath = self._backPath # Backup path to use, can be none
183
186
 
184
187
  # Spell Checking Settings
@@ -251,7 +254,7 @@ class Config:
251
254
 
252
255
  @property
253
256
  def recentProjects(self) -> RecentProjects:
254
- return self._recentObj
257
+ return self._recentProjects
255
258
 
256
259
  @property
257
260
  def mainWinSize(self) -> list[int]:
@@ -341,7 +344,7 @@ class Config:
341
344
  self._outlnPanePos = [int(x/self.guiScale) for x in pos]
342
345
  return
343
346
 
344
- def setLastPath(self, path: str | Path) -> None:
347
+ def setLastPath(self, key: str, path: str | Path) -> None:
345
348
  """Set the last used path. Only the folder is saved, so if the
346
349
  path is not a folder, the parent of the path is used instead.
347
350
  """
@@ -350,8 +353,7 @@ class Config:
350
353
  if not path.is_dir():
351
354
  path = path.parent
352
355
  if path.is_dir():
353
- self._lastPath = path
354
- logger.debug("Last path updated: %s" % self._lastPath)
356
+ self._recentPaths.setPath(key, path)
355
357
  return
356
358
 
357
359
  def setBackupPath(self, path: Path | str) -> None:
@@ -359,23 +361,53 @@ class Config:
359
361
  self._backupPath = checkPath(path, self._backPath)
360
362
  return
361
363
 
362
- def setTextFont(self, family: str | None, pointSize: int = 12) -> None:
364
+ def setGuiFont(self, value: QFont | str | None) -> None:
365
+ """Update the GUI's font style from settings."""
366
+ if isinstance(value, QFont):
367
+ self.guiFont = value
368
+ elif value and isinstance(value, str):
369
+ self.guiFont = QFont()
370
+ self.guiFont.fromString(value)
371
+ else:
372
+ font = QFont()
373
+ fontDB = QFontDatabase()
374
+ if self.osWindows and "Arial" in fontDB.families():
375
+ # On Windows we default to Arial if possible
376
+ font.setFamily("Arial")
377
+ font.setPointSize(10)
378
+ else:
379
+ font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
380
+ self.guiFont = font
381
+ logger.debug("GUI font set to: %s", describeFont(font))
382
+
383
+ QApplication.setFont(self.guiFont)
384
+
385
+ return
386
+
387
+ def setTextFont(self, value: QFont | str | None) -> None:
363
388
  """Set the text font if it exists. If it doesn't, or is None,
364
389
  set to default font.
365
390
  """
366
- fontDB = QFontDatabase()
367
- fontFam = fontDB.families()
368
- self.textSize = pointSize
369
- if family is None or family not in fontFam:
370
- logger.warning("Unknown font '%s'", family)
391
+ if isinstance(value, QFont):
392
+ self.textFont = value
393
+ elif value and isinstance(value, str):
394
+ self.textFont = QFont()
395
+ self.textFont.fromString(value)
396
+ else:
397
+ fontDB = QFontDatabase()
398
+ fontFam = fontDB.families()
371
399
  if self.osWindows and "Arial" in fontFam:
372
- self.textFont = "Arial"
400
+ font = QFont()
401
+ font.setFamily("Arial")
402
+ font.setPointSize(12)
373
403
  elif self.osDarwin and "Helvetica" in fontFam:
374
- self.textFont = "Helvetica"
404
+ font = QFont()
405
+ font.setFamily("Helvetica")
406
+ font.setPointSize(12)
375
407
  else:
376
- self.textFont = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont).family()
377
- else:
378
- self.textFont = family
408
+ font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
409
+ self.textFont = font
410
+ logger.debug("Text font set to: %s", describeFont(font))
379
411
  return
380
412
 
381
413
  ##
@@ -406,11 +438,12 @@ class Config:
406
438
  return self._appPath / "assets" / target
407
439
  return self._appPath / "assets"
408
440
 
409
- def lastPath(self) -> Path:
441
+ def lastPath(self, key: str) -> Path:
410
442
  """Return the last path used by the user, if it exists."""
411
- if isinstance(self._lastPath, Path):
412
- if self._lastPath.is_dir():
413
- return self._lastPath
443
+ if path := self._recentPaths.getPath(key):
444
+ asPath = Path(path)
445
+ if asPath.is_dir():
446
+ return asPath
414
447
  return self._homePath
415
448
 
416
449
  def backupPath(self) -> Path:
@@ -484,7 +517,6 @@ class Config:
484
517
  logger.debug("Data Path: %s", self._dataPath)
485
518
  logger.debug("App Root: %s", self._appRoot)
486
519
  logger.debug("App Path: %s", self._appPath)
487
- logger.debug("Last Path: %s", self._lastPath)
488
520
  logger.debug("PDF Manual: %s", self.pdfDocs)
489
521
 
490
522
  # If the config and data folders don't exist, create them
@@ -499,13 +531,8 @@ class Config:
499
531
  (self._dataPath / "syntax").mkdir(exist_ok=True)
500
532
  (self._dataPath / "themes").mkdir(exist_ok=True)
501
533
 
502
- # Check if config file exists, and load it. If not, we save defaults
503
- if (self._confPath / nwFiles.CONF_FILE).is_file():
504
- self.loadConfig()
505
- else:
506
- self.saveConfig()
507
-
508
- self._recentObj.loadCache()
534
+ self._recentPaths.loadCache()
535
+ self._recentProjects.loadCache()
509
536
  self._checkOptionalPackages()
510
537
 
511
538
  logger.debug("Config instance initialised")
@@ -545,6 +572,14 @@ class Config:
545
572
 
546
573
  conf = NWConfigParser()
547
574
  cnfPath = self._confPath / nwFiles.CONF_FILE
575
+
576
+ if not cnfPath.exists():
577
+ # Initial file, so we just create one from defaults
578
+ self.setGuiFont(None)
579
+ self.setTextFont(None)
580
+ self.saveConfig()
581
+ return True
582
+
548
583
  try:
549
584
  with open(cnfPath, mode="r", encoding="utf-8") as inFile:
550
585
  conf.read_file(inFile)
@@ -558,15 +593,14 @@ class Config:
558
593
 
559
594
  # Main
560
595
  sec = "Main"
596
+ self.setGuiFont(conf.rdStr(sec, "font", ""))
561
597
  self.guiTheme = conf.rdStr(sec, "theme", self.guiTheme)
562
598
  self.guiSyntax = conf.rdStr(sec, "syntax", self.guiSyntax)
563
- self.guiFont = conf.rdStr(sec, "font", self.guiFont)
564
- self.guiFontSize = conf.rdInt(sec, "fontsize", self.guiFontSize)
565
599
  self.guiLocale = conf.rdStr(sec, "localisation", self.guiLocale)
566
600
  self.hideVScroll = conf.rdBool(sec, "hidevscroll", self.hideVScroll)
567
601
  self.hideHScroll = conf.rdBool(sec, "hidehscroll", self.hideHScroll)
568
602
  self.lastNotes = conf.rdStr(sec, "lastnotes", self.lastNotes)
569
- self._lastPath = conf.rdPath(sec, "lastpath", self._lastPath)
603
+ self.nativeFont = conf.rdBool(sec, "nativefont", self.nativeFont)
570
604
 
571
605
  # Sizes
572
606
  sec = "Sizes"
@@ -588,8 +622,7 @@ class Config:
588
622
 
589
623
  # Editor
590
624
  sec = "Editor"
591
- self.textFont = conf.rdStr(sec, "textfont", self.textFont)
592
- self.textSize = conf.rdInt(sec, "textsize", self.textSize)
625
+ self.setTextFont(conf.rdStr(sec, "textfont", ""))
593
626
  self.textWidth = conf.rdInt(sec, "width", self.textWidth)
594
627
  self.textMargin = conf.rdInt(sec, "margin", self.textMargin)
595
628
  self.tabWidth = conf.rdInt(sec, "tabwidth", self.tabWidth)
@@ -618,9 +651,12 @@ class Config:
618
651
  self.showMultiSpaces = conf.rdBool(sec, "showmultispaces", self.showMultiSpaces)
619
652
  self.incNotesWCount = conf.rdBool(sec, "incnoteswcount", self.incNotesWCount)
620
653
  self.showFullPath = conf.rdBool(sec, "showfullpath", self.showFullPath)
621
- self.highlightQuotes = conf.rdBool(sec, "highlightquotes", self.highlightQuotes)
622
- self.allowOpenSQuote = conf.rdBool(sec, "allowopensquote", self.allowOpenSQuote)
623
- self.allowOpenDQuote = conf.rdBool(sec, "allowopendquote", self.allowOpenDQuote)
654
+ self.dialogStyle = conf.rdInt(sec, "dialogstyle", self.dialogStyle)
655
+ self.allowOpenDial = conf.rdBool(sec, "allowopendial", self.allowOpenDial)
656
+ self.narratorBreak = conf.rdStr(sec, "narratorbreak", self.narratorBreak)
657
+ self.altDialogOpen = conf.rdStr(sec, "altdialogopen", self.altDialogOpen)
658
+ self.altDialogClose = conf.rdStr(sec, "altdialogclose", self.altDialogClose)
659
+ self.dialogLine = conf.rdStr(sec, "dialogline", self.dialogLine)
624
660
  self.highlightEmph = conf.rdBool(sec, "highlightemph", self.highlightEmph)
625
661
  self.stopWhenIdle = conf.rdBool(sec, "stopwhenidle", self.stopWhenIdle)
626
662
  self.userIdleTime = conf.rdInt(sec, "useridletime", self.userIdleTime)
@@ -666,15 +702,14 @@ class Config:
666
702
  }
667
703
 
668
704
  conf["Main"] = {
705
+ "font": self.guiFont.toString(),
669
706
  "theme": str(self.guiTheme),
670
707
  "syntax": str(self.guiSyntax),
671
- "font": str(self.guiFont),
672
- "fontsize": str(self.guiFontSize),
673
708
  "localisation": str(self.guiLocale),
674
709
  "hidevscroll": str(self.hideVScroll),
675
710
  "hidehscroll": str(self.hideHScroll),
676
711
  "lastnotes": str(self.lastNotes),
677
- "lastpath": str(self._lastPath),
712
+ "nativefont": str(self.nativeFont),
678
713
  }
679
714
 
680
715
  conf["Sizes"] = {
@@ -696,8 +731,7 @@ class Config:
696
731
  }
697
732
 
698
733
  conf["Editor"] = {
699
- "textfont": str(self.textFont),
700
- "textsize": str(self.textSize),
734
+ "textfont": self.textFont.toString(),
701
735
  "width": str(self.textWidth),
702
736
  "margin": str(self.textMargin),
703
737
  "tabwidth": str(self.tabWidth),
@@ -726,9 +760,12 @@ class Config:
726
760
  "showmultispaces": str(self.showMultiSpaces),
727
761
  "incnoteswcount": str(self.incNotesWCount),
728
762
  "showfullpath": str(self.showFullPath),
729
- "highlightquotes": str(self.highlightQuotes),
730
- "allowopensquote": str(self.allowOpenSQuote),
731
- "allowopendquote": str(self.allowOpenDQuote),
763
+ "dialogstyle": str(self.dialogStyle),
764
+ "allowopendial": str(self.allowOpenDial),
765
+ "narratorbreak": str(self.narratorBreak),
766
+ "altdialogopen": str(self.altDialogOpen),
767
+ "altdialogclose": str(self.altDialogClose),
768
+ "dialogline": str(self.dialogLine),
732
769
  "highlightemph": str(self.highlightEmph),
733
770
  "stopwhenidle": str(self.stopWhenIdle),
734
771
  "useridletime": str(self.userIdleTime),
@@ -773,7 +810,7 @@ class Config:
773
810
  """Pack a list of items into a comma-separated string for saving
774
811
  to the config file.
775
812
  """
776
- return ", ".join([str(inVal) for inVal in data])
813
+ return ", ".join(str(inVal) for inVal in data)
777
814
 
778
815
  def _checkOptionalPackages(self) -> None:
779
816
  """Check optional packages used by some features."""
@@ -787,8 +824,6 @@ class Config:
787
824
  logger.debug("Checking package 'pyenchant': OK")
788
825
  return
789
826
 
790
- # END Class Config
791
-
792
827
 
793
828
  class RecentProjects:
794
829
 
@@ -858,4 +893,55 @@ class RecentProjects:
858
893
  self.saveCache()
859
894
  return
860
895
 
861
- # END Class RecentProjects
896
+
897
+ class RecentPaths:
898
+
899
+ KEYS = ["default", "project", "import", "outline", "stats"]
900
+
901
+ def __init__(self, config: Config) -> None:
902
+ self._conf = config
903
+ self._data = {}
904
+ return
905
+
906
+ def setPath(self, key: str, path: Path | str) -> None:
907
+ """Set a path for a given key, and save the cache."""
908
+ if key in self.KEYS:
909
+ self._data[key] = str(path)
910
+ self.saveCache()
911
+ return
912
+
913
+ def getPath(self, key: str) -> str | None:
914
+ """Get a path for a given key, or return None."""
915
+ return self._data.get(key)
916
+
917
+ def loadCache(self) -> bool:
918
+ """Load the cache file for recent paths."""
919
+ self._data = {}
920
+ cacheFile = self._conf.dataPath(nwFiles.RECENT_PATH)
921
+ if cacheFile.is_file():
922
+ try:
923
+ with open(cacheFile, mode="r", encoding="utf-8") as inFile:
924
+ data = json.load(inFile)
925
+ if isinstance(data, dict):
926
+ for key, path in data.items():
927
+ if key in self.KEYS and isinstance(path, str):
928
+ self._data[key] = path
929
+ except Exception:
930
+ logger.error("Could not load recent paths cache")
931
+ logException()
932
+ return False
933
+ return True
934
+
935
+ def saveCache(self) -> bool:
936
+ """Save the cache dictionary of recent paths."""
937
+ cacheFile = self._conf.dataPath(nwFiles.RECENT_PATH)
938
+ cacheTemp = cacheFile.with_suffix(".tmp")
939
+ try:
940
+ with open(cacheTemp, mode="w+", encoding="utf-8") as outFile:
941
+ json.dump(self._data, outFile, indent=2)
942
+ cacheTemp.replace(cacheFile)
943
+ except Exception:
944
+ logger.error("Could not save recent paths cache")
945
+ logException()
946
+ return False
947
+ return True
novelwriter/constants.py CHANGED
@@ -23,9 +23,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
23
23
  """
24
24
  from __future__ import annotations
25
25
 
26
- from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
26
+ from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
27
27
 
28
- from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
28
+ from novelwriter.enum import (
29
+ nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape
30
+ )
29
31
 
30
32
 
31
33
  def trConst(text: str) -> str:
@@ -55,21 +57,14 @@ class nwConst:
55
57
  STATUS_MSG_TIMEOUT = 15000 # milliseconds
56
58
  MAX_SEARCH_RESULT = 1000
57
59
 
58
- # Dialogs
59
- DLG_FINISHED = 2
60
-
61
- # END Class nwConst
62
-
63
60
 
64
61
  class nwRegEx:
65
62
 
66
63
  FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
67
- FMT_EB = r"(?<![\w\\])([\*]{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
68
- FMT_ST = r"(?<![\w\\])([~]{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)"
69
- FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:i|b|s|u|m|sup|sub)\])"
70
- FMT_SV = r"(?<!\\)(\[(?i)(?:fn|footnote):)(.+?)(?<!\\)(\])"
71
-
72
- # END Class nwRegEx
64
+ FMT_EB = r"(?<![\w\\])(\*{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
65
+ FMT_ST = r"(?<![\w\\])(~{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)"
66
+ FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:b|i|s|u|m|sup|sub)\])"
67
+ FMT_SV = r"(?i)(?<!\\)(\[(?:footnote):)(.+?)(?<!\\)(\])"
73
68
 
74
69
 
75
70
  class nwShortcode:
@@ -89,15 +84,19 @@ class nwShortcode:
89
84
  SUB_O = "[sub]"
90
85
  SUB_C = "[/sub]"
91
86
 
92
- # END Class nwShortcode
87
+ FOOTNOTE_B = "[footnote:"
88
+
89
+ COMMENT_STYLES = {
90
+ nwComment.FOOTNOTE: "[footnote:{0}]",
91
+ nwComment.COMMENT: "[comment:{0}]",
92
+ }
93
93
 
94
94
 
95
95
  class nwHeaders:
96
96
 
97
97
  H_VALID = ("H0", "H1", "H2", "H3", "H4")
98
98
  H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
99
-
100
- # END Class nwHeaders
99
+ H_SIZES = {0: 2.50, 1: 2.00, 2: 1.75, 3: 1.50, 4: 1.25}
101
100
 
102
101
 
103
102
  class nwFiles:
@@ -105,6 +104,7 @@ class nwFiles:
105
104
  # Config Files
106
105
  CONF_FILE = "novelwriter.conf"
107
106
  RECENT_FILE = "recentProjects.json"
107
+ RECENT_PATH = "recentPaths.json"
108
108
 
109
109
  # Project Root Files
110
110
  PROJ_FILE = "nwProject.nwx"
@@ -118,8 +118,6 @@ class nwFiles:
118
118
  DICT_FILE = "userdict.json"
119
119
  SESS_FILE = "sessions.jsonl"
120
120
 
121
- # END Class nwFiles
122
-
123
121
 
124
122
  class nwKeyWords:
125
123
 
@@ -153,8 +151,6 @@ class nwKeyWords:
153
151
  CUSTOM_KEY: nwItemClass.CUSTOM,
154
152
  }
155
153
 
156
- # END Class nwKeyWords
157
-
158
154
 
159
155
  class nwLists:
160
156
 
@@ -168,8 +164,6 @@ class nwLists:
168
164
  nwItemClass.CUSTOM,
169
165
  ]
170
166
 
171
- # END Class nwLists
172
-
173
167
 
174
168
  class nwLabels:
175
169
 
@@ -268,6 +262,34 @@ class nwLabels:
268
262
  nwBuildFmt.J_HTML: ".json",
269
263
  nwBuildFmt.J_NWD: ".json",
270
264
  }
265
+ SHAPES_PLAIN = {
266
+ nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"),
267
+ nwStatusShape.TRIANGLE: QT_TRANSLATE_NOOP("Constant", "Triangle"),
268
+ nwStatusShape.NABLA: QT_TRANSLATE_NOOP("Constant", "Nabla"),
269
+ nwStatusShape.DIAMOND: QT_TRANSLATE_NOOP("Constant", "Diamond"),
270
+ nwStatusShape.PENTAGON: QT_TRANSLATE_NOOP("Constant", "Pentagon"),
271
+ nwStatusShape.HEXAGON: QT_TRANSLATE_NOOP("Constant", "Hexagon"),
272
+ nwStatusShape.STAR: QT_TRANSLATE_NOOP("Constant", "Star"),
273
+ nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Constant", "Pacman"),
274
+ }
275
+ SHAPES_CIRCLE = {
276
+ nwStatusShape.CIRCLE_Q: QT_TRANSLATE_NOOP("Constant", "1/4 Circle"),
277
+ nwStatusShape.CIRCLE_H: QT_TRANSLATE_NOOP("Constant", "Half Circle"),
278
+ nwStatusShape.CIRCLE_T: QT_TRANSLATE_NOOP("Constant", "3/4 Circle"),
279
+ nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Full Circle"),
280
+ }
281
+ SHAPES_BARS = {
282
+ nwStatusShape.BARS_1: QT_TRANSLATE_NOOP("Constant", "1 Bar"),
283
+ nwStatusShape.BARS_2: QT_TRANSLATE_NOOP("Constant", "2 Bars"),
284
+ nwStatusShape.BARS_3: QT_TRANSLATE_NOOP("Constant", "3 Bars"),
285
+ nwStatusShape.BARS_4: QT_TRANSLATE_NOOP("Constant", "4 Bars"),
286
+ }
287
+ SHAPES_BLOCKS = {
288
+ nwStatusShape.BLOCK_1: QT_TRANSLATE_NOOP("Constant", "1 Block"),
289
+ nwStatusShape.BLOCK_2: QT_TRANSLATE_NOOP("Constant", "2 Blocks"),
290
+ nwStatusShape.BLOCK_3: QT_TRANSLATE_NOOP("Constant", "3 Blocks"),
291
+ nwStatusShape.BLOCK_4: QT_TRANSLATE_NOOP("Constant", "4 Blocks"),
292
+ }
271
293
  FILE_FILTERS = {
272
294
  "*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"),
273
295
  "*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"),
@@ -302,8 +324,6 @@ class nwLabels:
302
324
  "Custom": (-1.0, -1.0),
303
325
  }
304
326
 
305
- # END Class nwLabels
306
-
307
327
 
308
328
  class nwHeadFmt:
309
329
 
@@ -329,8 +349,6 @@ class nwHeadFmt:
329
349
  ODT_PAGE = "{Page}"
330
350
  ODT_AUTO = "{Project} / {Author} / {Page}"
331
351
 
332
- # END Class nwHeadFmt
333
-
334
352
 
335
353
  class nwQuotes:
336
354
  """Allowed quotation marks.
@@ -361,8 +379,6 @@ class nwQuotes:
361
379
  "\u300f": QT_TRANSLATE_NOOP("Constant", "Right white corner bracket"),
362
380
  }
363
381
 
364
- # END Class nwQuotes
365
-
366
382
 
367
383
  class nwUnicode:
368
384
  """Supported unicode character constants and their HTML equivalents."""
@@ -396,7 +412,7 @@ class nwUnicode:
396
412
  U_EMDASH = "\u2014" # Long dash
397
413
  U_HBAR = "\u2015" # Horizontal bar
398
414
  U_HELLIP = "\u2026" # Ellipsis
399
- U_MAPOSS = "\u02bc" # Modifier letter single apostrophe
415
+ U_MAPOS = "\u02bc" # Modifier letter single apostrophe
400
416
  U_PRIME = "\u2032" # Prime
401
417
  U_DPRIME = "\u2033" # Double prime
402
418
 
@@ -463,7 +479,7 @@ class nwUnicode:
463
479
  H_EMDASH = "&mdash;"
464
480
  H_HBAR = "&#8213;"
465
481
  H_HELLIP = "&hellip;"
466
- H_MAPOSS = "&#700;"
482
+ H_MAPOS = "&#700;"
467
483
  H_PRIME = "&prime;"
468
484
  H_DPRIME = "&#8243;"
469
485
 
@@ -497,8 +513,6 @@ class nwUnicode:
497
513
  H_LTRI = "&#9664;"
498
514
  H_LTRIS = "&#9666;"
499
515
 
500
- # END Class nwUnicode
501
-
502
516
 
503
517
  class nwHtmlUnicode():
504
518
 
@@ -530,7 +544,7 @@ class nwHtmlUnicode():
530
544
  nwUnicode.U_EMDASH: nwUnicode.H_EMDASH,
531
545
  nwUnicode.U_HBAR: nwUnicode.H_HBAR,
532
546
  nwUnicode.U_HELLIP: nwUnicode.H_HELLIP,
533
- nwUnicode.U_MAPOSS: nwUnicode.H_MAPOSS,
547
+ nwUnicode.U_MAPOS: nwUnicode.H_MAPOS,
534
548
  nwUnicode.U_PRIME: nwUnicode.H_PRIME,
535
549
  nwUnicode.U_DPRIME: nwUnicode.H_DPRIME,
536
550
 
@@ -554,5 +568,3 @@ class nwHtmlUnicode():
554
568
  nwUnicode.U_TIMES: nwUnicode.H_TIMES,
555
569
  nwUnicode.U_DIVIDE: nwUnicode.H_DIVIDE,
556
570
  }
557
-
558
- # END Class nwHtmlUnicode