novelWriter 2.3.1__py3-none-any.whl → 2.4b1__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 (81) hide show
  1. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/RECORD +81 -70
  3. novelwriter/__init__.py +5 -5
  4. novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
  5. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
  6. novelwriter/assets/icons/typicons_dark/typ_arrow-down.svg +4 -0
  7. novelwriter/assets/icons/typicons_dark/typ_arrow-right.svg +4 -0
  8. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
  9. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
  10. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
  11. novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
  12. novelwriter/assets/icons/typicons_light/icons.conf +4 -0
  13. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
  14. novelwriter/assets/icons/typicons_light/typ_arrow-down.svg +4 -0
  15. novelwriter/assets/icons/typicons_light/typ_arrow-right.svg +4 -0
  16. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
  17. novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
  18. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
  19. novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
  20. novelwriter/assets/manual.pdf +0 -0
  21. novelwriter/assets/sample.zip +0 -0
  22. novelwriter/assets/syntax/default_dark.conf +1 -0
  23. novelwriter/assets/syntax/default_light.conf +1 -0
  24. novelwriter/assets/syntax/grey_dark.conf +1 -0
  25. novelwriter/assets/syntax/grey_light.conf +1 -0
  26. novelwriter/assets/syntax/light_owl.conf +1 -0
  27. novelwriter/assets/syntax/night_owl.conf +1 -0
  28. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  29. novelwriter/assets/syntax/solarized_light.conf +1 -0
  30. novelwriter/assets/syntax/tomorrow.conf +1 -0
  31. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  32. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  33. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  34. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  35. novelwriter/assets/text/credits_en.htm +25 -23
  36. novelwriter/common.py +1 -1
  37. novelwriter/config.py +35 -12
  38. novelwriter/constants.py +5 -6
  39. novelwriter/core/buildsettings.py +60 -40
  40. novelwriter/core/coretools.py +98 -13
  41. novelwriter/core/docbuild.py +74 -7
  42. novelwriter/core/document.py +24 -3
  43. novelwriter/core/index.py +31 -112
  44. novelwriter/core/project.py +10 -15
  45. novelwriter/core/sessions.py +2 -2
  46. novelwriter/core/status.py +4 -4
  47. novelwriter/core/storage.py +8 -2
  48. novelwriter/core/tohtml.py +22 -25
  49. novelwriter/core/tokenizer.py +416 -232
  50. novelwriter/core/tomd.py +17 -8
  51. novelwriter/core/toodt.py +65 -7
  52. novelwriter/core/tree.py +8 -8
  53. novelwriter/dialogs/docsplit.py +7 -8
  54. novelwriter/dialogs/preferences.py +3 -6
  55. novelwriter/enum.py +17 -14
  56. novelwriter/extensions/modified.py +20 -2
  57. novelwriter/extensions/versioninfo.py +1 -1
  58. novelwriter/gui/doceditor.py +257 -279
  59. novelwriter/gui/dochighlight.py +29 -25
  60. novelwriter/gui/docviewer.py +139 -148
  61. novelwriter/gui/docviewerpanel.py +4 -24
  62. novelwriter/gui/editordocument.py +12 -1
  63. novelwriter/gui/itemdetails.py +6 -6
  64. novelwriter/gui/mainmenu.py +37 -16
  65. novelwriter/gui/noveltree.py +11 -19
  66. novelwriter/gui/outline.py +43 -20
  67. novelwriter/gui/projtree.py +35 -43
  68. novelwriter/gui/search.py +316 -0
  69. novelwriter/gui/sidebar.py +25 -30
  70. novelwriter/gui/theme.py +59 -6
  71. novelwriter/guimain.py +176 -173
  72. novelwriter/shared.py +26 -1
  73. novelwriter/text/__init__.py +3 -0
  74. novelwriter/text/counting.py +137 -0
  75. novelwriter/tools/manuscript.py +344 -55
  76. novelwriter/tools/manussettings.py +213 -71
  77. novelwriter/tools/welcome.py +1 -1
  78. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/LICENSE.md +0 -0
  79. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/WHEEL +0 -0
  80. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/entry_points.txt +0 -0
  81. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/top_level.txt +0 -0
novelwriter/shared.py CHANGED
@@ -30,7 +30,7 @@ from time import time
30
30
  from typing import TYPE_CHECKING, TypeVar
31
31
  from pathlib import Path
32
32
 
33
- from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal
33
+ from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal
34
34
  from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget
35
35
  from novelwriter.common import formatFileFilter
36
36
 
@@ -57,10 +57,12 @@ class SharedData(QObject):
57
57
  projectStatusChanged = pyqtSignal(bool)
58
58
  projectStatusMessage = pyqtSignal(str)
59
59
  spellLanguageChanged = pyqtSignal(str, str)
60
+ focusModeChanged = pyqtSignal(bool)
60
61
  indexScannedText = pyqtSignal(str)
61
62
  indexChangedTags = pyqtSignal(list, list)
62
63
  indexCleared = pyqtSignal()
63
64
  indexAvailable = pyqtSignal()
65
+ mainClockTick = pyqtSignal()
64
66
 
65
67
  def __init__(self) -> None:
66
68
  super().__init__()
@@ -76,6 +78,11 @@ class SharedData(QObject):
76
78
  self._lastAlert = ""
77
79
  self._idleTime = 0.0
78
80
  self._idleRefTime = time()
81
+ self._focusMode = False
82
+
83
+ self._clock = QTimer(self)
84
+ self._clock.setInterval(1000)
85
+ self._clock.timeout.connect(lambda: self.mainClockTick.emit())
79
86
 
80
87
  return
81
88
 
@@ -111,6 +118,11 @@ class SharedData(QObject):
111
118
  raise Exception("SharedData class not fully initialised")
112
119
  return self._spelling
113
120
 
121
+ @property
122
+ def focusMode(self) -> bool:
123
+ """Return the Focus Mode state."""
124
+ return self._focusMode
125
+
114
126
  @property
115
127
  def hasProject(self) -> bool:
116
128
  """Return True if the project instance is populated."""
@@ -131,6 +143,17 @@ class SharedData(QObject):
131
143
  """Return the last alert message."""
132
144
  return self._lastAlert
133
145
 
146
+ ##
147
+ # Setters
148
+ ##
149
+
150
+ def setFocusMode(self, state: bool) -> None:
151
+ """Set focus mode on or off."""
152
+ if state is not self._focusMode:
153
+ self._focusMode = state
154
+ self.focusModeChanged.emit(state)
155
+ return
156
+
134
157
  ##
135
158
  # Methods
136
159
  ##
@@ -140,6 +163,7 @@ class SharedData(QObject):
140
163
  soon as the Main GUI is created to ensure the SHARED singleton
141
164
  has the properties needed for operation.
142
165
  """
166
+ self._clock.start()
143
167
  self._gui = gui
144
168
  self._theme = theme
145
169
  self._resetProject()
@@ -323,6 +347,7 @@ class SharedData(QObject):
323
347
  self._project = NWProject()
324
348
  self._spelling = NWSpellEnchant(self._project)
325
349
  self.updateSpellCheckLanguage()
350
+ self._focusMode = False
326
351
  return
327
352
 
328
353
  def _resetIdleTimer(self) -> None:
@@ -0,0 +1,3 @@
1
+ """
2
+ novelWriter – Text Analysis Tools
3
+ """
@@ -0,0 +1,137 @@
1
+ """
2
+ novelWriter – Text Counting Functions
3
+ =====================================
4
+
5
+ File History:
6
+ Created: 2019-04-22 [0.0.1] standardCounter
7
+ Rewritten: 2024-02-27 [2.4b1] preProcessText, standardCounter
8
+ Created: 2024-02-27 [2.4b1] bodyTextCounter
9
+
10
+ This file is a part of novelWriter
11
+ Copyright 2018–2024, Veronica Berglyd Olsen
12
+
13
+ This program is free software: you can redistribute it and/or modify
14
+ it under the terms of the GNU General Public License as published by
15
+ the Free Software Foundation, either version 3 of the License, or
16
+ (at your option) any later version.
17
+
18
+ This program is distributed in the hope that it will be useful, but
19
+ WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21
+ General Public License for more details.
22
+
23
+ You should have received a copy of the GNU General Public License
24
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import re
29
+
30
+ from novelwriter.constants import nwRegEx, nwUnicode
31
+
32
+ RX_SC = re.compile(nwRegEx.FMT_SC)
33
+ RX_LO = re.compile(r"(?i)(?<!\\)(\[(?:vspace|newpage|new page)(:\d+)?)(?<!\\)(\])")
34
+
35
+
36
+ def preProcessText(text: str, keepHeaders: bool = True) -> list[str]:
37
+ """Strip formatting codes from the text and split into lines."""
38
+ if not isinstance(text, str):
39
+ return []
40
+
41
+ # We need to treat dashes as word separators for counting words.
42
+ # The check+replace approach is much faster than direct replace for
43
+ # large texts, and a bit slower for small texts, but in the latter
44
+ # case it doesn't really matter.
45
+ if nwUnicode.U_ENDASH in text:
46
+ text = text.replace(nwUnicode.U_ENDASH, " ")
47
+ if nwUnicode.U_EMDASH in text:
48
+ text = text.replace(nwUnicode.U_EMDASH, " ")
49
+
50
+ ignore = "%@" if keepHeaders else "%@#"
51
+
52
+ result = []
53
+ for line in text.splitlines():
54
+ line = line.rstrip()
55
+ if line:
56
+ if line[0] in ignore:
57
+ continue
58
+ if line[0] == ">":
59
+ line = line.lstrip(">").lstrip(" ")
60
+ if line[-1] == "<":
61
+ line = line.rstrip("<").rstrip(" ")
62
+ if "[" in line:
63
+ # Strip shortcodes and special formatting
64
+ # RegEx is slow, so we do this only when necessary
65
+ line = RX_SC.sub("", line)
66
+ line = RX_LO.sub("", line)
67
+
68
+ result.append(line)
69
+
70
+ return result
71
+
72
+
73
+ def standardCounter(text: str) -> tuple[int, int, int]:
74
+ """A counter that counts paragraphs, words and characters.
75
+ This is the standard counter that includes headings in the word and
76
+ character counts.
77
+ """
78
+ cCount = 0
79
+ wCount = 0
80
+ pCount = 0
81
+ prevEmpty = True
82
+
83
+ for line in preProcessText(text):
84
+
85
+ countPara = True
86
+ if not line:
87
+ prevEmpty = True
88
+ continue
89
+
90
+ if line[0] == "#":
91
+ if line[:5] == "#### ":
92
+ line = line[5:]
93
+ countPara = False
94
+ elif line[:4] == "### ":
95
+ line = line[4:]
96
+ countPara = False
97
+ elif line[:3] == "## ":
98
+ line = line[3:]
99
+ countPara = False
100
+ elif line[:2] == "# ":
101
+ line = line[2:]
102
+ countPara = False
103
+ elif line[:3] == "#! ":
104
+ line = line[3:]
105
+ countPara = False
106
+ elif line[:4] == "##! ":
107
+ line = line[4:]
108
+ countPara = False
109
+ elif line[:5] == "###! ":
110
+ line = line[5:]
111
+ countPara = False
112
+
113
+ wCount += len(line.split())
114
+ cCount += len(line)
115
+ if countPara and prevEmpty:
116
+ pCount += 1
117
+
118
+ prevEmpty = not countPara
119
+
120
+ return cCount, wCount, pCount
121
+
122
+
123
+ def bodyTextCounter(text: str) -> tuple[int, int, int]:
124
+ """A counter that counts body text words, characters, and characters
125
+ without white spaces.
126
+ """
127
+ wCount = 0
128
+ cCount = 0
129
+ sCount = 0
130
+
131
+ for line in preProcessText(text, keepHeaders=False):
132
+ words = line.split()
133
+ wCount += len(words)
134
+ cCount += len(line)
135
+ sCount += len("".join(words))
136
+
137
+ return wCount, cCount, sCount