novelWriter 2.1.1__py3-none-any.whl → 2.2rc1__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.1.1.dist-info → novelWriter-2.2rc1.dist-info}/METADATA +3 -3
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/RECORD +105 -76
- novelwriter/__init__.py +6 -24
- novelwriter/assets/i18n/project_de_DE.json +10 -0
- novelwriter/assets/i18n/project_en_GB.json +11 -0
- novelwriter/assets/i18n/project_en_US.json +10 -0
- novelwriter/assets/i18n/project_ja_JP.json +11 -1
- novelwriter/assets/i18n/project_nb_NO.json +10 -0
- novelwriter/assets/i18n/project_nn_NO.json +10 -0
- novelwriter/assets/icons/novelwriter.ico +0 -0
- novelwriter/assets/icons/novelwriter.svg +8 -183
- novelwriter/assets/icons/typicons_dark/icons.conf +17 -2
- novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_panel.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +8 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +8 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +5 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +5 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +5 -0
- novelwriter/assets/icons/typicons_dark/typ_eye.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +17 -2
- novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-note.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_panel.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +8 -0
- novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +8 -0
- novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +5 -0
- novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +5 -0
- novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +5 -0
- novelwriter/assets/icons/typicons_light/typ_eye.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +4 -0
- novelwriter/assets/icons/x-novelwriter-project.ico +0 -0
- novelwriter/assets/icons/x-novelwriter-project.svg +7 -206
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -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/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 +7 -0
- novelwriter/assets/text/release_notes.htm +7 -37
- novelwriter/common.py +22 -1
- novelwriter/config.py +27 -42
- novelwriter/constants.py +45 -7
- novelwriter/core/buildsettings.py +40 -24
- novelwriter/core/coretools.py +8 -1
- novelwriter/core/docbuild.py +2 -6
- novelwriter/core/index.py +264 -175
- novelwriter/core/options.py +8 -3
- novelwriter/core/project.py +2 -2
- novelwriter/core/projectdata.py +3 -3
- novelwriter/core/tohtml.py +60 -59
- novelwriter/core/tokenizer.py +110 -70
- novelwriter/core/tomd.py +51 -38
- novelwriter/core/toodt.py +184 -147
- novelwriter/dialogs/preferences.py +75 -106
- novelwriter/dialogs/projsettings.py +101 -110
- novelwriter/dialogs/updates.py +25 -14
- novelwriter/enum.py +28 -3
- novelwriter/extensions/novelselector.py +1 -1
- novelwriter/gui/doceditor.py +1345 -1235
- novelwriter/gui/dochighlight.py +98 -62
- novelwriter/gui/docviewer.py +151 -340
- novelwriter/gui/docviewerpanel.py +457 -0
- novelwriter/gui/editordocument.py +126 -0
- novelwriter/gui/mainmenu.py +350 -300
- novelwriter/gui/noveltree.py +101 -125
- novelwriter/gui/outline.py +154 -171
- novelwriter/gui/projtree.py +480 -380
- novelwriter/gui/sidebar.py +106 -75
- novelwriter/gui/statusbar.py +1 -1
- novelwriter/gui/theme.py +114 -75
- novelwriter/guimain.py +353 -254
- novelwriter/shared.py +36 -3
- novelwriter/tools/dictionaries.py +268 -0
- novelwriter/tools/manusbuild.py +17 -6
- novelwriter/tools/manuscript.py +11 -3
- novelwriter/tools/manussettings.py +0 -14
- novelwriter/tools/projwizard.py +16 -2
- novelwriter/tools/writingstats.py +1 -1
- novelwriter/assets/icons/typicons_dark/typ_at.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_th-menu.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_at.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_th-menu.svg +0 -4
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/WHEEL +0 -0
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/top_level.txt +0 -0
novelwriter/shared.py
CHANGED
@@ -29,7 +29,7 @@ from time import time
|
|
29
29
|
from typing import TYPE_CHECKING
|
30
30
|
from pathlib import Path
|
31
31
|
|
32
|
-
from PyQt5.QtCore import QObject, pyqtSignal
|
32
|
+
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal
|
33
33
|
from PyQt5.QtWidgets import QMessageBox, QWidget
|
34
34
|
|
35
35
|
from novelwriter.core.spellcheck import NWSpellEnchant
|
@@ -52,17 +52,26 @@ class SharedData(QObject):
|
|
52
52
|
projectStatusChanged = pyqtSignal(bool)
|
53
53
|
projectStatusMessage = pyqtSignal(str)
|
54
54
|
spellLanguageChanged = pyqtSignal(str, str)
|
55
|
+
indexScannedText = pyqtSignal(str)
|
56
|
+
indexChangedTags = pyqtSignal(list, list)
|
57
|
+
indexCleared = pyqtSignal()
|
58
|
+
indexAvailable = pyqtSignal()
|
55
59
|
|
56
60
|
def __init__(self) -> None:
|
57
61
|
super().__init__()
|
62
|
+
|
63
|
+
# Objects
|
58
64
|
self._gui = None
|
59
65
|
self._theme = None
|
60
66
|
self._project = None
|
61
67
|
self._spelling = None
|
68
|
+
|
69
|
+
# Settings
|
62
70
|
self._lockedBy = None
|
63
71
|
self._alert = None
|
64
72
|
self._idleTime = 0.0
|
65
73
|
self._idleRefTime = time()
|
74
|
+
|
66
75
|
return
|
67
76
|
|
68
77
|
##
|
@@ -129,7 +138,8 @@ class SharedData(QObject):
|
|
129
138
|
self._gui = gui
|
130
139
|
self._theme = theme
|
131
140
|
self._resetProject()
|
132
|
-
logger.debug("SharedData
|
141
|
+
logger.debug("Ready: SharedData")
|
142
|
+
logger.debug("Thread Pool Max Count: %d", QThreadPool.globalInstance().maxThreadCount())
|
133
143
|
return
|
134
144
|
|
135
145
|
def openProject(self, path: str | Path, clearLock: bool = False) -> bool:
|
@@ -165,7 +175,7 @@ class SharedData(QObject):
|
|
165
175
|
return
|
166
176
|
|
167
177
|
def updateSpellCheckLanguage(self, reload: bool = False) -> None:
|
168
|
-
"""Update the active spell check
|
178
|
+
"""Update the active spell check language from settings."""
|
169
179
|
from novelwriter import CONFIG
|
170
180
|
language = self.project.data.spellLang or CONFIG.spellLanguage
|
171
181
|
if language != self.spelling.spellLanguage or reload:
|
@@ -199,6 +209,29 @@ class SharedData(QObject):
|
|
199
209
|
self.projectStatusChanged.emit(state)
|
200
210
|
return
|
201
211
|
|
212
|
+
def runInThreadPool(self, runnable: QRunnable, priority: int = 0) -> None:
|
213
|
+
"""Queue a runnable in the application thread pool."""
|
214
|
+
QThreadPool.globalInstance().start(runnable, priority=priority)
|
215
|
+
return
|
216
|
+
|
217
|
+
##
|
218
|
+
# Signal Proxy
|
219
|
+
##
|
220
|
+
|
221
|
+
def indexSignalProxy(self, data: dict) -> None:
|
222
|
+
"""Emit signals on behalf of the index."""
|
223
|
+
event = data.get("event")
|
224
|
+
logger.debug("Received '%s' event from the index", event)
|
225
|
+
if event == "updateTags":
|
226
|
+
self.indexChangedTags.emit(data.get("updated", []), data.get("deleted", []))
|
227
|
+
elif event == "scanText":
|
228
|
+
self.indexScannedText.emit(data.get("handle", ""))
|
229
|
+
elif event == "clearIndex":
|
230
|
+
self.indexCleared.emit()
|
231
|
+
elif event == "buildIndex":
|
232
|
+
self.indexAvailable.emit()
|
233
|
+
return
|
234
|
+
|
202
235
|
##
|
203
236
|
# Alert Boxes
|
204
237
|
##
|
@@ -0,0 +1,268 @@
|
|
1
|
+
"""
|
2
|
+
novelWriter – GUI Dictionary Downloader
|
3
|
+
=======================================
|
4
|
+
|
5
|
+
File History:
|
6
|
+
Created: 2023-11-19 [2.2rc1]
|
7
|
+
|
8
|
+
This file is a part of novelWriter
|
9
|
+
Copyright 2018–2023, 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
|
+
import logging
|
27
|
+
|
28
|
+
from pathlib import Path
|
29
|
+
from zipfile import ZipFile
|
30
|
+
|
31
|
+
from PyQt5.QtGui import QCloseEvent, QTextCursor
|
32
|
+
from PyQt5.QtCore import pyqtSlot
|
33
|
+
from PyQt5.QtWidgets import (
|
34
|
+
QDialog, QDialogButtonBox, QFileDialog, QFrame, QHBoxLayout, QLabel,
|
35
|
+
QLineEdit, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget, qApp
|
36
|
+
)
|
37
|
+
|
38
|
+
from novelwriter import CONFIG, SHARED
|
39
|
+
from novelwriter.error import formatException
|
40
|
+
from novelwriter.common import openExternalPath, formatInt, getFileSize
|
41
|
+
|
42
|
+
logger = logging.getLogger(__name__)
|
43
|
+
|
44
|
+
|
45
|
+
class GuiDictionaries(QDialog):
|
46
|
+
|
47
|
+
def __init__(self, parent: QWidget) -> None:
|
48
|
+
super().__init__(parent=parent)
|
49
|
+
|
50
|
+
logger.debug("Create: GuiDictionaries")
|
51
|
+
self.setObjectName("GuiDictionaries")
|
52
|
+
self.setWindowTitle(self.tr("Add Dictionaries"))
|
53
|
+
|
54
|
+
self._installPath = None
|
55
|
+
self._currDicts = set()
|
56
|
+
|
57
|
+
iPx = CONFIG.pxInt(4)
|
58
|
+
mPx = CONFIG.pxInt(8)
|
59
|
+
sPx = CONFIG.pxInt(16)
|
60
|
+
|
61
|
+
self.setMinimumWidth(CONFIG.pxInt(500))
|
62
|
+
self.setMinimumHeight(CONFIG.pxInt(300))
|
63
|
+
|
64
|
+
# Hunspell Dictionaries
|
65
|
+
foUrl = "https://www.freeoffice.com/en/download/dictionaries"
|
66
|
+
loUrl = "https://extensions.libreoffice.org"
|
67
|
+
self.huInfo = QLabel("<br>".join([
|
68
|
+
self.tr("Download a dictionary from one of the links, and add it below."),
|
69
|
+
f" \u203a <a href='{foUrl}'>{foUrl}</a>",
|
70
|
+
f" \u203a <a href='{loUrl}'>{loUrl}</a>",
|
71
|
+
]))
|
72
|
+
self.huInfo.setOpenExternalLinks(True)
|
73
|
+
self.huInfo.setWordWrap(True)
|
74
|
+
self.huInput = QLineEdit(self)
|
75
|
+
self.huBrowse = QPushButton(self)
|
76
|
+
self.huBrowse.setIcon(SHARED.theme.getIcon("browse"))
|
77
|
+
self.huBrowse.clicked.connect(self._doBrowseHunspell)
|
78
|
+
self.huImport = QPushButton(self.tr("Add Dictionary"), self)
|
79
|
+
self.huImport.setIcon(SHARED.theme.getIcon("add"))
|
80
|
+
self.huImport.clicked.connect(self._doImportHunspell)
|
81
|
+
|
82
|
+
self.huPathBox = QHBoxLayout()
|
83
|
+
self.huPathBox.addWidget(self.huInput)
|
84
|
+
self.huPathBox.addWidget(self.huBrowse)
|
85
|
+
self.huPathBox.setSpacing(iPx)
|
86
|
+
self.huAddBox = QHBoxLayout()
|
87
|
+
self.huAddBox.addStretch(1)
|
88
|
+
self.huAddBox.addWidget(self.huImport)
|
89
|
+
|
90
|
+
# Install Path
|
91
|
+
self.inInfo = QLabel(self.tr("Dictionary install location"))
|
92
|
+
self.inPath = QLineEdit(self)
|
93
|
+
self.inPath.setReadOnly(True)
|
94
|
+
self.inBrowse = QPushButton(self)
|
95
|
+
self.inBrowse.setIcon(SHARED.theme.getIcon("browse"))
|
96
|
+
self.inBrowse.clicked.connect(self._doOpenInstallLocation)
|
97
|
+
|
98
|
+
self.inBox = QHBoxLayout()
|
99
|
+
self.inBox.addWidget(self.inPath)
|
100
|
+
self.inBox.addWidget(self.inBrowse)
|
101
|
+
self.inBox.setSpacing(iPx)
|
102
|
+
|
103
|
+
# Info Box
|
104
|
+
self.infoBox = QPlainTextEdit(self)
|
105
|
+
self.infoBox.setReadOnly(True)
|
106
|
+
self.infoBox.setFixedHeight(4*SHARED.theme.fontPixelSize)
|
107
|
+
self.infoBox.setFrameStyle(QFrame.Shape.NoFrame)
|
108
|
+
|
109
|
+
# Buttons
|
110
|
+
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
|
111
|
+
self.buttonBox.rejected.connect(self._doClose)
|
112
|
+
|
113
|
+
# Assemble
|
114
|
+
self.innerBox = QVBoxLayout()
|
115
|
+
self.innerBox.addWidget(self.huInfo)
|
116
|
+
self.innerBox.addLayout(self.huPathBox)
|
117
|
+
self.innerBox.addLayout(self.huAddBox)
|
118
|
+
self.innerBox.addSpacing(mPx)
|
119
|
+
self.innerBox.addWidget(self.inInfo)
|
120
|
+
self.innerBox.addLayout(self.inBox)
|
121
|
+
self.innerBox.addWidget(self.infoBox)
|
122
|
+
self.innerBox.setSpacing(iPx)
|
123
|
+
|
124
|
+
self.outerBox = QVBoxLayout()
|
125
|
+
self.outerBox.addLayout(self.innerBox, 0)
|
126
|
+
self.outerBox.addStretch(1)
|
127
|
+
self.outerBox.addWidget(self.buttonBox, 0)
|
128
|
+
self.outerBox.setSpacing(sPx)
|
129
|
+
|
130
|
+
self.setLayout(self.outerBox)
|
131
|
+
|
132
|
+
logger.debug("Ready: GuiDictionaries")
|
133
|
+
|
134
|
+
return
|
135
|
+
|
136
|
+
def __del__(self) -> None: # pragma: no cover
|
137
|
+
logger.debug("Delete: GuiDictionaries")
|
138
|
+
return
|
139
|
+
|
140
|
+
def initDialog(self) -> bool:
|
141
|
+
"""Prepare and check that we can proceed."""
|
142
|
+
try:
|
143
|
+
import enchant
|
144
|
+
path = Path(enchant.get_user_config_dir())
|
145
|
+
except Exception:
|
146
|
+
logger.error("Could not get enchant path")
|
147
|
+
return False
|
148
|
+
|
149
|
+
self._installPath = Path(path).resolve()
|
150
|
+
if path.is_dir():
|
151
|
+
self.inPath.setText(str(path))
|
152
|
+
hunspell = path / "hunspell"
|
153
|
+
if hunspell.is_dir():
|
154
|
+
self._currDicts = set(
|
155
|
+
i.stem for i in hunspell.iterdir() if i.is_file() and i.suffix == ".aff"
|
156
|
+
)
|
157
|
+
self._appendLog(self.tr(
|
158
|
+
"Additional dictionaries found: {0}"
|
159
|
+
).format(len(self._currDicts)))
|
160
|
+
|
161
|
+
qApp.processEvents()
|
162
|
+
self.adjustSize()
|
163
|
+
|
164
|
+
return True
|
165
|
+
|
166
|
+
##
|
167
|
+
# Events
|
168
|
+
##
|
169
|
+
|
170
|
+
def closeEvent(self, event: QCloseEvent) -> None:
|
171
|
+
"""Capture the user closing the window."""
|
172
|
+
event.accept()
|
173
|
+
self.deleteLater()
|
174
|
+
return
|
175
|
+
|
176
|
+
##
|
177
|
+
# Private Slots
|
178
|
+
##
|
179
|
+
|
180
|
+
@pyqtSlot()
|
181
|
+
def _doBrowseHunspell(self):
|
182
|
+
"""Browse for a Free/Libre Office dictionary."""
|
183
|
+
extFilter = [
|
184
|
+
self.tr("Free or Libre Office extension ({0})").format("*.sox *.oxt"),
|
185
|
+
self.tr("All files ({0})").format("*"),
|
186
|
+
]
|
187
|
+
soxFile, _ = QFileDialog.getOpenFileName(
|
188
|
+
self, self.tr("Browse Files"), "", filter=";;".join(extFilter)
|
189
|
+
)
|
190
|
+
if soxFile:
|
191
|
+
path = Path(soxFile).absolute()
|
192
|
+
self.huInput.setText(str(path))
|
193
|
+
return
|
194
|
+
|
195
|
+
@pyqtSlot()
|
196
|
+
def _doImportHunspell(self):
|
197
|
+
"""Import a hunspell dictionary from .sox or .oxt file."""
|
198
|
+
procErr = self.tr("Could not process dictionary file")
|
199
|
+
if self._installPath:
|
200
|
+
temp = self.huInput.text()
|
201
|
+
if temp and (path := Path(temp)).is_file():
|
202
|
+
hunspell = self._installPath / "hunspell"
|
203
|
+
hunspell.mkdir(exist_ok=True)
|
204
|
+
try:
|
205
|
+
nAff, nDic = self._extractDicts(path, hunspell)
|
206
|
+
if nAff == 0 or nDic == 0:
|
207
|
+
self._appendLog(procErr, err=True)
|
208
|
+
except Exception as exc:
|
209
|
+
self._appendLog(procErr, err=True)
|
210
|
+
self._appendLog(formatException(exc), err=True)
|
211
|
+
else:
|
212
|
+
self._appendLog(procErr, err=True)
|
213
|
+
return
|
214
|
+
|
215
|
+
@pyqtSlot()
|
216
|
+
def _doOpenInstallLocation(self) -> None:
|
217
|
+
"""Open the dictionary folder."""
|
218
|
+
if not openExternalPath(Path(self.inPath.text())):
|
219
|
+
SHARED.error("Path not found.")
|
220
|
+
return
|
221
|
+
|
222
|
+
@pyqtSlot()
|
223
|
+
def _doClose(self) -> None:
|
224
|
+
"""Close the dialog."""
|
225
|
+
self.close()
|
226
|
+
return
|
227
|
+
|
228
|
+
##
|
229
|
+
# Internal Functions
|
230
|
+
##
|
231
|
+
|
232
|
+
def _extractDicts(self, path: Path, output: Path) -> tuple[int, int]:
|
233
|
+
"""Extract a zip archive and return the number of .aff and .dic
|
234
|
+
files found in it.
|
235
|
+
"""
|
236
|
+
nAff = nDic = 0
|
237
|
+
with ZipFile(path, mode="r") as zipObj:
|
238
|
+
for item in zipObj.namelist():
|
239
|
+
zPath = Path(item)
|
240
|
+
if zPath.suffix not in (".aff", ".dic"):
|
241
|
+
continue
|
242
|
+
nAff += 1 if zPath.suffix == ".aff" else 0
|
243
|
+
nDic += 1 if zPath.suffix == ".dic" else 0
|
244
|
+
with zipObj.open(item) as zF:
|
245
|
+
oPath = output / zPath.name
|
246
|
+
oPath.write_bytes(zF.read())
|
247
|
+
size = getFileSize(oPath)
|
248
|
+
self._appendLog(self.tr(
|
249
|
+
"Added: {0} [{1}B]"
|
250
|
+
).format(zPath.name, formatInt(size)))
|
251
|
+
return nAff, nDic
|
252
|
+
|
253
|
+
def _appendLog(self, text: str, err: bool = False) -> None:
|
254
|
+
"""Append a line to the log output."""
|
255
|
+
cursor = self.infoBox.textCursor()
|
256
|
+
cursor.movePosition(QTextCursor.MoveOperation.End)
|
257
|
+
if cursor.position() > 0:
|
258
|
+
cursor.insertText("\n")
|
259
|
+
if err:
|
260
|
+
cursor.insertHtml(f"<font color='red'>{text}</font>")
|
261
|
+
else:
|
262
|
+
cursor.insertText(text)
|
263
|
+
cursor.movePosition(QTextCursor.MoveOperation.End)
|
264
|
+
cursor.deleteChar()
|
265
|
+
self.infoBox.setTextCursor(cursor)
|
266
|
+
return
|
267
|
+
|
268
|
+
# END Class GuiDictionaries
|
novelwriter/tools/manusbuild.py
CHANGED
@@ -27,6 +27,7 @@ import logging
|
|
27
27
|
|
28
28
|
from pathlib import Path
|
29
29
|
|
30
|
+
from PyQt5.QtGui import QCloseEvent
|
30
31
|
from PyQt5.QtCore import QSize, QTimer, Qt, pyqtSlot
|
31
32
|
from PyQt5.QtWidgets import (
|
32
33
|
QAbstractButton, QAbstractItemView, QDialog, QDialogButtonBox, QFileDialog,
|
@@ -36,7 +37,7 @@ from PyQt5.QtWidgets import (
|
|
36
37
|
|
37
38
|
from novelwriter import CONFIG, SHARED
|
38
39
|
from novelwriter.enum import nwBuildFmt
|
39
|
-
from novelwriter.common import makeFileNameSafe
|
40
|
+
from novelwriter.common import makeFileNameSafe, openExternalPath
|
40
41
|
from novelwriter.constants import nwLabels
|
41
42
|
from novelwriter.core.item import NWItem
|
42
43
|
from novelwriter.core.docbuild import NWBuildDocument
|
@@ -175,9 +176,11 @@ class GuiManuscriptBuild(QDialog):
|
|
175
176
|
self.buildBox.setVerticalSpacing(sp4)
|
176
177
|
|
177
178
|
# Dialog Buttons
|
179
|
+
self.btnOpen = QPushButton(SHARED.theme.getIcon("browse"), self.tr("Open Folder"))
|
178
180
|
self.btnBuild = QPushButton(SHARED.theme.getIcon("export"), self.tr("&Build"))
|
179
|
-
self.dlgButtons = QDialogButtonBox(QDialogButtonBox.Close)
|
180
|
-
self.dlgButtons.addButton(self.
|
181
|
+
self.dlgButtons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
|
182
|
+
self.dlgButtons.addButton(self.btnOpen, QDialogButtonBox.ButtonRole.ActionRole)
|
183
|
+
self.dlgButtons.addButton(self.btnBuild, QDialogButtonBox.ButtonRole.ActionRole)
|
181
184
|
|
182
185
|
# Assemble GUI
|
183
186
|
# ============
|
@@ -227,7 +230,7 @@ class GuiManuscriptBuild(QDialog):
|
|
227
230
|
|
228
231
|
return
|
229
232
|
|
230
|
-
def __del__(self): # pragma: no cover
|
233
|
+
def __del__(self) -> None: # pragma: no cover
|
231
234
|
logger.debug("Delete: GuiManuscriptBuild")
|
232
235
|
return
|
233
236
|
|
@@ -235,7 +238,7 @@ class GuiManuscriptBuild(QDialog):
|
|
235
238
|
# Events
|
236
239
|
##
|
237
240
|
|
238
|
-
def closeEvent(self, event):
|
241
|
+
def closeEvent(self, event: QCloseEvent) -> None:
|
239
242
|
"""Capture the user closing the window so we can save GUI
|
240
243
|
settings.
|
241
244
|
"""
|
@@ -253,7 +256,10 @@ class GuiManuscriptBuild(QDialog):
|
|
253
256
|
"""Handle button clicks from the dialog button box."""
|
254
257
|
role = self.dlgButtons.buttonRole(button)
|
255
258
|
if role == QDialogButtonBox.ActionRole:
|
256
|
-
self.
|
259
|
+
if button == self.btnBuild:
|
260
|
+
self._runBuild()
|
261
|
+
elif button == self.btnOpen:
|
262
|
+
self._openOutputFolder()
|
257
263
|
elif role == QDialogButtonBox.RejectRole:
|
258
264
|
self.close()
|
259
265
|
return
|
@@ -385,4 +391,9 @@ class GuiManuscriptBuild(QDialog):
|
|
385
391
|
|
386
392
|
return
|
387
393
|
|
394
|
+
def _openOutputFolder(self):
|
395
|
+
"""Open the build folder in the system's file explorer."""
|
396
|
+
openExternalPath(Path(self.buildPath.text()))
|
397
|
+
return
|
398
|
+
|
388
399
|
# END Class GuiManuscriptBuild
|
novelwriter/tools/manuscript.py
CHANGED
@@ -144,6 +144,8 @@ class GuiManuscript(QDialog):
|
|
144
144
|
self.buildList.setIconSize(QSize(iPx, iPx))
|
145
145
|
self.buildList.doubleClicked.connect(self._editSelectedBuild)
|
146
146
|
self.buildList.currentItemChanged.connect(self._updateBuildDetails)
|
147
|
+
self.buildList.setSelectionMode(QAbstractItemView.SingleSelection)
|
148
|
+
self.buildList.setDragDropMode(QAbstractItemView.InternalMove)
|
147
149
|
|
148
150
|
self.buildDetails = _DetailsWidget(self)
|
149
151
|
self.buildDetails.setColumnWidth(
|
@@ -417,9 +419,15 @@ class GuiManuscript(QDialog):
|
|
417
419
|
"""Save the user GUI settings."""
|
418
420
|
logger.debug("Saving GuiManuscript settings")
|
419
421
|
|
422
|
+
buildOrder = []
|
423
|
+
for i in range(self.buildList.count()):
|
424
|
+
if item := self.buildList.item(i):
|
425
|
+
buildOrder.append(item.data(self.D_KEY))
|
426
|
+
|
420
427
|
current = self.buildList.currentItem()
|
421
|
-
if isinstance(current, QListWidgetItem)
|
422
|
-
|
428
|
+
lastBuild = current.data(self.D_KEY) if isinstance(current, QListWidgetItem) else ""
|
429
|
+
|
430
|
+
self._builds.setBuildsState(lastBuild, buildOrder)
|
423
431
|
|
424
432
|
winWidth = CONFIG.rpxInt(self.width())
|
425
433
|
winHeight = CONFIG.rpxInt(self.height())
|
@@ -499,7 +507,7 @@ class _DetailsWidget(QWidget):
|
|
499
507
|
|
500
508
|
self._initExpanded = True
|
501
509
|
|
502
|
-
# Tree
|
510
|
+
# Tree Widget
|
503
511
|
self.listView = QTreeWidget(self)
|
504
512
|
self.listView.setHeaderLabels([self.tr("Setting"), self.tr("Value")])
|
505
513
|
self.listView.setIndentation(SHARED.theme.baseIconSize)
|
@@ -974,15 +974,6 @@ class _FormatTab(QWidget):
|
|
974
974
|
self.formFormat = NConfigLayout()
|
975
975
|
self.formFormat.addGroupLabel(self._build.getLabel("format.grpFormat"))
|
976
976
|
|
977
|
-
# Build Language
|
978
|
-
self.buildLang = QComboBox()
|
979
|
-
langauges = CONFIG.listLanguages(CONFIG.LANG_PROJ)
|
980
|
-
self.buildLang.addItem("[%s]" % self.tr("Not Set"), "None")
|
981
|
-
for langID, langName in langauges:
|
982
|
-
self.buildLang.addItem(langName, langID)
|
983
|
-
|
984
|
-
self.formFormat.addRow(self._build.getLabel("format.buildLang"), self.buildLang)
|
985
|
-
|
986
977
|
# Font Family
|
987
978
|
self.textFont = QLineEdit()
|
988
979
|
self.textFont.setReadOnly(True)
|
@@ -1103,10 +1094,6 @@ class _FormatTab(QWidget):
|
|
1103
1094
|
|
1104
1095
|
def loadContent(self) -> None:
|
1105
1096
|
"""Populate the widgets."""
|
1106
|
-
langIdx = self.buildLang.findData(self._build.getStr("format.buildLang"))
|
1107
|
-
if langIdx != -1:
|
1108
|
-
self.buildLang.setCurrentIndex(langIdx)
|
1109
|
-
|
1110
1097
|
textFont = self._build.getStr("format.textFont")
|
1111
1098
|
if not textFont:
|
1112
1099
|
textFont = str(CONFIG.textFont)
|
@@ -1146,7 +1133,6 @@ class _FormatTab(QWidget):
|
|
1146
1133
|
|
1147
1134
|
def saveContent(self) -> None:
|
1148
1135
|
"""Save choices back into build object."""
|
1149
|
-
self._build.setValue("format.buildLang", str(self.buildLang.currentData()))
|
1150
1136
|
self._build.setValue("format.textFont", self.textFont.text())
|
1151
1137
|
self._build.setValue("format.textSize", self.textSize.value())
|
1152
1138
|
self._build.setValue("format.lineHeight", self.lineHeight.value())
|
novelwriter/tools/projwizard.py
CHANGED
@@ -28,8 +28,9 @@ import logging
|
|
28
28
|
|
29
29
|
from PyQt5.QtCore import Qt
|
30
30
|
from PyQt5.QtWidgets import (
|
31
|
-
QFileDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel,
|
32
|
-
QPushButton, QRadioButton, QSpinBox, QVBoxLayout, QWizard,
|
31
|
+
QComboBox, QFileDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel,
|
32
|
+
QLineEdit, QPushButton, QRadioButton, QSpinBox, QVBoxLayout, QWizard,
|
33
|
+
QWizardPage
|
33
34
|
)
|
34
35
|
|
35
36
|
from novelwriter import CONFIG, SHARED
|
@@ -127,15 +128,28 @@ class ProjWizardIntroPage(QWizardPage):
|
|
127
128
|
self.projAuthor.setFixedWidth(xW)
|
128
129
|
self.projAuthor.setPlaceholderText(self.tr("Optional"))
|
129
130
|
|
131
|
+
self.projLang = QComboBox(self)
|
132
|
+
self.projLang.setMaximumWidth(xW)
|
133
|
+
for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ):
|
134
|
+
self.projLang.addItem(language, tag)
|
135
|
+
|
136
|
+
langIdx = self.projLang.findData(CONFIG.guiLocale)
|
137
|
+
if langIdx == -1:
|
138
|
+
langIdx = self.projLang.findData("en_GB")
|
139
|
+
if langIdx != -1:
|
140
|
+
self.projLang.setCurrentIndex(langIdx)
|
141
|
+
|
130
142
|
self.mainForm = QFormLayout()
|
131
143
|
self.mainForm.addRow(self.tr("Project Name"), self.projName)
|
132
144
|
self.mainForm.addRow(self.tr("Novel Title"), self.projTitle)
|
133
145
|
self.mainForm.addRow(self.tr("Author(s)"), self.projAuthor)
|
146
|
+
self.mainForm.addRow(self.tr("Language"), self.projLang)
|
134
147
|
self.mainForm.setVerticalSpacing(fS)
|
135
148
|
|
136
149
|
self.registerField("projName*", self.projName)
|
137
150
|
self.registerField("projTitle", self.projTitle)
|
138
151
|
self.registerField("projAuthor", self.projAuthor)
|
152
|
+
self.registerField("projLang", self.projLang)
|
139
153
|
|
140
154
|
# Assemble
|
141
155
|
self.outerBox = QVBoxLayout()
|
@@ -125,7 +125,7 @@ class GuiWritingStats(QDialog):
|
|
125
125
|
pOptions.getInt("GuiWritingStats", "sortOrder", Qt.DescendingOrder),
|
126
126
|
(Qt.AscendingOrder, Qt.DescendingOrder), Qt.DescendingOrder
|
127
127
|
)
|
128
|
-
self.listBox.sortByColumn(sortCol, sortOrder)
|
128
|
+
self.listBox.sortByColumn(sortCol, sortOrder) # type: ignore
|
129
129
|
self.listBox.setSortingEnabled(True)
|
130
130
|
|
131
131
|
# Word Bar
|
@@ -1,4 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
3
|
-
<path d="m12 2c-5.5138 0-10 4.4862-10 10s4.4862 10 10 10c2.02 0 3.965-0.59875 5.6238-1.73 0.57-0.39 0.7175-1.1675 0.32875-1.7375-0.38875-0.57125-1.165-0.715-1.7375-0.32875-1.2425 0.84875-2.7 1.2962-4.215 1.2962-4.1362 0-7.5-3.3638-7.5-7.5s3.3638-7.5 7.5-7.5 7.5 3.3638 7.5 7.5v0.625c0 0.69-0.56 1.25-1.25 1.25s-1.25-0.56-1.25-1.25v-3.75c0-0.69125-0.55875-1.25-1.25-1.25-0.55125 0-1.0062 0.3625-1.1725 0.86-0.725-0.53375-1.6112-0.86-2.5775-0.86-2.4125 0-4.375 1.9625-4.375 4.375s1.9625 4.375 4.375 4.375c1.3062 0 2.4688-0.5875 3.27-1.4988 0.685 0.90375 1.76 1.4988 2.98 1.4988 2.0675 0 3.75-1.6825 3.75-3.75v-0.625c0-5.5138-4.4862-10-10-10zm0 11.875c-1.0338 0-1.875-0.84125-1.875-1.875s0.84125-1.875 1.875-1.875 1.875 0.84125 1.875 1.875-0.84125 1.875-1.875 1.875z" fill="#69c" stroke-width="1.25"/>
|
4
|
-
</svg>
|
@@ -1,4 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
3
|
-
<path d="M19 17h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 10h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 3h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2z" fill="#69c"/>
|
4
|
-
</svg>
|
@@ -1,4 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
3
|
-
<path d="m12 2c-5.5138 0-10 4.4862-10 10s4.4862 10 10 10c2.02 0 3.965-0.59875 5.6238-1.73 0.57-0.39 0.7175-1.1675 0.32875-1.7375-0.38875-0.57125-1.165-0.715-1.7375-0.32875-1.2425 0.84875-2.7 1.2962-4.215 1.2962-4.1362 0-7.5-3.3638-7.5-7.5s3.3638-7.5 7.5-7.5 7.5 3.3638 7.5 7.5v0.625c0 0.69-0.56 1.25-1.25 1.25s-1.25-0.56-1.25-1.25v-3.75c0-0.69125-0.55875-1.25-1.25-1.25-0.55125 0-1.0062 0.3625-1.1725 0.86-0.725-0.53375-1.6112-0.86-2.5775-0.86-2.4125 0-4.375 1.9625-4.375 4.375s1.9625 4.375 4.375 4.375c1.3062 0 2.4688-0.5875 3.27-1.4988 0.685 0.90375 1.76 1.4988 2.98 1.4988 2.0675 0 3.75-1.6825 3.75-3.75v-0.625c0-5.5138-4.4862-10-10-10zm0 11.875c-1.0338 0-1.875-0.84125-1.875-1.875s0.84125-1.875 1.875-1.875 1.875 0.84125 1.875 1.875-0.84125 1.875-1.875 1.875z" fill="#4271ae" stroke-width="1.25"/>
|
4
|
-
</svg>
|
@@ -1,4 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
3
|
-
<path d="M19 17h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 10h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 3h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2z" fill="#4271ae"/>
|
4
|
-
</svg>
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|