novelWriter 2.4.4__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 (110) hide show
  1. {novelWriter-2.4.4.dist-info → novelWriter-2.5rc1.dist-info}/METADATA +4 -5
  2. {novelWriter-2.4.4.dist-info → novelWriter-2.5rc1.dist-info}/RECORD +109 -101
  3. {novelWriter-2.4.4.dist-info → novelWriter-2.5rc1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +33 -39
  5. novelwriter/assets/i18n/project_en_GB.json +1 -0
  6. novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
  7. novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
  8. novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
  9. novelwriter/assets/icons/typicons_light/icons.conf +2 -0
  10. novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
  11. novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
  12. novelwriter/assets/manual.pdf +0 -0
  13. novelwriter/assets/sample.zip +0 -0
  14. novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
  15. novelwriter/assets/syntax/default_dark.conf +32 -18
  16. novelwriter/assets/syntax/default_light.conf +24 -10
  17. novelwriter/assets/syntax/dracula.conf +44 -0
  18. novelwriter/assets/syntax/grey_dark.conf +5 -4
  19. novelwriter/assets/syntax/grey_light.conf +5 -4
  20. novelwriter/assets/syntax/light_owl.conf +7 -6
  21. novelwriter/assets/syntax/night_owl.conf +7 -6
  22. novelwriter/assets/syntax/snazzy.conf +42 -0
  23. novelwriter/assets/syntax/solarized_dark.conf +4 -3
  24. novelwriter/assets/syntax/solarized_light.conf +4 -3
  25. novelwriter/assets/syntax/tango.conf +27 -11
  26. novelwriter/assets/syntax/tomorrow.conf +6 -5
  27. novelwriter/assets/syntax/tomorrow_night.conf +7 -6
  28. novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
  29. novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
  30. novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
  31. novelwriter/assets/text/credits_en.htm +4 -1
  32. novelwriter/assets/themes/cyberpunk_night.conf +3 -0
  33. novelwriter/assets/themes/default_dark.conf +2 -0
  34. novelwriter/assets/themes/default_light.conf +2 -0
  35. novelwriter/assets/themes/dracula.conf +48 -0
  36. novelwriter/assets/themes/solarized_dark.conf +2 -0
  37. novelwriter/assets/themes/solarized_light.conf +2 -0
  38. novelwriter/common.py +33 -12
  39. novelwriter/config.py +184 -98
  40. novelwriter/constants.py +47 -35
  41. novelwriter/core/buildsettings.py +68 -69
  42. novelwriter/core/coretools.py +5 -23
  43. novelwriter/core/docbuild.py +52 -40
  44. novelwriter/core/document.py +3 -5
  45. novelwriter/core/index.py +115 -45
  46. novelwriter/core/item.py +8 -19
  47. novelwriter/core/options.py +2 -4
  48. novelwriter/core/project.py +23 -57
  49. novelwriter/core/projectdata.py +1 -3
  50. novelwriter/core/projectxml.py +12 -15
  51. novelwriter/core/sessions.py +3 -5
  52. novelwriter/core/spellcheck.py +4 -9
  53. novelwriter/core/status.py +211 -164
  54. novelwriter/core/storage.py +0 -8
  55. novelwriter/core/tohtml.py +139 -105
  56. novelwriter/core/tokenizer.py +278 -122
  57. novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
  58. novelwriter/core/toodt.py +257 -166
  59. novelwriter/core/toqdoc.py +419 -0
  60. novelwriter/core/tree.py +5 -7
  61. novelwriter/dialogs/about.py +11 -18
  62. novelwriter/dialogs/docmerge.py +17 -19
  63. novelwriter/dialogs/docsplit.py +17 -19
  64. novelwriter/dialogs/editlabel.py +6 -10
  65. novelwriter/dialogs/preferences.py +193 -144
  66. novelwriter/dialogs/projectsettings.py +225 -189
  67. novelwriter/dialogs/quotes.py +12 -9
  68. novelwriter/dialogs/wordlist.py +9 -15
  69. novelwriter/enum.py +35 -30
  70. novelwriter/error.py +8 -15
  71. novelwriter/extensions/configlayout.py +40 -21
  72. novelwriter/extensions/eventfilters.py +1 -5
  73. novelwriter/extensions/modified.py +58 -14
  74. novelwriter/extensions/novelselector.py +1 -3
  75. novelwriter/extensions/pagedsidebar.py +9 -12
  76. novelwriter/extensions/{circularprogress.py → progressbars.py} +30 -8
  77. novelwriter/extensions/statusled.py +29 -25
  78. novelwriter/extensions/switch.py +4 -6
  79. novelwriter/extensions/switchbox.py +7 -6
  80. novelwriter/extensions/versioninfo.py +3 -9
  81. novelwriter/gui/doceditor.py +118 -137
  82. novelwriter/gui/dochighlight.py +231 -186
  83. novelwriter/gui/docviewer.py +66 -107
  84. novelwriter/gui/docviewerpanel.py +3 -10
  85. novelwriter/gui/editordocument.py +1 -3
  86. novelwriter/gui/itemdetails.py +7 -11
  87. novelwriter/gui/mainmenu.py +22 -18
  88. novelwriter/gui/noveltree.py +11 -24
  89. novelwriter/gui/outline.py +14 -26
  90. novelwriter/gui/projtree.py +35 -60
  91. novelwriter/gui/search.py +10 -3
  92. novelwriter/gui/sidebar.py +2 -6
  93. novelwriter/gui/statusbar.py +29 -37
  94. novelwriter/gui/theme.py +26 -48
  95. novelwriter/guimain.py +134 -148
  96. novelwriter/shared.py +36 -32
  97. novelwriter/text/patterns.py +113 -0
  98. novelwriter/tools/dictionaries.py +10 -20
  99. novelwriter/tools/lipsum.py +10 -16
  100. novelwriter/tools/manusbuild.py +9 -11
  101. novelwriter/tools/manuscript.py +71 -145
  102. novelwriter/tools/manussettings.py +71 -75
  103. novelwriter/tools/noveldetails.py +16 -21
  104. novelwriter/tools/welcome.py +12 -26
  105. novelwriter/tools/writingstats.py +9 -12
  106. novelwriter/types.py +49 -4
  107. novelwriter/extensions/simpleprogress.py +0 -55
  108. {novelWriter-2.4.4.dist-info → novelWriter-2.5rc1.dist-info}/LICENSE.md +0 -0
  109. {novelWriter-2.4.4.dist-info → novelWriter-2.5rc1.dist-info}/entry_points.txt +0 -0
  110. {novelWriter-2.4.4.dist-info → novelWriter-2.5rc1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,113 @@
1
+ """
2
+ novelWriter – Text Pattern Functions
3
+ ====================================
4
+
5
+ File History:
6
+ Created: 2024-06-01 [2.5ec1]
7
+
8
+ This file is a part of novelWriter
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
+
11
+ This program is free software: you can redistribute it and/or modify
12
+ it under the terms of the GNU General Public License as published by
13
+ the Free Software Foundation, either version 3 of the License, or
14
+ (at your option) any later version.
15
+
16
+ This program is distributed in the hope that it will be useful, but
17
+ WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19
+ General Public License for more details.
20
+
21
+ You should have received a copy of the GNU General Public License
22
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ from PyQt5.QtCore import QRegularExpression
27
+
28
+ from novelwriter import CONFIG
29
+ from novelwriter.constants import nwRegEx
30
+ from novelwriter.types import QRegExUnicode
31
+
32
+
33
+ class RegExPatterns:
34
+
35
+ @property
36
+ def markdownItalic(self) -> QRegularExpression:
37
+ """Markdown italic style."""
38
+ rxRule = QRegularExpression(nwRegEx.FMT_EI)
39
+ rxRule.setPatternOptions(QRegExUnicode)
40
+ return rxRule
41
+
42
+ @property
43
+ def markdownBold(self) -> QRegularExpression:
44
+ """Markdown bold style."""
45
+ rxRule = QRegularExpression(nwRegEx.FMT_EB)
46
+ rxRule.setPatternOptions(QRegExUnicode)
47
+ return rxRule
48
+
49
+ @property
50
+ def markdownStrike(self) -> QRegularExpression:
51
+ """Markdown strikethrough style."""
52
+ rxRule = QRegularExpression(nwRegEx.FMT_ST)
53
+ rxRule.setPatternOptions(QRegExUnicode)
54
+ return rxRule
55
+
56
+ @property
57
+ def shortcodePlain(self) -> QRegularExpression:
58
+ """Plain shortcode style."""
59
+ rxRule = QRegularExpression(nwRegEx.FMT_SC)
60
+ rxRule.setPatternOptions(QRegExUnicode)
61
+ return rxRule
62
+
63
+ @property
64
+ def shortcodeValue(self) -> QRegularExpression:
65
+ """Plain shortcode style."""
66
+ rxRule = QRegularExpression(nwRegEx.FMT_SV)
67
+ rxRule.setPatternOptions(QRegExUnicode)
68
+ return rxRule
69
+
70
+ @property
71
+ def dialogStyle(self) -> QRegularExpression:
72
+ """Dialogue detection rule based on user settings."""
73
+ symO = ""
74
+ symC = ""
75
+ if CONFIG.dialogStyle in (1, 3):
76
+ symO += CONFIG.fmtSQuoteOpen
77
+ symC += CONFIG.fmtSQuoteClose
78
+ if CONFIG.dialogStyle in (2, 3):
79
+ symO += CONFIG.fmtDQuoteOpen
80
+ symC += CONFIG.fmtDQuoteClose
81
+
82
+ rxEnd = "|$" if CONFIG.allowOpenDial else ""
83
+ rxRule = QRegularExpression(f"\\B[{symO}].*?(?:[{symC}]\\B{rxEnd})")
84
+ rxRule.setPatternOptions(QRegExUnicode)
85
+ return rxRule
86
+
87
+ @property
88
+ def dialogLine(self) -> QRegularExpression:
89
+ """Dialogue line rule based on user settings."""
90
+ sym = QRegularExpression.escape(CONFIG.dialogLine)
91
+ rxRule = QRegularExpression(f"^{sym}.*?$")
92
+ rxRule.setPatternOptions(QRegExUnicode)
93
+ return rxRule
94
+
95
+ @property
96
+ def narratorBreak(self) -> QRegularExpression:
97
+ """Dialogue narrator break rule based on user settings."""
98
+ sym = QRegularExpression.escape(CONFIG.narratorBreak)
99
+ rxRule = QRegularExpression(f"\\B{sym}\\S.*?\\S{sym}\\B")
100
+ rxRule.setPatternOptions(QRegExUnicode)
101
+ return rxRule
102
+
103
+ @property
104
+ def altDialogStyle(self) -> QRegularExpression:
105
+ """Dialogue alternative rule based on user settings."""
106
+ symO = QRegularExpression.escape(CONFIG.altDialogOpen)
107
+ symC = QRegularExpression.escape(CONFIG.altDialogClose)
108
+ rxRule = QRegularExpression(f"\\B{symO}.*?{symC}\\B")
109
+ rxRule.setPatternOptions(QRegExUnicode)
110
+ return rxRule
111
+
112
+
113
+ REGEX_PATTERNS = RegExPatterns()
@@ -31,20 +31,20 @@ from zipfile import ZipFile
31
31
  from PyQt5.QtCore import pyqtSlot
32
32
  from PyQt5.QtGui import QCloseEvent, QTextCursor
33
33
  from PyQt5.QtWidgets import (
34
- QApplication, QDialog, QDialogButtonBox, QFileDialog, QFrame, QHBoxLayout,
35
- QLabel, QLineEdit, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget
34
+ QApplication, QDialogButtonBox, QFileDialog, QFrame, QHBoxLayout, QLabel,
35
+ QLineEdit, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget
36
36
  )
37
37
 
38
38
  from novelwriter import CONFIG, SHARED
39
- from novelwriter.common import formatFileFilter, formatInt, getFileSize, openExternalPath
39
+ from novelwriter.common import cssCol, formatFileFilter, formatInt, getFileSize, openExternalPath
40
40
  from novelwriter.error import formatException
41
- from novelwriter.extensions.modified import NIconToolButton
41
+ from novelwriter.extensions.modified import NIconToolButton, NNonBlockingDialog
42
42
  from novelwriter.types import QtDialogClose
43
43
 
44
44
  logger = logging.getLogger(__name__)
45
45
 
46
46
 
47
- class GuiDictionaries(QDialog):
47
+ class GuiDictionaries(NNonBlockingDialog):
48
48
 
49
49
  def __init__(self, parent: QWidget) -> None:
50
50
  super().__init__(parent=parent)
@@ -109,7 +109,7 @@ class GuiDictionaries(QDialog):
109
109
 
110
110
  # Buttons
111
111
  self.buttonBox = QDialogButtonBox(QtDialogClose, self)
112
- self.buttonBox.rejected.connect(self._doClose)
112
+ self.buttonBox.rejected.connect(self.reject)
113
113
 
114
114
  # Assemble
115
115
  self.innerBox = QVBoxLayout()
@@ -172,7 +172,7 @@ class GuiDictionaries(QDialog):
172
172
  def closeEvent(self, event: QCloseEvent) -> None:
173
173
  """Capture the user closing the window."""
174
174
  event.accept()
175
- self.deleteLater()
175
+ self.softDelete()
176
176
  return
177
177
 
178
178
  ##
@@ -186,7 +186,7 @@ class GuiDictionaries(QDialog):
186
186
  (self.tr("Free or Libre Office extension"), "*.sox *.oxt"), "*"
187
187
  ])
188
188
  soxFile, _ = QFileDialog.getOpenFileName(
189
- self, self.tr("Browse Files"), "", filter=ffilter
189
+ self, self.tr("Browse Files"), str(CONFIG.homePath()), filter=ffilter
190
190
  )
191
191
  if soxFile:
192
192
  path = Path(soxFile).absolute()
@@ -220,12 +220,6 @@ class GuiDictionaries(QDialog):
220
220
  SHARED.error("Path not found.")
221
221
  return
222
222
 
223
- @pyqtSlot()
224
- def _doClose(self) -> None:
225
- """Close the dialog."""
226
- self.close()
227
- return
228
-
229
223
  ##
230
224
  # Internal Functions
231
225
  ##
@@ -257,13 +251,9 @@ class GuiDictionaries(QDialog):
257
251
  cursor.movePosition(QTextCursor.MoveOperation.End)
258
252
  if cursor.position() > 0:
259
253
  cursor.insertText("\n")
260
- if err:
261
- cursor.insertHtml(f"<font color='red'>{text}</font>")
262
- else:
263
- cursor.insertText(text)
254
+ textCol = cssCol(SHARED.theme.errorText if err else self.palette().text().color())
255
+ cursor.insertHtml(f"<font style='color: {textCol}'>{text}</font>")
264
256
  cursor.movePosition(QTextCursor.MoveOperation.End)
265
257
  cursor.deleteChar()
266
258
  self.infoBox.setTextCursor(cursor)
267
259
  return
268
-
269
- # END Class GuiDictionaries
@@ -23,37 +23,35 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
23
23
  """
24
24
  from __future__ import annotations
25
25
 
26
- import random
27
26
  import logging
27
+ import random
28
28
 
29
- from PyQt5.QtCore import Qt, pyqtSlot
29
+ from PyQt5.QtCore import pyqtSlot
30
30
  from PyQt5.QtWidgets import (
31
- QDialog, QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, QSpinBox,
32
- QVBoxLayout, QWidget
31
+ QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, QSpinBox, QVBoxLayout,
32
+ QWidget
33
33
  )
34
34
 
35
35
  from novelwriter import CONFIG, SHARED
36
36
  from novelwriter.common import readTextFile
37
+ from novelwriter.extensions.modified import NDialog
37
38
  from novelwriter.extensions.switch import NSwitch
38
- from novelwriter.types import QtAlignLeft, QtAlignRight, QtRoleAction, QtDialogClose
39
+ from novelwriter.types import QtAlignLeft, QtAlignRight, QtDialogClose, QtRoleAction
39
40
 
40
41
  logger = logging.getLogger(__name__)
41
42
 
42
43
 
43
- class GuiLipsum(QDialog):
44
+ class GuiLipsum(NDialog):
44
45
 
45
46
  def __init__(self, parent: QWidget) -> None:
46
47
  super().__init__(parent=parent)
47
48
 
48
49
  logger.debug("Create: GuiLipsum")
49
50
  self.setObjectName("GuiLipsum")
50
- if CONFIG.osDarwin:
51
- self.setWindowFlag(Qt.WindowType.Tool)
51
+ self.setWindowTitle(self.tr("Insert Placeholder Text"))
52
52
 
53
53
  self._lipsumText = ""
54
54
 
55
- self.setWindowTitle(self.tr("Insert Placeholder Text"))
56
-
57
55
  vSp = CONFIG.pxInt(4)
58
56
  nPx = CONFIG.pxInt(64)
59
57
 
@@ -95,7 +93,7 @@ class GuiLipsum(QDialog):
95
93
 
96
94
  # Buttons
97
95
  self.buttonBox = QDialogButtonBox(self)
98
- self.buttonBox.rejected.connect(self.close)
96
+ self.buttonBox.rejected.connect(self.reject)
99
97
 
100
98
  self.btnClose = self.buttonBox.addButton(QtDialogClose)
101
99
  self.btnClose.setAutoDefault(False)
@@ -104,8 +102,6 @@ class GuiLipsum(QDialog):
104
102
  self.btnInsert.clicked.connect(self._doInsert)
105
103
  self.btnInsert.setAutoDefault(False)
106
104
 
107
- self.rejected.connect(self.close)
108
-
109
105
  # Assemble
110
106
  self.outerBox = QVBoxLayout()
111
107
  self.outerBox.addLayout(self.innerBox)
@@ -132,7 +128,7 @@ class GuiLipsum(QDialog):
132
128
  cls = GuiLipsum(parent)
133
129
  cls.exec()
134
130
  text = cls.lipsumText
135
- cls.deleteLater()
131
+ cls.softDelete()
136
132
  return text
137
133
 
138
134
  ##
@@ -150,5 +146,3 @@ class GuiLipsum(QDialog):
150
146
  self._lipsumText = "\n\n".join(lipsumText[0:pCount]) + "\n\n"
151
147
  self.close()
152
148
  return
153
-
154
- # END Class GuiLipsum
@@ -30,7 +30,7 @@ from pathlib import Path
30
30
  from PyQt5.QtCore import QTimer, pyqtSlot
31
31
  from PyQt5.QtGui import QCloseEvent
32
32
  from PyQt5.QtWidgets import (
33
- QAbstractButton, QAbstractItemView, QDialog, QDialogButtonBox, QFileDialog,
33
+ QAbstractButton, QAbstractItemView, QDialogButtonBox, QFileDialog,
34
34
  QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
35
35
  QPushButton, QSplitter, QVBoxLayout, QWidget
36
36
  )
@@ -42,14 +42,14 @@ from novelwriter.core.buildsettings import BuildSettings
42
42
  from novelwriter.core.docbuild import NWBuildDocument
43
43
  from novelwriter.core.item import NWItem
44
44
  from novelwriter.enum import nwBuildFmt
45
- from novelwriter.extensions.modified import NIconToolButton
46
- from novelwriter.extensions.simpleprogress import NProgressSimple
45
+ from novelwriter.extensions.modified import NDialog, NIconToolButton
46
+ from novelwriter.extensions.progressbars import NProgressSimple
47
47
  from novelwriter.types import QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole
48
48
 
49
49
  logger = logging.getLogger(__name__)
50
50
 
51
51
 
52
- class GuiManuscriptBuild(QDialog):
52
+ class GuiManuscriptBuild(NDialog):
53
53
  """GUI Tools: Manuscript Build Dialog
54
54
 
55
55
  This is the tool for running the build itself. It can be accessed
@@ -220,7 +220,7 @@ class GuiManuscriptBuild(QDialog):
220
220
 
221
221
  self.btnBuild.setFocus()
222
222
  self._populateContentList()
223
- self.buildPath.setText(str(self._build.lastPath))
223
+ self.buildPath.setText(str(self._build.lastBuildPath))
224
224
  if self._build.lastBuildName:
225
225
  self.buildName.setText(self._build.lastBuildName)
226
226
  else:
@@ -250,7 +250,7 @@ class GuiManuscriptBuild(QDialog):
250
250
  """
251
251
  self._saveSettings()
252
252
  event.accept()
253
- self.deleteLater()
253
+ self.softDelete()
254
254
  return
255
255
 
256
256
  ##
@@ -274,7 +274,7 @@ class GuiManuscriptBuild(QDialog):
274
274
  def _doSelectPath(self) -> None:
275
275
  """Select a folder for output."""
276
276
  bPath = Path(self.buildPath.text())
277
- bPath = bPath if bPath.is_dir() else self._build.lastPath
277
+ bPath = bPath if bPath.is_dir() else self._build.lastBuildPath
278
278
  savePath = QFileDialog.getExistingDirectory(
279
279
  self, self.tr("Select Folder"), str(bPath)
280
280
  )
@@ -327,7 +327,7 @@ class GuiManuscriptBuild(QDialog):
327
327
  return False
328
328
 
329
329
  # Make sure editor content is saved before we start
330
- SHARED.mainGui.saveDocument()
330
+ SHARED.saveEditor()
331
331
 
332
332
  docBuild = NWBuildDocument(SHARED.project, self._build)
333
333
  docBuild.queueAll()
@@ -336,7 +336,7 @@ class GuiManuscriptBuild(QDialog):
336
336
  for i, _ in docBuild.iterBuild(buildPath, bFormat):
337
337
  self.buildProgress.setValue(i+1)
338
338
 
339
- self._build.setLastPath(bPath)
339
+ self._build.setLastBuildPath(bPath)
340
340
  self._build.setLastBuildName(bName)
341
341
  self._build.setLastFormat(bFormat)
342
342
 
@@ -403,5 +403,3 @@ class GuiManuscriptBuild(QDialog):
403
403
  """Open the build folder in the system's file explorer."""
404
404
  openExternalPath(Path(self.buildPath.text()))
405
405
  return
406
-
407
- # END Class GuiManuscriptBuild