novelWriter 2.5.1__py3-none-any.whl → 2.6b1__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.5.1.dist-info → novelWriter-2.6b1.dist-info}/METADATA +2 -1
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/RECORD +61 -56
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +3 -3
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +1 -0
- novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +1 -0
- novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +63 -0
- novelwriter/config.py +10 -3
- novelwriter/constants.py +153 -60
- novelwriter/core/buildsettings.py +66 -39
- novelwriter/core/coretools.py +34 -22
- novelwriter/core/docbuild.py +130 -169
- novelwriter/core/index.py +29 -18
- novelwriter/core/item.py +2 -2
- novelwriter/core/options.py +4 -1
- novelwriter/core/spellcheck.py +9 -14
- novelwriter/dialogs/preferences.py +45 -32
- novelwriter/dialogs/projectsettings.py +3 -3
- novelwriter/enum.py +29 -23
- novelwriter/extensions/configlayout.py +24 -11
- novelwriter/extensions/modified.py +13 -1
- novelwriter/extensions/pagedsidebar.py +5 -5
- novelwriter/formats/shared.py +155 -0
- novelwriter/formats/todocx.py +1195 -0
- novelwriter/formats/tohtml.py +452 -0
- novelwriter/{core → formats}/tokenizer.py +483 -485
- novelwriter/formats/tomarkdown.py +217 -0
- novelwriter/{core → formats}/toodt.py +270 -320
- novelwriter/formats/toqdoc.py +436 -0
- novelwriter/formats/toraw.py +91 -0
- novelwriter/gui/doceditor.py +240 -193
- novelwriter/gui/dochighlight.py +96 -84
- novelwriter/gui/docviewer.py +56 -30
- novelwriter/gui/docviewerpanel.py +3 -3
- novelwriter/gui/editordocument.py +17 -2
- novelwriter/gui/itemdetails.py +8 -4
- novelwriter/gui/mainmenu.py +121 -60
- novelwriter/gui/noveltree.py +35 -37
- novelwriter/gui/outline.py +186 -238
- novelwriter/gui/projtree.py +142 -131
- novelwriter/gui/sidebar.py +7 -6
- novelwriter/gui/theme.py +5 -4
- novelwriter/guimain.py +43 -155
- novelwriter/shared.py +14 -4
- novelwriter/text/counting.py +2 -0
- novelwriter/text/patterns.py +155 -59
- novelwriter/tools/manusbuild.py +1 -1
- novelwriter/tools/manuscript.py +121 -78
- novelwriter/tools/manussettings.py +403 -260
- novelwriter/tools/welcome.py +4 -4
- novelwriter/tools/writingstats.py +3 -3
- novelwriter/types.py +16 -6
- novelwriter/core/tohtml.py +0 -530
- novelwriter/core/tomarkdown.py +0 -252
- novelwriter/core/toqdoc.py +0 -419
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/top_level.txt +0 -0
novelwriter/guimain.py
CHANGED
@@ -44,7 +44,7 @@ from novelwriter.dialogs.about import GuiAbout
|
|
44
44
|
from novelwriter.dialogs.preferences import GuiPreferences
|
45
45
|
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
46
46
|
from novelwriter.dialogs.wordlist import GuiWordList
|
47
|
-
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwFocus,
|
47
|
+
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwFocus, nwView
|
48
48
|
from novelwriter.gui.doceditor import GuiDocEditor
|
49
49
|
from novelwriter.gui.docviewer import GuiDocViewer
|
50
50
|
from novelwriter.gui.docviewerpanel import GuiDocViewerPanel
|
@@ -249,11 +249,13 @@ class GuiMain(QMainWindow):
|
|
249
249
|
self.projSearch.openDocumentSelectRequest.connect(self._openDocumentSelection)
|
250
250
|
self.projSearch.selectedItemChanged.connect(self.itemDetails.updateViewBox)
|
251
251
|
|
252
|
-
self.docEditor.
|
252
|
+
self.docEditor.closeEditorRequest.connect(self.closeDocEditor)
|
253
253
|
self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
|
254
254
|
self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
|
255
255
|
self.docEditor.docTextChanged.connect(self.projSearch.textChanged)
|
256
256
|
self.docEditor.editedStatusChanged.connect(self.mainStatus.updateDocumentStatus)
|
257
|
+
self.docEditor.itemHandleChanged.connect(self.novelView.setActiveHandle)
|
258
|
+
self.docEditor.itemHandleChanged.connect(self.projView.setActiveHandle)
|
257
259
|
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
|
258
260
|
self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta)
|
259
261
|
self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
|
@@ -262,12 +264,13 @@ class GuiMain(QMainWindow):
|
|
262
264
|
self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
|
263
265
|
self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
|
264
266
|
self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState)
|
265
|
-
self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage)
|
266
267
|
self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
|
268
|
+
self.docEditor.updateStatusMessage.connect(self.mainStatus.setStatusMessage)
|
267
269
|
|
268
270
|
self.docViewer.closeDocumentRequest.connect(self.closeDocViewer)
|
269
271
|
self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle)
|
270
272
|
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
|
273
|
+
self.docViewer.openDocumentRequest.connect(self._openDocument)
|
271
274
|
self.docViewer.reloadDocumentRequest.connect(self._reloadViewer)
|
272
275
|
self.docViewer.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
|
273
276
|
self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
|
@@ -289,19 +292,17 @@ class GuiMain(QMainWindow):
|
|
289
292
|
self.asDocTimer = QTimer(self)
|
290
293
|
self.asDocTimer.timeout.connect(self._autoSaveDocument)
|
291
294
|
|
292
|
-
# Shortcuts
|
293
|
-
self._connectMenuActions()
|
294
|
-
|
295
|
+
# Shortcuts
|
295
296
|
self.keyReturn = QShortcut(self)
|
296
|
-
self.keyReturn.setKey(
|
297
|
+
self.keyReturn.setKey("Return")
|
297
298
|
self.keyReturn.activated.connect(self._keyPressReturn)
|
298
299
|
|
299
300
|
self.keyEnter = QShortcut(self)
|
300
|
-
self.keyEnter.setKey(
|
301
|
+
self.keyEnter.setKey("Enter")
|
301
302
|
self.keyEnter.activated.connect(self._keyPressReturn)
|
302
303
|
|
303
304
|
self.keyEscape = QShortcut(self)
|
304
|
-
self.keyEscape.setKey(
|
305
|
+
self.keyEscape.setKey("Esc")
|
305
306
|
self.keyEscape.activated.connect(self._keyPressEscape)
|
306
307
|
|
307
308
|
# Initialise Main GUI
|
@@ -481,8 +482,7 @@ class GuiMain(QMainWindow):
|
|
481
482
|
QApplication.processEvents()
|
482
483
|
self.openDocument(lastEdited, doScroll=True)
|
483
484
|
|
484
|
-
lastViewed
|
485
|
-
if lastViewed is not None:
|
485
|
+
if lastViewed := SHARED.project.data.getLastHandle("viewer"):
|
486
486
|
QApplication.processEvents()
|
487
487
|
self.viewDocument(lastViewed)
|
488
488
|
|
@@ -512,7 +512,7 @@ class GuiMain(QMainWindow):
|
|
512
512
|
# Document Actions
|
513
513
|
##
|
514
514
|
|
515
|
-
def closeDocument(self
|
515
|
+
def closeDocument(self) -> None:
|
516
516
|
"""Close the document and clear the editor and title field."""
|
517
517
|
if SHARED.hasProject:
|
518
518
|
# Disable focus mode if it is active
|
@@ -520,8 +520,6 @@ class GuiMain(QMainWindow):
|
|
520
520
|
SHARED.setFocusMode(False)
|
521
521
|
self.saveDocument()
|
522
522
|
self.docEditor.clearEditor()
|
523
|
-
if not beforeOpen:
|
524
|
-
self.novelView.setActiveHandle(None)
|
525
523
|
return
|
526
524
|
|
527
525
|
def openDocument(
|
@@ -533,12 +531,8 @@ class GuiMain(QMainWindow):
|
|
533
531
|
doScroll: bool = False
|
534
532
|
) -> bool:
|
535
533
|
"""Open a specific document, optionally at a given line."""
|
536
|
-
if not SHARED.hasProject:
|
537
|
-
logger.error("
|
538
|
-
return False
|
539
|
-
|
540
|
-
if not tHandle or not SHARED.project.tree.checkType(tHandle, nwItemType.FILE):
|
541
|
-
logger.debug("Requested item '%s' is not a document", tHandle)
|
534
|
+
if not (SHARED.hasProject and tHandle):
|
535
|
+
logger.error("Nothing to open open")
|
542
536
|
return False
|
543
537
|
|
544
538
|
if sTitle and tLine is None:
|
@@ -548,19 +542,15 @@ class GuiMain(QMainWindow):
|
|
548
542
|
self._changeView(nwView.EDITOR)
|
549
543
|
if tHandle == self.docEditor.docHandle:
|
550
544
|
self.docEditor.setCursorLine(tLine)
|
551
|
-
if changeFocus:
|
552
|
-
self.docEditor.setFocus()
|
553
|
-
return True
|
554
|
-
|
555
|
-
self.closeDocument(beforeOpen=True)
|
556
|
-
if self.docEditor.loadText(tHandle, tLine):
|
557
|
-
SHARED.project.data.setLastHandle(tHandle, "editor")
|
558
|
-
self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
|
559
|
-
self.novelView.setActiveHandle(tHandle, doScroll=doScroll)
|
560
|
-
if changeFocus:
|
561
|
-
self.docEditor.setFocus()
|
562
545
|
else:
|
563
|
-
|
546
|
+
self.closeDocument()
|
547
|
+
if self.docEditor.loadText(tHandle, tLine):
|
548
|
+
self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
|
549
|
+
else:
|
550
|
+
return False
|
551
|
+
|
552
|
+
if changeFocus:
|
553
|
+
self.docEditor.setFocus()
|
564
554
|
|
565
555
|
return True
|
566
556
|
|
@@ -855,13 +845,11 @@ class GuiMain(QMainWindow):
|
|
855
845
|
|
856
846
|
def closeMain(self) -> bool:
|
857
847
|
"""Save everything, and close novelWriter."""
|
858
|
-
if SHARED.hasProject
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
if not msgYes:
|
864
|
-
return False
|
848
|
+
if SHARED.hasProject and not SHARED.question("%s<br>%s" % (
|
849
|
+
self.tr("Do you want to exit novelWriter?"),
|
850
|
+
self.tr("Changes are saved automatically.")
|
851
|
+
)):
|
852
|
+
return False
|
865
853
|
|
866
854
|
logger.info("Exiting novelWriter")
|
867
855
|
|
@@ -906,11 +894,6 @@ class GuiMain(QMainWindow):
|
|
906
894
|
|
907
895
|
return not self.splitView.isVisible()
|
908
896
|
|
909
|
-
def toggleFullScreenMode(self) -> None:
|
910
|
-
"""Toggle full screen mode"""
|
911
|
-
self.setWindowState(self.windowState() ^ Qt.WindowState.WindowFullScreen)
|
912
|
-
return
|
913
|
-
|
914
897
|
##
|
915
898
|
# Events
|
916
899
|
##
|
@@ -926,6 +909,12 @@ class GuiMain(QMainWindow):
|
|
926
909
|
# Public Slots
|
927
910
|
##
|
928
911
|
|
912
|
+
@pyqtSlot()
|
913
|
+
def toggleFullScreenMode(self) -> None:
|
914
|
+
"""Toggle full screen mode"""
|
915
|
+
self.setWindowState(self.windowState() ^ Qt.WindowState.WindowFullScreen)
|
916
|
+
return
|
917
|
+
|
929
918
|
@pyqtSlot()
|
930
919
|
def closeDocEditor(self) -> None:
|
931
920
|
"""Close the document editor. This does not hide the editor."""
|
@@ -1111,8 +1100,16 @@ class GuiMain(QMainWindow):
|
|
1111
1100
|
@pyqtSlot(str, nwDocMode)
|
1112
1101
|
def _followTag(self, tag: str, mode: nwDocMode) -> None:
|
1113
1102
|
"""Follow a tag after user interaction with a link."""
|
1114
|
-
tHandle, sTitle =
|
1115
|
-
if tHandle is
|
1103
|
+
tHandle, sTitle = SHARED.project.index.getTagSource(tag)
|
1104
|
+
if tHandle is None:
|
1105
|
+
SHARED.error(self.tr(
|
1106
|
+
"Could not find the reference for tag '{0}'. It either doesn't "
|
1107
|
+
"exist, or the index is out of date. The index can be updated "
|
1108
|
+
"from the Tools menu, or by pressing {1}."
|
1109
|
+
).format(
|
1110
|
+
tag, "F9"
|
1111
|
+
))
|
1112
|
+
else:
|
1116
1113
|
if mode == nwDocMode.EDIT:
|
1117
1114
|
self.openDocument(tHandle, sTitle=sTitle)
|
1118
1115
|
elif mode == nwDocMode.VIEW:
|
@@ -1299,116 +1296,7 @@ class GuiMain(QMainWindow):
|
|
1299
1296
|
# Internal Functions
|
1300
1297
|
##
|
1301
1298
|
|
1302
|
-
def _connectMenuActions(self) -> None:
|
1303
|
-
"""Connect to the main window all menu actions that need to be
|
1304
|
-
available also when the main menu is hidden.
|
1305
|
-
"""
|
1306
|
-
# Project
|
1307
|
-
self.addAction(self.mainMenu.aSaveProject)
|
1308
|
-
self.addAction(self.mainMenu.aEditItem)
|
1309
|
-
self.addAction(self.mainMenu.aExitNW)
|
1310
|
-
|
1311
|
-
# Document
|
1312
|
-
self.addAction(self.mainMenu.aSaveDoc)
|
1313
|
-
self.addAction(self.mainMenu.aCloseDoc)
|
1314
|
-
|
1315
|
-
# Edit
|
1316
|
-
self.addAction(self.mainMenu.aEditUndo)
|
1317
|
-
self.addAction(self.mainMenu.aEditRedo)
|
1318
|
-
self.addAction(self.mainMenu.aEditCut)
|
1319
|
-
self.addAction(self.mainMenu.aEditCopy)
|
1320
|
-
self.addAction(self.mainMenu.aEditPaste)
|
1321
|
-
self.addAction(self.mainMenu.aSelectAll)
|
1322
|
-
self.addAction(self.mainMenu.aSelectPar)
|
1323
|
-
|
1324
|
-
# View
|
1325
|
-
self.addAction(self.mainMenu.aFocusMode)
|
1326
|
-
self.addAction(self.mainMenu.aFullScreen)
|
1327
|
-
|
1328
|
-
# Insert
|
1329
|
-
self.addAction(self.mainMenu.aInsENDash)
|
1330
|
-
self.addAction(self.mainMenu.aInsEMDash)
|
1331
|
-
self.addAction(self.mainMenu.aInsHorBar)
|
1332
|
-
self.addAction(self.mainMenu.aInsFigDash)
|
1333
|
-
self.addAction(self.mainMenu.aInsQuoteLS)
|
1334
|
-
self.addAction(self.mainMenu.aInsQuoteRS)
|
1335
|
-
self.addAction(self.mainMenu.aInsQuoteLD)
|
1336
|
-
self.addAction(self.mainMenu.aInsQuoteRD)
|
1337
|
-
self.addAction(self.mainMenu.aInsMSApos)
|
1338
|
-
self.addAction(self.mainMenu.aInsEllipsis)
|
1339
|
-
self.addAction(self.mainMenu.aInsPrime)
|
1340
|
-
self.addAction(self.mainMenu.aInsDPrime)
|
1341
|
-
self.addAction(self.mainMenu.aInsNBSpace)
|
1342
|
-
self.addAction(self.mainMenu.aInsThinSpace)
|
1343
|
-
self.addAction(self.mainMenu.aInsThinNBSpace)
|
1344
|
-
self.addAction(self.mainMenu.aInsBullet)
|
1345
|
-
self.addAction(self.mainMenu.aInsHyBull)
|
1346
|
-
self.addAction(self.mainMenu.aInsFlower)
|
1347
|
-
self.addAction(self.mainMenu.aInsPerMille)
|
1348
|
-
self.addAction(self.mainMenu.aInsDegree)
|
1349
|
-
self.addAction(self.mainMenu.aInsMinus)
|
1350
|
-
self.addAction(self.mainMenu.aInsTimes)
|
1351
|
-
self.addAction(self.mainMenu.aInsDivide)
|
1352
|
-
self.addAction(self.mainMenu.aInsSynopsis)
|
1353
|
-
self.addAction(self.mainMenu.aInsShort)
|
1354
|
-
|
1355
|
-
for mAction, _ in self.mainMenu.mInsKWItems.values():
|
1356
|
-
self.addAction(mAction)
|
1357
|
-
|
1358
|
-
# Search
|
1359
|
-
self.addAction(self.mainMenu.aFind)
|
1360
|
-
self.addAction(self.mainMenu.aReplace)
|
1361
|
-
self.addAction(self.mainMenu.aFindNext)
|
1362
|
-
self.addAction(self.mainMenu.aFindPrev)
|
1363
|
-
self.addAction(self.mainMenu.aReplaceNext)
|
1364
|
-
|
1365
|
-
# Format
|
1366
|
-
self.addAction(self.mainMenu.aFmtItalic)
|
1367
|
-
self.addAction(self.mainMenu.aFmtBold)
|
1368
|
-
self.addAction(self.mainMenu.aFmtStrike)
|
1369
|
-
self.addAction(self.mainMenu.aFmtDQuote)
|
1370
|
-
self.addAction(self.mainMenu.aFmtSQuote)
|
1371
|
-
self.addAction(self.mainMenu.aFmtHead1)
|
1372
|
-
self.addAction(self.mainMenu.aFmtHead2)
|
1373
|
-
self.addAction(self.mainMenu.aFmtHead3)
|
1374
|
-
self.addAction(self.mainMenu.aFmtHead4)
|
1375
|
-
self.addAction(self.mainMenu.aFmtAlignLeft)
|
1376
|
-
self.addAction(self.mainMenu.aFmtAlignCentre)
|
1377
|
-
self.addAction(self.mainMenu.aFmtAlignRight)
|
1378
|
-
self.addAction(self.mainMenu.aFmtIndentLeft)
|
1379
|
-
self.addAction(self.mainMenu.aFmtIndentRight)
|
1380
|
-
self.addAction(self.mainMenu.aFmtComment)
|
1381
|
-
self.addAction(self.mainMenu.aFmtNoFormat)
|
1382
|
-
|
1383
|
-
# Tools
|
1384
|
-
self.addAction(self.mainMenu.aSpellCheck)
|
1385
|
-
self.addAction(self.mainMenu.aReRunSpell)
|
1386
|
-
self.addAction(self.mainMenu.aPreferences)
|
1387
|
-
|
1388
|
-
# Help
|
1389
|
-
self.addAction(self.mainMenu.aHelpDocs)
|
1390
|
-
if isinstance(CONFIG.pdfDocs, Path):
|
1391
|
-
self.addAction(self.mainMenu.aPdfDocs)
|
1392
|
-
|
1393
|
-
return
|
1394
|
-
|
1395
1299
|
def _updateWindowTitle(self, projName: str | None = None) -> None:
|
1396
1300
|
"""Set the window title and add the project's name."""
|
1397
1301
|
self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName])))
|
1398
1302
|
return
|
1399
|
-
|
1400
|
-
def _getTagSource(self, tag: str) -> tuple[str | None, str | None]:
|
1401
|
-
"""Handle the index lookup of a tag and display an alert if the
|
1402
|
-
tag cannot be found.
|
1403
|
-
"""
|
1404
|
-
tHandle, sTitle = SHARED.project.index.getTagSource(tag)
|
1405
|
-
if tHandle is None:
|
1406
|
-
SHARED.error(self.tr(
|
1407
|
-
"Could not find the reference for tag '{0}'. It either doesn't "
|
1408
|
-
"exist, or the index is out of date. The index can be updated "
|
1409
|
-
"from the Tools menu, or by pressing {1}."
|
1410
|
-
).format(
|
1411
|
-
tag, "F9"
|
1412
|
-
))
|
1413
|
-
return None, None
|
1414
|
-
return tHandle, sTitle
|
novelwriter/shared.py
CHANGED
@@ -30,8 +30,8 @@ from pathlib import Path
|
|
30
30
|
from time import time
|
31
31
|
from typing import TYPE_CHECKING, TypeVar
|
32
32
|
|
33
|
-
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal
|
34
|
-
from PyQt5.QtGui import QFont
|
33
|
+
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
|
34
|
+
from PyQt5.QtGui import QDesktopServices, QFont
|
35
35
|
from PyQt5.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
|
36
36
|
|
37
37
|
from novelwriter.common import formatFileFilter
|
@@ -292,6 +292,16 @@ class SharedData(QObject):
|
|
292
292
|
return widget
|
293
293
|
return None
|
294
294
|
|
295
|
+
##
|
296
|
+
# Public Slots
|
297
|
+
##
|
298
|
+
|
299
|
+
@pyqtSlot(str)
|
300
|
+
def openWebsite(self, url: str) -> None:
|
301
|
+
"""Open a URL in the system's default browser."""
|
302
|
+
QDesktopServices.openUrl(QUrl(url))
|
303
|
+
return
|
304
|
+
|
295
305
|
##
|
296
306
|
# Signal Proxy
|
297
307
|
##
|
@@ -441,9 +451,9 @@ class _GuiAlert(QMessageBox):
|
|
441
451
|
Yes/No buttons or just an Ok button.
|
442
452
|
"""
|
443
453
|
if isYesNo:
|
444
|
-
self.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
454
|
+
self.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
445
455
|
else:
|
446
|
-
self.setStandardButtons(QMessageBox.Ok)
|
456
|
+
self.setStandardButtons(QMessageBox.StandardButton.Ok)
|
447
457
|
pSz = 2*self._theme.baseIconHeight
|
448
458
|
if level == self.INFO:
|
449
459
|
self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz)))
|
novelwriter/text/counting.py
CHANGED
@@ -30,6 +30,7 @@ import re
|
|
30
30
|
from novelwriter.constants import nwRegEx, nwUnicode
|
31
31
|
|
32
32
|
RX_SC = re.compile(nwRegEx.FMT_SC)
|
33
|
+
RX_SV = re.compile(nwRegEx.FMT_SV)
|
33
34
|
RX_LO = re.compile(r"(?i)(?<!\\)(\[(?:vspace|newpage|new page)(:\d+)?)(?<!\\)(\])")
|
34
35
|
|
35
36
|
|
@@ -64,6 +65,7 @@ def preProcessText(text: str, keepHeaders: bool = True) -> list[str]:
|
|
64
65
|
# Strip shortcodes and special formatting
|
65
66
|
# RegEx is slow, so we do this only when necessary
|
66
67
|
line = RX_SC.sub("", line)
|
68
|
+
line = RX_SV.sub("", line)
|
67
69
|
line = RX_LO.sub("", line)
|
68
70
|
|
69
71
|
result.append(line)
|
novelwriter/text/patterns.py
CHANGED
@@ -3,7 +3,8 @@ novelWriter – Text Pattern Functions
|
|
3
3
|
====================================
|
4
4
|
|
5
5
|
File History:
|
6
|
-
Created: 2024-06-01 [2.
|
6
|
+
Created: 2024-06-01 [2.5rc1] RegExPatterns
|
7
|
+
Created: 2024-11-04 [2.6b1] DialogParser
|
7
8
|
|
8
9
|
This file is a part of novelWriter
|
9
10
|
Copyright 2018–2024, Veronica Berglyd Olsen
|
@@ -23,91 +24,186 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
23
24
|
"""
|
24
25
|
from __future__ import annotations
|
25
26
|
|
26
|
-
|
27
|
+
import re
|
27
28
|
|
28
29
|
from novelwriter import CONFIG
|
30
|
+
from novelwriter.common import compact, uniqueCompact
|
29
31
|
from novelwriter.constants import nwRegEx
|
30
|
-
from novelwriter.types import QRegExUnicode
|
31
32
|
|
32
33
|
|
33
34
|
class RegExPatterns:
|
34
35
|
|
36
|
+
# Static RegExes
|
37
|
+
_rxUrl = re.compile(nwRegEx.URL, re.ASCII)
|
38
|
+
_rxWords = re.compile(nwRegEx.WORDS, re.UNICODE)
|
39
|
+
_rxBreak = re.compile(nwRegEx.BREAK, re.UNICODE)
|
40
|
+
_rxItalic = re.compile(nwRegEx.FMT_EI, re.UNICODE)
|
41
|
+
_rxBold = re.compile(nwRegEx.FMT_EB, re.UNICODE)
|
42
|
+
_rxStrike = re.compile(nwRegEx.FMT_ST, re.UNICODE)
|
43
|
+
_rxSCPlain = re.compile(nwRegEx.FMT_SC, re.UNICODE)
|
44
|
+
_rxSCValue = re.compile(nwRegEx.FMT_SV, re.UNICODE)
|
45
|
+
|
46
|
+
@property
|
47
|
+
def url(self) -> re.Pattern:
|
48
|
+
"""Find URLs."""
|
49
|
+
return self._rxUrl
|
50
|
+
|
51
|
+
@property
|
52
|
+
def wordSplit(self) -> re.Pattern:
|
53
|
+
"""Split text into words."""
|
54
|
+
return self._rxWords
|
55
|
+
|
35
56
|
@property
|
36
|
-
def
|
57
|
+
def lineBreak(self) -> re.Pattern:
|
58
|
+
"""Find forced line break."""
|
59
|
+
return self._rxBreak
|
60
|
+
|
61
|
+
@property
|
62
|
+
def markdownItalic(self) -> re.Pattern:
|
37
63
|
"""Markdown italic style."""
|
38
|
-
|
39
|
-
rxRule.setPatternOptions(QRegExUnicode)
|
40
|
-
return rxRule
|
64
|
+
return self._rxItalic
|
41
65
|
|
42
66
|
@property
|
43
|
-
def markdownBold(self) ->
|
67
|
+
def markdownBold(self) -> re.Pattern:
|
44
68
|
"""Markdown bold style."""
|
45
|
-
|
46
|
-
rxRule.setPatternOptions(QRegExUnicode)
|
47
|
-
return rxRule
|
69
|
+
return self._rxBold
|
48
70
|
|
49
71
|
@property
|
50
|
-
def markdownStrike(self) ->
|
72
|
+
def markdownStrike(self) -> re.Pattern:
|
51
73
|
"""Markdown strikethrough style."""
|
52
|
-
|
53
|
-
rxRule.setPatternOptions(QRegExUnicode)
|
54
|
-
return rxRule
|
74
|
+
return self._rxStrike
|
55
75
|
|
56
76
|
@property
|
57
|
-
def shortcodePlain(self) ->
|
77
|
+
def shortcodePlain(self) -> re.Pattern:
|
58
78
|
"""Plain shortcode style."""
|
59
|
-
|
60
|
-
rxRule.setPatternOptions(QRegExUnicode)
|
61
|
-
return rxRule
|
79
|
+
return self._rxSCPlain
|
62
80
|
|
63
81
|
@property
|
64
|
-
def shortcodeValue(self) ->
|
82
|
+
def shortcodeValue(self) -> re.Pattern:
|
65
83
|
"""Plain shortcode style."""
|
66
|
-
|
67
|
-
rxRule.setPatternOptions(QRegExUnicode)
|
68
|
-
return rxRule
|
84
|
+
return self._rxSCValue
|
69
85
|
|
70
86
|
@property
|
71
|
-
def dialogStyle(self) ->
|
87
|
+
def dialogStyle(self) -> re.Pattern | None:
|
72
88
|
"""Dialogue detection rule based on user settings."""
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
return
|
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
|
89
|
+
if CONFIG.dialogStyle > 0:
|
90
|
+
end = "|$" if CONFIG.allowOpenDial else ""
|
91
|
+
rx = []
|
92
|
+
if CONFIG.dialogStyle in (1, 3):
|
93
|
+
qO = CONFIG.fmtSQuoteOpen.strip()[:1]
|
94
|
+
qC = CONFIG.fmtSQuoteClose.strip()[:1]
|
95
|
+
rx.append(f"(?:\\B{qO}.*?(?:{qC}\\B{end}))")
|
96
|
+
if CONFIG.dialogStyle in (2, 3):
|
97
|
+
qO = CONFIG.fmtDQuoteOpen.strip()[:1]
|
98
|
+
qC = CONFIG.fmtDQuoteClose.strip()[:1]
|
99
|
+
rx.append(f"(?:\\B{qO}.*?(?:{qC}\\B{end}))")
|
100
|
+
return re.compile("|".join(rx), re.UNICODE)
|
101
|
+
return None
|
94
102
|
|
95
103
|
@property
|
96
|
-
def
|
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:
|
104
|
+
def altDialogStyle(self) -> re.Pattern | None:
|
105
105
|
"""Dialogue alternative rule based on user settings."""
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
return
|
106
|
+
if CONFIG.altDialogOpen and CONFIG.altDialogClose:
|
107
|
+
qO = re.escape(compact(CONFIG.altDialogOpen))
|
108
|
+
qC = re.escape(compact(CONFIG.altDialogClose))
|
109
|
+
return re.compile(f"\\B{qO}.*?{qC}\\B", re.UNICODE)
|
110
|
+
return None
|
111
111
|
|
112
112
|
|
113
113
|
REGEX_PATTERNS = RegExPatterns()
|
114
|
+
|
115
|
+
|
116
|
+
class DialogParser:
|
117
|
+
|
118
|
+
__slots__ = (
|
119
|
+
"_quotes", "_dialog", "_alternate", "_enabled",
|
120
|
+
"_narrator", "_breakD", "_breakQ", "_mode",
|
121
|
+
)
|
122
|
+
|
123
|
+
def __init__(self) -> None:
|
124
|
+
self._quotes = None
|
125
|
+
self._dialog = ""
|
126
|
+
self._alternate = ""
|
127
|
+
self._enabled = False
|
128
|
+
self._narrator = ""
|
129
|
+
self._breakD = None
|
130
|
+
self._breakQ = None
|
131
|
+
self._mode = ""
|
132
|
+
return
|
133
|
+
|
134
|
+
@property
|
135
|
+
def enabled(self) -> bool:
|
136
|
+
"""Return True if there are any settings to parse."""
|
137
|
+
return self._enabled
|
138
|
+
|
139
|
+
def initParser(self) -> None:
|
140
|
+
"""Init parser settings. This method must also be called when
|
141
|
+
the config changes.
|
142
|
+
"""
|
143
|
+
self._quotes = REGEX_PATTERNS.dialogStyle
|
144
|
+
self._dialog = uniqueCompact(CONFIG.dialogLine)
|
145
|
+
self._alternate = CONFIG.narratorDialog.strip()[:1]
|
146
|
+
|
147
|
+
# One of the three modes are needed for the class to have
|
148
|
+
# anything to do
|
149
|
+
self._enabled = bool(self._quotes or self._dialog or self._alternate)
|
150
|
+
|
151
|
+
# Build narrator break RegExes
|
152
|
+
if narrator := CONFIG.narratorBreak.strip()[:1]:
|
153
|
+
punct = re.escape(".,:;!?")
|
154
|
+
self._breakD = re.compile(f"{narrator}.*?(?:{narrator}[{punct}]?|$)", re.UNICODE)
|
155
|
+
self._breakQ = re.compile(f"{narrator}.*?(?:{narrator}[{punct}]?)", re.UNICODE)
|
156
|
+
self._narrator = narrator
|
157
|
+
self._mode = f" {narrator}"
|
158
|
+
|
159
|
+
return
|
160
|
+
|
161
|
+
def __call__(self, text: str) -> list[tuple[int, int]]:
|
162
|
+
"""Caller wrapper for dialogue processing."""
|
163
|
+
temp: list[int] = []
|
164
|
+
result: list[tuple[int, int]] = []
|
165
|
+
if text:
|
166
|
+
plain = True
|
167
|
+
if self._dialog and text[0] in self._dialog:
|
168
|
+
# The whole line is dialogue
|
169
|
+
plain = False
|
170
|
+
temp.append(0)
|
171
|
+
temp.append(len(text))
|
172
|
+
if self._breakD:
|
173
|
+
# Process narrator breaks in the dialogue
|
174
|
+
for res in self._breakD.finditer(text, 1):
|
175
|
+
temp.append(res.start(0))
|
176
|
+
temp.append(res.end(0))
|
177
|
+
elif self._quotes:
|
178
|
+
# Quoted dialogue is enabled, so we look for them
|
179
|
+
for res in self._quotes.finditer(text):
|
180
|
+
plain = False
|
181
|
+
temp.append(res.start(0))
|
182
|
+
temp.append(res.end(0))
|
183
|
+
if self._breakQ:
|
184
|
+
for sub in self._breakQ.finditer(text, res.start(0), res.end(0)):
|
185
|
+
temp.append(sub.start(0))
|
186
|
+
temp.append(sub.end(0))
|
187
|
+
|
188
|
+
if plain and self._alternate:
|
189
|
+
# The main rules found no dialogue, so we check for
|
190
|
+
# alternating dialogue sections, if enabled
|
191
|
+
pos = 0
|
192
|
+
for num, bit in enumerate(text.split(self._alternate)):
|
193
|
+
length = len(bit) + (1 if num > 0 else 0)
|
194
|
+
if num%2:
|
195
|
+
temp.append(pos)
|
196
|
+
temp.append(pos + length)
|
197
|
+
pos += length
|
198
|
+
|
199
|
+
if temp:
|
200
|
+
# Sort unique edges in increasing order, and add them in pairs
|
201
|
+
start = None
|
202
|
+
for pos in sorted(set(temp)):
|
203
|
+
if start is None:
|
204
|
+
start = pos
|
205
|
+
else:
|
206
|
+
result.append((start, pos))
|
207
|
+
start = None
|
208
|
+
|
209
|
+
return result
|
novelwriter/tools/manusbuild.py
CHANGED
@@ -333,7 +333,7 @@ class GuiManuscriptBuild(NDialog):
|
|
333
333
|
docBuild.queueAll()
|
334
334
|
|
335
335
|
self.buildProgress.setMaximum(len(docBuild))
|
336
|
-
for i, _ in docBuild.
|
336
|
+
for i, _ in docBuild.iterBuildDocument(buildPath, bFormat):
|
337
337
|
self.buildProgress.setValue(i+1)
|
338
338
|
|
339
339
|
self._build.setLastBuildPath(bPath)
|