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)
         
     |