novelWriter 2.3rc1__py3-none-any.whl → 2.4__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.
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/METADATA +5 -6
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/RECORD +119 -109
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/WHEEL +1 -1
- novelWriter-2.4.dist-info/entry_points.txt +2 -0
- novelwriter/__init__.py +17 -10
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_nl_NL.json +11 -0
- novelwriter/assets/i18n/project_pt_BR.json +11 -0
- novelwriter/assets/icons/none.svg +4 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tango.conf +23 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +25 -23
- novelwriter/assets/themes/cyberpunk_night.conf +29 -0
- novelwriter/common.py +12 -4
- novelwriter/config.py +47 -16
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +64 -44
- novelwriter/core/coretools.py +97 -13
- novelwriter/core/docbuild.py +74 -7
- novelwriter/core/document.py +24 -3
- novelwriter/core/index.py +31 -112
- novelwriter/core/project.py +11 -15
- novelwriter/core/projectxml.py +3 -2
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/spellcheck.py +3 -3
- novelwriter/core/status.py +6 -5
- novelwriter/core/storage.py +16 -6
- novelwriter/core/tohtml.py +22 -25
- novelwriter/core/tokenizer.py +417 -237
- novelwriter/core/tomd.py +17 -8
- novelwriter/core/toodt.py +386 -351
- novelwriter/core/tree.py +8 -8
- novelwriter/dialogs/about.py +10 -12
- novelwriter/dialogs/docmerge.py +17 -14
- novelwriter/dialogs/docsplit.py +20 -19
- novelwriter/dialogs/editlabel.py +5 -4
- novelwriter/dialogs/preferences.py +32 -40
- novelwriter/dialogs/projectsettings.py +31 -28
- novelwriter/dialogs/quotes.py +10 -9
- novelwriter/dialogs/wordlist.py +18 -15
- novelwriter/enum.py +17 -14
- novelwriter/error.py +14 -12
- novelwriter/extensions/circularprogress.py +12 -8
- novelwriter/extensions/configlayout.py +23 -3
- novelwriter/extensions/modified.py +51 -2
- novelwriter/extensions/pagedsidebar.py +16 -14
- novelwriter/extensions/simpleprogress.py +3 -1
- novelwriter/extensions/statusled.py +3 -1
- novelwriter/extensions/switch.py +10 -9
- novelwriter/extensions/switchbox.py +14 -13
- novelwriter/extensions/versioninfo.py +1 -1
- novelwriter/gui/doceditor.py +433 -496
- novelwriter/gui/dochighlight.py +54 -33
- novelwriter/gui/docviewer.py +162 -175
- novelwriter/gui/docviewerpanel.py +20 -37
- novelwriter/gui/editordocument.py +15 -4
- novelwriter/gui/itemdetails.py +51 -54
- novelwriter/gui/mainmenu.py +37 -17
- novelwriter/gui/noveltree.py +31 -37
- novelwriter/gui/outline.py +120 -98
- novelwriter/gui/projtree.py +114 -112
- novelwriter/gui/search.py +362 -0
- novelwriter/gui/sidebar.py +36 -45
- novelwriter/gui/statusbar.py +14 -14
- novelwriter/gui/theme.py +116 -34
- novelwriter/guimain.py +216 -207
- novelwriter/shared.py +31 -6
- novelwriter/text/counting.py +138 -0
- novelwriter/tools/dictionaries.py +15 -14
- novelwriter/tools/lipsum.py +20 -17
- novelwriter/tools/manusbuild.py +43 -35
- novelwriter/tools/manuscript.py +381 -104
- novelwriter/tools/manussettings.py +263 -125
- novelwriter/tools/noveldetails.py +21 -19
- novelwriter/tools/welcome.py +59 -57
- novelwriter/tools/writingstats.py +61 -55
- novelwriter/types.py +90 -0
- novelWriter-2.3rc1.dist-info/entry_points.txt +0 -5
- novelwriter/core/__init__.py +0 -3
- novelwriter/dialogs/__init__.py +0 -3
- novelwriter/extensions/__init__.py +0 -3
- novelwriter/gui/__init__.py +0 -3
- novelwriter/tools/__init__.py +0 -3
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.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()
|
@@ -268,7 +292,7 @@ class SharedData(QObject):
|
|
268
292
|
self._lastAlert = alert.logMessage
|
269
293
|
if log:
|
270
294
|
logger.info(self._lastAlert, stacklevel=2)
|
271
|
-
alert.
|
295
|
+
alert.exec()
|
272
296
|
alert.deleteLater()
|
273
297
|
return
|
274
298
|
|
@@ -280,7 +304,7 @@ class SharedData(QObject):
|
|
280
304
|
self._lastAlert = alert.logMessage
|
281
305
|
if log:
|
282
306
|
logger.warning(self._lastAlert, stacklevel=2)
|
283
|
-
alert.
|
307
|
+
alert.exec()
|
284
308
|
alert.deleteLater()
|
285
309
|
return
|
286
310
|
|
@@ -295,7 +319,7 @@ class SharedData(QObject):
|
|
295
319
|
self._lastAlert = alert.logMessage
|
296
320
|
if log:
|
297
321
|
logger.error(self._lastAlert, stacklevel=2)
|
298
|
-
alert.
|
322
|
+
alert.exec()
|
299
323
|
alert.deleteLater()
|
300
324
|
return
|
301
325
|
|
@@ -305,7 +329,7 @@ class SharedData(QObject):
|
|
305
329
|
alert.setMessage(text, info, details)
|
306
330
|
alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
|
307
331
|
self._lastAlert = alert.logMessage
|
308
|
-
alert.
|
332
|
+
alert.exec()
|
309
333
|
isYes = alert.result() == QMessageBox.StandardButton.Yes
|
310
334
|
alert.deleteLater()
|
311
335
|
return isYes
|
@@ -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:
|
@@ -379,7 +404,7 @@ class _GuiAlert(QMessageBox):
|
|
379
404
|
self.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
380
405
|
else:
|
381
406
|
self.setStandardButtons(QMessageBox.Ok)
|
382
|
-
pSz = 2*self._theme.
|
407
|
+
pSz = 2*self._theme.baseIconHeight
|
383
408
|
if level == self.INFO:
|
384
409
|
self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz)))
|
385
410
|
self.setWindowTitle(self.tr("Information"))
|
@@ -0,0 +1,138 @@
|
|
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: # Above block can return empty line (Issue #1816)
|
61
|
+
if line[-1] == "<":
|
62
|
+
line = line.rstrip("<").rstrip(" ")
|
63
|
+
if "[" in line:
|
64
|
+
# Strip shortcodes and special formatting
|
65
|
+
# RegEx is slow, so we do this only when necessary
|
66
|
+
line = RX_SC.sub("", line)
|
67
|
+
line = RX_LO.sub("", line)
|
68
|
+
|
69
|
+
result.append(line)
|
70
|
+
|
71
|
+
return result
|
72
|
+
|
73
|
+
|
74
|
+
def standardCounter(text: str) -> tuple[int, int, int]:
|
75
|
+
"""A counter that counts paragraphs, words and characters.
|
76
|
+
This is the standard counter that includes headings in the word and
|
77
|
+
character counts.
|
78
|
+
"""
|
79
|
+
cCount = 0
|
80
|
+
wCount = 0
|
81
|
+
pCount = 0
|
82
|
+
prevEmpty = True
|
83
|
+
|
84
|
+
for line in preProcessText(text):
|
85
|
+
|
86
|
+
countPara = True
|
87
|
+
if not line:
|
88
|
+
prevEmpty = True
|
89
|
+
continue
|
90
|
+
|
91
|
+
if line[0] == "#":
|
92
|
+
if line[:5] == "#### ":
|
93
|
+
line = line[5:]
|
94
|
+
countPara = False
|
95
|
+
elif line[:4] == "### ":
|
96
|
+
line = line[4:]
|
97
|
+
countPara = False
|
98
|
+
elif line[:3] == "## ":
|
99
|
+
line = line[3:]
|
100
|
+
countPara = False
|
101
|
+
elif line[:2] == "# ":
|
102
|
+
line = line[2:]
|
103
|
+
countPara = False
|
104
|
+
elif line[:3] == "#! ":
|
105
|
+
line = line[3:]
|
106
|
+
countPara = False
|
107
|
+
elif line[:4] == "##! ":
|
108
|
+
line = line[4:]
|
109
|
+
countPara = False
|
110
|
+
elif line[:5] == "###! ":
|
111
|
+
line = line[5:]
|
112
|
+
countPara = False
|
113
|
+
|
114
|
+
wCount += len(line.split())
|
115
|
+
cCount += len(line)
|
116
|
+
if countPara and prevEmpty:
|
117
|
+
pCount += 1
|
118
|
+
|
119
|
+
prevEmpty = not countPara
|
120
|
+
|
121
|
+
return cCount, wCount, pCount
|
122
|
+
|
123
|
+
|
124
|
+
def bodyTextCounter(text: str) -> tuple[int, int, int]:
|
125
|
+
"""A counter that counts body text words, characters, and characters
|
126
|
+
without white spaces.
|
127
|
+
"""
|
128
|
+
wCount = 0
|
129
|
+
cCount = 0
|
130
|
+
sCount = 0
|
131
|
+
|
132
|
+
for line in preProcessText(text, keepHeaders=False):
|
133
|
+
words = line.split()
|
134
|
+
wCount += len(words)
|
135
|
+
cCount += len(line)
|
136
|
+
sCount += len("".join(words))
|
137
|
+
|
138
|
+
return wCount, cCount, sCount
|
@@ -28,16 +28,18 @@ import logging
|
|
28
28
|
from pathlib import Path
|
29
29
|
from zipfile import ZipFile
|
30
30
|
|
31
|
-
from PyQt5.QtGui import QCloseEvent, QTextCursor
|
32
31
|
from PyQt5.QtCore import pyqtSlot
|
32
|
+
from PyQt5.QtGui import QCloseEvent, QTextCursor
|
33
33
|
from PyQt5.QtWidgets import (
|
34
|
-
QDialog, QDialogButtonBox, QFileDialog, QFrame, QHBoxLayout,
|
35
|
-
QLineEdit, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget
|
34
|
+
QApplication, QDialog, QDialogButtonBox, QFileDialog, QFrame, QHBoxLayout,
|
35
|
+
QLabel, QLineEdit, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget
|
36
36
|
)
|
37
37
|
|
38
38
|
from novelwriter import CONFIG, SHARED
|
39
|
-
from novelwriter.error import formatException
|
40
39
|
from novelwriter.common import formatFileFilter, openExternalPath, formatInt, getFileSize
|
40
|
+
from novelwriter.error import formatException
|
41
|
+
from novelwriter.extensions.modified import NIconToolButton
|
42
|
+
from novelwriter.types import QtDialogClose
|
41
43
|
|
42
44
|
logger = logging.getLogger(__name__)
|
43
45
|
|
@@ -54,6 +56,7 @@ class GuiDictionaries(QDialog):
|
|
54
56
|
self._installPath = None
|
55
57
|
self._currDicts = set()
|
56
58
|
|
59
|
+
iSz = SHARED.theme.baseIconSize
|
57
60
|
iPx = CONFIG.pxInt(4)
|
58
61
|
mPx = CONFIG.pxInt(8)
|
59
62
|
sPx = CONFIG.pxInt(16)
|
@@ -68,12 +71,11 @@ class GuiDictionaries(QDialog):
|
|
68
71
|
self.tr("Download a dictionary from one of the links, and add it below."),
|
69
72
|
f" \u203a <a href='{foUrl}'>{foUrl}</a>",
|
70
73
|
f" \u203a <a href='{loUrl}'>{loUrl}</a>",
|
71
|
-
]))
|
74
|
+
]), self)
|
72
75
|
self.huInfo.setOpenExternalLinks(True)
|
73
76
|
self.huInfo.setWordWrap(True)
|
74
77
|
self.huInput = QLineEdit(self)
|
75
|
-
self.huBrowse =
|
76
|
-
self.huBrowse.setIcon(SHARED.theme.getIcon("browse"))
|
78
|
+
self.huBrowse = NIconToolButton(self, iSz, "browse")
|
77
79
|
self.huBrowse.clicked.connect(self._doBrowseHunspell)
|
78
80
|
self.huImport = QPushButton(self.tr("Add Dictionary"), self)
|
79
81
|
self.huImport.setIcon(SHARED.theme.getIcon("add"))
|
@@ -88,11 +90,10 @@ class GuiDictionaries(QDialog):
|
|
88
90
|
self.huAddBox.addWidget(self.huImport)
|
89
91
|
|
90
92
|
# Install Path
|
91
|
-
self.inInfo = QLabel(self.tr("Dictionary install location"))
|
93
|
+
self.inInfo = QLabel(self.tr("Dictionary install location"), self)
|
92
94
|
self.inPath = QLineEdit(self)
|
93
95
|
self.inPath.setReadOnly(True)
|
94
|
-
self.inBrowse =
|
95
|
-
self.inBrowse.setIcon(SHARED.theme.getIcon("browse"))
|
96
|
+
self.inBrowse = NIconToolButton(self, iSz, "browse")
|
96
97
|
self.inBrowse.clicked.connect(self._doOpenInstallLocation)
|
97
98
|
|
98
99
|
self.inBox = QHBoxLayout()
|
@@ -107,7 +108,7 @@ class GuiDictionaries(QDialog):
|
|
107
108
|
self.infoBox.setFrameStyle(QFrame.Shape.NoFrame)
|
108
109
|
|
109
110
|
# Buttons
|
110
|
-
self.buttonBox = QDialogButtonBox(
|
111
|
+
self.buttonBox = QDialogButtonBox(QtDialogClose, self)
|
111
112
|
self.buttonBox.rejected.connect(self._doClose)
|
112
113
|
|
113
114
|
# Assemble
|
@@ -158,7 +159,7 @@ class GuiDictionaries(QDialog):
|
|
158
159
|
"Additional dictionaries found: {0}"
|
159
160
|
).format(len(self._currDicts)))
|
160
161
|
|
161
|
-
|
162
|
+
QApplication.processEvents()
|
162
163
|
self.adjustSize()
|
163
164
|
|
164
165
|
return True
|
@@ -178,7 +179,7 @@ class GuiDictionaries(QDialog):
|
|
178
179
|
##
|
179
180
|
|
180
181
|
@pyqtSlot()
|
181
|
-
def _doBrowseHunspell(self):
|
182
|
+
def _doBrowseHunspell(self) -> None:
|
182
183
|
"""Browse for a Free/Libre Office dictionary."""
|
183
184
|
ffilter = formatFileFilter([
|
184
185
|
(self.tr("Free or Libre Office extension"), "*.sox *.oxt"), "*"
|
@@ -192,7 +193,7 @@ class GuiDictionaries(QDialog):
|
|
192
193
|
return
|
193
194
|
|
194
195
|
@pyqtSlot()
|
195
|
-
def _doImportHunspell(self):
|
196
|
+
def _doImportHunspell(self) -> None:
|
196
197
|
"""Import a hunspell dictionary from .sox or .oxt file."""
|
197
198
|
procErr = self.tr("Could not process dictionary file")
|
198
199
|
if self._installPath:
|
novelwriter/tools/lipsum.py
CHANGED
@@ -35,6 +35,7 @@ from PyQt5.QtWidgets import (
|
|
35
35
|
from novelwriter import CONFIG, SHARED
|
36
36
|
from novelwriter.common import readTextFile
|
37
37
|
from novelwriter.extensions.switch import NSwitch
|
38
|
+
from novelwriter.types import QtAlignLeft, QtAlignRight, QtRoleAction, QtDialogClose
|
38
39
|
|
39
40
|
logger = logging.getLogger(__name__)
|
40
41
|
|
@@ -53,13 +54,14 @@ class GuiLipsum(QDialog):
|
|
53
54
|
|
54
55
|
self.setWindowTitle(self.tr("Insert Placeholder Text"))
|
55
56
|
|
57
|
+
vSp = CONFIG.pxInt(4)
|
58
|
+
nPx = CONFIG.pxInt(64)
|
59
|
+
|
56
60
|
self.innerBox = QHBoxLayout()
|
57
61
|
self.innerBox.setSpacing(CONFIG.pxInt(16))
|
58
62
|
|
59
63
|
# Icon
|
60
|
-
|
61
|
-
vSp = CONFIG.pxInt(4)
|
62
|
-
self.docIcon = QLabel()
|
64
|
+
self.docIcon = QLabel(self)
|
63
65
|
self.docIcon.setPixmap(SHARED.theme.getPixmap("proj_document", (nPx, nPx)))
|
64
66
|
|
65
67
|
self.leftBox = QVBoxLayout()
|
@@ -69,35 +71,36 @@ class GuiLipsum(QDialog):
|
|
69
71
|
self.innerBox.addLayout(self.leftBox)
|
70
72
|
|
71
73
|
# Form
|
72
|
-
self.headLabel = QLabel(
|
74
|
+
self.headLabel = QLabel(self.tr("Insert Lorem Ipsum Text"))
|
75
|
+
self.headLabel.setFont(SHARED.theme.guiFontB)
|
73
76
|
|
74
|
-
self.paraLabel = QLabel(self.tr("Number of paragraphs"))
|
75
|
-
self.paraCount = QSpinBox()
|
77
|
+
self.paraLabel = QLabel(self.tr("Number of paragraphs"), self)
|
78
|
+
self.paraCount = QSpinBox(self)
|
76
79
|
self.paraCount.setMinimum(1)
|
77
80
|
self.paraCount.setMaximum(100)
|
78
81
|
self.paraCount.setValue(5)
|
79
82
|
|
80
|
-
self.randLabel = QLabel(self.tr("Randomise order"))
|
81
|
-
self.randSwitch = NSwitch()
|
83
|
+
self.randLabel = QLabel(self.tr("Randomise order"), self)
|
84
|
+
self.randSwitch = NSwitch(self)
|
82
85
|
|
83
86
|
self.formBox = QGridLayout()
|
84
|
-
self.formBox.addWidget(self.headLabel, 0, 0, 1, 2,
|
85
|
-
self.formBox.addWidget(self.paraLabel, 1, 0, 1, 1,
|
86
|
-
self.formBox.addWidget(self.paraCount, 1, 1, 1, 1,
|
87
|
-
self.formBox.addWidget(self.randLabel, 2, 0, 1, 1,
|
88
|
-
self.formBox.addWidget(self.randSwitch, 2, 1, 1, 1,
|
87
|
+
self.formBox.addWidget(self.headLabel, 0, 0, 1, 2, QtAlignLeft)
|
88
|
+
self.formBox.addWidget(self.paraLabel, 1, 0, 1, 1, QtAlignLeft)
|
89
|
+
self.formBox.addWidget(self.paraCount, 1, 1, 1, 1, QtAlignRight)
|
90
|
+
self.formBox.addWidget(self.randLabel, 2, 0, 1, 1, QtAlignLeft)
|
91
|
+
self.formBox.addWidget(self.randSwitch, 2, 1, 1, 1, QtAlignRight)
|
89
92
|
self.formBox.setVerticalSpacing(vSp)
|
90
93
|
self.formBox.setRowStretch(3, 1)
|
91
94
|
self.innerBox.addLayout(self.formBox)
|
92
95
|
|
93
96
|
# Buttons
|
94
|
-
self.buttonBox = QDialogButtonBox()
|
97
|
+
self.buttonBox = QDialogButtonBox(self)
|
95
98
|
self.buttonBox.rejected.connect(self.close)
|
96
99
|
|
97
|
-
self.btnClose = self.buttonBox.addButton(
|
100
|
+
self.btnClose = self.buttonBox.addButton(QtDialogClose)
|
98
101
|
self.btnClose.setAutoDefault(False)
|
99
102
|
|
100
|
-
self.btnInsert = self.buttonBox.addButton(self.tr("Insert"),
|
103
|
+
self.btnInsert = self.buttonBox.addButton(self.tr("Insert"), QtRoleAction)
|
101
104
|
self.btnInsert.clicked.connect(self._doInsert)
|
102
105
|
self.btnInsert.setAutoDefault(False)
|
103
106
|
|
@@ -127,7 +130,7 @@ class GuiLipsum(QDialog):
|
|
127
130
|
def getLipsum(cls, parent: QWidget) -> str:
|
128
131
|
"""Pop the dialog and return the lipsum text."""
|
129
132
|
cls = GuiLipsum(parent)
|
130
|
-
cls.
|
133
|
+
cls.exec()
|
131
134
|
text = cls.lipsumText
|
132
135
|
cls.deleteLater()
|
133
136
|
return text
|
novelwriter/tools/manusbuild.py
CHANGED
@@ -27,8 +27,8 @@ import logging
|
|
27
27
|
|
28
28
|
from pathlib import Path
|
29
29
|
|
30
|
+
from PyQt5.QtCore import QTimer, pyqtSlot
|
30
31
|
from PyQt5.QtGui import QCloseEvent
|
31
|
-
from PyQt5.QtCore import QSize, QTimer, Qt, pyqtSlot
|
32
32
|
from PyQt5.QtWidgets import (
|
33
33
|
QAbstractButton, QAbstractItemView, QDialog, QDialogButtonBox, QFileDialog,
|
34
34
|
QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
|
@@ -36,13 +36,17 @@ from PyQt5.QtWidgets import (
|
|
36
36
|
)
|
37
37
|
|
38
38
|
from novelwriter import CONFIG, SHARED
|
39
|
-
from novelwriter.enum import nwBuildFmt
|
40
39
|
from novelwriter.common import makeFileNameSafe, openExternalPath
|
41
40
|
from novelwriter.constants import nwLabels
|
42
|
-
from novelwriter.core.item import NWItem
|
43
|
-
from novelwriter.core.docbuild import NWBuildDocument
|
44
41
|
from novelwriter.core.buildsettings import BuildSettings
|
42
|
+
from novelwriter.core.docbuild import NWBuildDocument
|
43
|
+
from novelwriter.core.item import NWItem
|
44
|
+
from novelwriter.enum import nwBuildFmt
|
45
|
+
from novelwriter.extensions.modified import NIconToolButton
|
45
46
|
from novelwriter.extensions.simpleprogress import NProgressSimple
|
47
|
+
from novelwriter.types import (
|
48
|
+
QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole
|
49
|
+
)
|
46
50
|
|
47
51
|
logger = logging.getLogger(__name__)
|
48
52
|
|
@@ -54,9 +58,9 @@ class GuiManuscriptBuild(QDialog):
|
|
54
58
|
independently of the Manuscript Build Tool.
|
55
59
|
"""
|
56
60
|
|
57
|
-
D_KEY =
|
61
|
+
D_KEY = QtUserRole
|
58
62
|
|
59
|
-
def __init__(self, parent: QWidget, build: BuildSettings):
|
63
|
+
def __init__(self, parent: QWidget, build: BuildSettings) -> None:
|
60
64
|
super().__init__(parent=parent)
|
61
65
|
|
62
66
|
logger.debug("Create: GuiManuscriptBuild")
|
@@ -69,7 +73,8 @@ class GuiManuscriptBuild(QDialog):
|
|
69
73
|
self.setMinimumWidth(CONFIG.pxInt(500))
|
70
74
|
self.setMinimumHeight(CONFIG.pxInt(300))
|
71
75
|
|
72
|
-
|
76
|
+
iSz = SHARED.theme.baseIconSize
|
77
|
+
bSz = SHARED.theme.buttonIconSize
|
73
78
|
sp4 = CONFIG.pxInt(4)
|
74
79
|
sp8 = CONFIG.pxInt(8)
|
75
80
|
sp16 = CONFIG.pxInt(16)
|
@@ -85,9 +90,9 @@ class GuiManuscriptBuild(QDialog):
|
|
85
90
|
# Output Format
|
86
91
|
# =============
|
87
92
|
|
88
|
-
self.lblFormat = QLabel(self.tr("Output Format"))
|
89
|
-
self.listFormats = QListWidget()
|
90
|
-
self.listFormats.setIconSize(
|
93
|
+
self.lblFormat = QLabel(self.tr("Output Format"), self)
|
94
|
+
self.listFormats = QListWidget(self)
|
95
|
+
self.listFormats.setIconSize(iSz)
|
91
96
|
current = None
|
92
97
|
for key in nwBuildFmt:
|
93
98
|
item = QListWidgetItem()
|
@@ -104,17 +109,17 @@ class GuiManuscriptBuild(QDialog):
|
|
104
109
|
self.formatBox.addWidget(self.listFormats, 1)
|
105
110
|
self.formatBox.setContentsMargins(0, 0, 0, 0)
|
106
111
|
|
107
|
-
self.formatWidget = QWidget()
|
112
|
+
self.formatWidget = QWidget(self)
|
108
113
|
self.formatWidget.setLayout(self.formatBox)
|
109
114
|
self.formatWidget.setContentsMargins(0, 0, 0, 0)
|
110
115
|
|
111
116
|
# Table of Contents
|
112
117
|
# =================
|
113
118
|
|
114
|
-
self.lblContent = QLabel(self.tr("Table of Contents"))
|
119
|
+
self.lblContent = QLabel(self.tr("Table of Contents"), self)
|
115
120
|
|
116
121
|
self.listContent = QListWidget(self)
|
117
|
-
self.listContent.setIconSize(
|
122
|
+
self.listContent.setIconSize(iSz)
|
118
123
|
self.listContent.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
119
124
|
|
120
125
|
self.contentBox = QVBoxLayout()
|
@@ -122,7 +127,7 @@ class GuiManuscriptBuild(QDialog):
|
|
122
127
|
self.contentBox.addWidget(self.listContent, 0)
|
123
128
|
self.contentBox.setContentsMargins(0, 0, 0, 0)
|
124
129
|
|
125
|
-
self.contentWidget = QWidget()
|
130
|
+
self.contentWidget = QWidget(self)
|
126
131
|
self.contentWidget.setLayout(self.contentBox)
|
127
132
|
self.contentWidget.setContentsMargins(0, 0, 0, 0)
|
128
133
|
|
@@ -134,14 +139,14 @@ class GuiManuscriptBuild(QDialog):
|
|
134
139
|
font.setUnderline(True)
|
135
140
|
font.setPointSizeF(1.5*font.pointSizeF())
|
136
141
|
|
137
|
-
self.lblMain = QLabel(self._build.name)
|
142
|
+
self.lblMain = QLabel(self._build.name, self)
|
138
143
|
self.lblMain.setWordWrap(True)
|
139
144
|
self.lblMain.setFont(font)
|
140
145
|
|
141
146
|
# Build Path
|
142
|
-
self.lblPath = QLabel(self.tr("Path"))
|
147
|
+
self.lblPath = QLabel(self.tr("Path"), self)
|
143
148
|
self.buildPath = QLineEdit(self)
|
144
|
-
self.btnBrowse =
|
149
|
+
self.btnBrowse = NIconToolButton(self, iSz, "browse")
|
145
150
|
|
146
151
|
self.pathBox = QHBoxLayout()
|
147
152
|
self.pathBox.addWidget(self.buildPath)
|
@@ -149,9 +154,9 @@ class GuiManuscriptBuild(QDialog):
|
|
149
154
|
self.pathBox.setSpacing(sp8)
|
150
155
|
|
151
156
|
# Build Name
|
152
|
-
self.lblName = QLabel(self.tr("File Name"))
|
157
|
+
self.lblName = QLabel(self.tr("File Name"), self)
|
153
158
|
self.buildName = QLineEdit(self)
|
154
|
-
self.btnReset =
|
159
|
+
self.btnReset = NIconToolButton(self, iSz, "revert")
|
155
160
|
self.btnReset.setToolTip(self.tr("Reset file name to default"))
|
156
161
|
|
157
162
|
self.nameBox = QHBoxLayout()
|
@@ -176,16 +181,19 @@ class GuiManuscriptBuild(QDialog):
|
|
176
181
|
self.buildBox.setVerticalSpacing(sp4)
|
177
182
|
|
178
183
|
# Dialog Buttons
|
179
|
-
self.btnOpen = QPushButton(SHARED.theme.getIcon("browse"), self.tr("Open Folder"))
|
180
|
-
self.
|
181
|
-
self.
|
182
|
-
self.
|
183
|
-
|
184
|
+
self.btnOpen = QPushButton(SHARED.theme.getIcon("browse"), self.tr("Open Folder"), self)
|
185
|
+
self.btnOpen.setIconSize(bSz)
|
186
|
+
self.btnBuild = QPushButton(SHARED.theme.getIcon("export"), self.tr("&Build"), self)
|
187
|
+
self.btnBuild.setIconSize(bSz)
|
188
|
+
|
189
|
+
self.dlgButtons = QDialogButtonBox(QtDialogClose, self)
|
190
|
+
self.dlgButtons.addButton(self.btnOpen, QtRoleAction)
|
191
|
+
self.dlgButtons.addButton(self.btnBuild, QtRoleAction)
|
184
192
|
|
185
193
|
# Assemble GUI
|
186
194
|
# ============
|
187
195
|
|
188
|
-
self.mainSplit = QSplitter()
|
196
|
+
self.mainSplit = QSplitter(self)
|
189
197
|
self.mainSplit.addWidget(self.formatWidget)
|
190
198
|
self.mainSplit.addWidget(self.contentWidget)
|
191
199
|
self.mainSplit.setHandleWidth(sp16)
|
@@ -199,7 +207,7 @@ class GuiManuscriptBuild(QDialog):
|
|
199
207
|
])
|
200
208
|
|
201
209
|
self.outerBox = QVBoxLayout()
|
202
|
-
self.outerBox.addWidget(self.lblMain, 0,
|
210
|
+
self.outerBox.addWidget(self.lblMain, 0, QtAlignCenter)
|
203
211
|
self.outerBox.addSpacing(sp16)
|
204
212
|
self.outerBox.addWidget(self.mainSplit, 1)
|
205
213
|
self.outerBox.addSpacing(sp4)
|
@@ -252,20 +260,20 @@ class GuiManuscriptBuild(QDialog):
|
|
252
260
|
##
|
253
261
|
|
254
262
|
@pyqtSlot("QAbstractButton*")
|
255
|
-
def _dialogButtonClicked(self, button: QAbstractButton):
|
263
|
+
def _dialogButtonClicked(self, button: QAbstractButton) -> None:
|
256
264
|
"""Handle button clicks from the dialog button box."""
|
257
265
|
role = self.dlgButtons.buttonRole(button)
|
258
|
-
if role ==
|
266
|
+
if role == QtRoleAction:
|
259
267
|
if button == self.btnBuild:
|
260
268
|
self._runBuild()
|
261
269
|
elif button == self.btnOpen:
|
262
270
|
self._openOutputFolder()
|
263
|
-
elif role ==
|
271
|
+
elif role == QtRoleReject:
|
264
272
|
self.close()
|
265
273
|
return
|
266
274
|
|
267
275
|
@pyqtSlot()
|
268
|
-
def _doSelectPath(self):
|
276
|
+
def _doSelectPath(self) -> None:
|
269
277
|
"""Select a folder for output."""
|
270
278
|
bPath = Path(self.buildPath.text())
|
271
279
|
bPath = bPath if bPath.is_dir() else self._build.lastPath
|
@@ -277,7 +285,7 @@ class GuiManuscriptBuild(QDialog):
|
|
277
285
|
return
|
278
286
|
|
279
287
|
@pyqtSlot()
|
280
|
-
def _doResetBuildName(self):
|
288
|
+
def _doResetBuildName(self) -> None:
|
281
289
|
"""Generate a default build name."""
|
282
290
|
bName = f"{SHARED.project.data.name} - {self._build.name}"
|
283
291
|
self.buildName.setText(bName)
|
@@ -285,7 +293,7 @@ class GuiManuscriptBuild(QDialog):
|
|
285
293
|
return
|
286
294
|
|
287
295
|
@pyqtSlot()
|
288
|
-
def _resetProgress(self):
|
296
|
+
def _resetProgress(self) -> None:
|
289
297
|
"""Set the progress bar back to 0."""
|
290
298
|
self.buildProgress.setValue(0)
|
291
299
|
return
|
@@ -342,7 +350,7 @@ class GuiManuscriptBuild(QDialog):
|
|
342
350
|
return items[0].data(self.D_KEY)
|
343
351
|
return None
|
344
352
|
|
345
|
-
def _saveSettings(self):
|
353
|
+
def _saveSettings(self) -> None:
|
346
354
|
"""Save the user GUI settings."""
|
347
355
|
winWidth = CONFIG.rpxInt(self.width())
|
348
356
|
winHeight = CONFIG.rpxInt(self.height())
|
@@ -361,7 +369,7 @@ class GuiManuscriptBuild(QDialog):
|
|
361
369
|
|
362
370
|
return
|
363
371
|
|
364
|
-
def _populateContentList(self):
|
372
|
+
def _populateContentList(self) -> None:
|
365
373
|
"""Build the content list."""
|
366
374
|
rootMap = {}
|
367
375
|
filtered = self._build.buildItemFilter(SHARED.project)
|
@@ -390,7 +398,7 @@ class GuiManuscriptBuild(QDialog):
|
|
390
398
|
|
391
399
|
return
|
392
400
|
|
393
|
-
def _openOutputFolder(self):
|
401
|
+
def _openOutputFolder(self) -> None:
|
394
402
|
"""Open the build folder in the system's file explorer."""
|
395
403
|
openExternalPath(Path(self.buildPath.text()))
|
396
404
|
return
|