novelWriter 2.7.5__py3-none-any.whl → 2.8b1__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/__init__.py +8 -7
 - novelwriter/assets/icons/font_awesome.icons +22 -4
 - novelwriter/assets/icons/material_filled_normal.icons +20 -2
 - novelwriter/assets/icons/material_filled_thin.icons +20 -2
 - novelwriter/assets/icons/material_rounded_normal.icons +20 -2
 - novelwriter/assets/icons/material_rounded_thin.icons +20 -2
 - novelwriter/assets/icons/material_sharp_normal.icons +20 -2
 - novelwriter/assets/icons/material_sharp_thin.icons +20 -2
 - novelwriter/assets/icons/remix_filled.icons +20 -2
 - novelwriter/assets/icons/remix_outline.icons +20 -2
 - novelwriter/assets/images/welcome.webp +0 -0
 - novelwriter/assets/manual.pdf +0 -0
 - novelwriter/assets/manual_fr.pdf +0 -0
 - novelwriter/assets/sample.zip +0 -0
 - novelwriter/assets/text/credits_en.htm +61 -11
 - novelwriter/assets/themes/aura.conf +97 -0
 - novelwriter/assets/themes/aura_bright.conf +95 -0
 - novelwriter/assets/themes/aura_soft.conf +97 -0
 - novelwriter/assets/themes/b2t_garden_dark.conf +97 -0
 - novelwriter/assets/themes/b2t_garden_light.conf +97 -0
 - novelwriter/assets/themes/b2t_suburb_dark.conf +97 -0
 - novelwriter/assets/themes/b2t_suburb_light.conf +97 -0
 - novelwriter/assets/themes/b4t_classic_o_dark.conf +97 -0
 - novelwriter/assets/themes/b4t_classic_o_light.conf +97 -0
 - novelwriter/assets/themes/b4t_modern_c_dark.conf +97 -0
 - novelwriter/assets/themes/b4t_modern_c_light.conf +97 -0
 - novelwriter/assets/themes/blue_streak_dark.conf +97 -0
 - novelwriter/assets/themes/blue_streak_light.conf +97 -0
 - novelwriter/assets/themes/castle_day.conf +95 -0
 - novelwriter/assets/themes/castle_night.conf +95 -0
 - novelwriter/assets/themes/catppuccin_latte.conf +97 -0
 - novelwriter/assets/themes/catppuccin_mocha.conf +97 -0
 - novelwriter/assets/themes/chalky_soil.conf +95 -0
 - novelwriter/assets/themes/chernozem.conf +95 -0
 - novelwriter/assets/themes/cyberpunk_night.conf +88 -40
 - novelwriter/assets/themes/default_dark.conf +89 -41
 - novelwriter/assets/themes/default_light.conf +89 -41
 - novelwriter/assets/themes/dracula.conf +91 -42
 - novelwriter/assets/themes/espresso.conf +97 -0
 - novelwriter/assets/themes/everforest_dark.conf +97 -0
 - novelwriter/assets/themes/everforest_light.conf +97 -0
 - novelwriter/assets/themes/floral_daydream.conf +95 -0
 - novelwriter/assets/themes/floral_midnight.conf +95 -0
 - novelwriter/assets/themes/full_moon.conf +95 -0
 - novelwriter/assets/themes/grey_dark.conf +97 -0
 - novelwriter/assets/themes/grey_light.conf +97 -0
 - novelwriter/assets/themes/horizon_dark.conf +97 -0
 - novelwriter/assets/themes/horizon_light.conf +97 -0
 - novelwriter/assets/themes/jewel_case_dark.conf +95 -0
 - novelwriter/assets/themes/jewel_case_light.conf +95 -0
 - novelwriter/assets/themes/lcars.conf +97 -0
 - novelwriter/assets/themes/light_owl.conf +117 -0
 - novelwriter/assets/themes/new_moon.conf +97 -0
 - novelwriter/assets/themes/night_owl.conf +117 -0
 - novelwriter/assets/themes/noctis.conf +129 -0
 - novelwriter/assets/themes/noctis_lux.conf +129 -0
 - novelwriter/assets/themes/nord.conf +97 -0
 - novelwriter/assets/themes/nordlicht.conf +95 -0
 - novelwriter/assets/themes/otium_dark.conf +95 -0
 - novelwriter/assets/themes/otium_light.conf +95 -0
 - novelwriter/assets/themes/paragon.conf +96 -0
 - novelwriter/assets/themes/primer_light.conf +97 -0
 - novelwriter/assets/themes/primer_night.conf +97 -0
 - novelwriter/assets/themes/rose_pine.conf +97 -0
 - novelwriter/assets/themes/rose_pine_dawn.conf +97 -0
 - novelwriter/assets/themes/ruby_day.conf +95 -0
 - novelwriter/assets/themes/ruby_night.conf +95 -0
 - novelwriter/assets/themes/selenium_dark.conf +95 -0
 - novelwriter/assets/themes/selenium_light.conf +95 -0
 - novelwriter/assets/themes/sepia_dark.conf +95 -0
 - novelwriter/assets/themes/sepia_light.conf +95 -0
 - novelwriter/assets/themes/snazzy.conf +102 -40
 - novelwriter/assets/themes/solarized_dark.conf +108 -40
 - novelwriter/assets/themes/solarized_light.conf +108 -40
 - novelwriter/assets/themes/sultana_light.conf +95 -0
 - novelwriter/assets/themes/sultana_night.conf +95 -0
 - novelwriter/assets/themes/tango_dark.conf +111 -0
 - novelwriter/assets/themes/tango_light.conf +111 -0
 - novelwriter/assets/themes/tomorrow.conf +117 -0
 - novelwriter/assets/themes/tomorrow_night.conf +117 -0
 - novelwriter/assets/themes/tomorrow_night_blue.conf +117 -0
 - novelwriter/assets/themes/tomorrow_night_bright.conf +117 -0
 - novelwriter/assets/themes/tomorrow_night_eighties.conf +117 -0
 - novelwriter/assets/themes/vivid_black_green.conf +97 -0
 - novelwriter/assets/themes/vivid_black_red.conf +97 -0
 - novelwriter/assets/themes/vivid_white_green.conf +97 -0
 - novelwriter/assets/themes/vivid_white_red.conf +97 -0
 - novelwriter/assets/themes/warpgate.conf +96 -0
 - novelwriter/assets/themes/waterlily_dark.conf +95 -0
 - novelwriter/assets/themes/waterlily_light.conf +95 -0
 - novelwriter/common.py +47 -17
 - novelwriter/config.py +57 -62
 - novelwriter/constants.py +32 -6
 - novelwriter/core/buildsettings.py +3 -23
 - novelwriter/core/coretools.py +21 -25
 - novelwriter/core/docbuild.py +4 -9
 - novelwriter/core/document.py +2 -6
 - novelwriter/core/index.py +33 -53
 - novelwriter/core/indexdata.py +17 -22
 - novelwriter/core/item.py +11 -35
 - novelwriter/core/itemmodel.py +5 -21
 - novelwriter/core/novelmodel.py +3 -7
 - novelwriter/core/options.py +3 -4
 - novelwriter/core/project.py +31 -21
 - novelwriter/core/projectdata.py +2 -21
 - novelwriter/core/projectxml.py +13 -21
 - novelwriter/core/sessions.py +2 -4
 - novelwriter/core/spellcheck.py +12 -13
 - novelwriter/core/status.py +27 -20
 - novelwriter/core/storage.py +5 -10
 - novelwriter/core/tree.py +6 -15
 - novelwriter/dialogs/about.py +9 -10
 - novelwriter/dialogs/docmerge.py +17 -14
 - novelwriter/dialogs/docsplit.py +18 -14
 - novelwriter/dialogs/editlabel.py +15 -9
 - novelwriter/dialogs/preferences.py +69 -68
 - novelwriter/dialogs/projectsettings.py +88 -67
 - novelwriter/dialogs/quotes.py +15 -10
 - novelwriter/dialogs/wordlist.py +18 -21
 - novelwriter/enum.py +75 -30
 - novelwriter/error.py +6 -11
 - novelwriter/extensions/configlayout.py +8 -34
 - novelwriter/extensions/eventfilters.py +3 -3
 - novelwriter/extensions/modified.py +87 -32
 - novelwriter/extensions/novelselector.py +13 -12
 - novelwriter/extensions/pagedsidebar.py +10 -18
 - novelwriter/extensions/progressbars.py +5 -11
 - novelwriter/extensions/statusled.py +3 -6
 - novelwriter/extensions/switch.py +8 -11
 - novelwriter/extensions/switchbox.py +2 -11
 - novelwriter/extensions/versioninfo.py +6 -7
 - novelwriter/formats/shared.py +10 -2
 - novelwriter/formats/todocx.py +15 -37
 - novelwriter/formats/tohtml.py +52 -61
 - novelwriter/formats/tokenizer.py +33 -64
 - novelwriter/formats/tomarkdown.py +4 -11
 - novelwriter/formats/toodt.py +12 -71
 - novelwriter/formats/toqdoc.py +11 -21
 - novelwriter/formats/toraw.py +2 -6
 - novelwriter/gui/doceditor.py +160 -225
 - novelwriter/gui/dochighlight.py +142 -101
 - novelwriter/gui/docviewer.py +53 -84
 - novelwriter/gui/docviewerpanel.py +18 -41
 - novelwriter/gui/editordocument.py +12 -17
 - novelwriter/gui/itemdetails.py +5 -14
 - novelwriter/gui/mainmenu.py +24 -32
 - novelwriter/gui/noveltree.py +13 -51
 - novelwriter/gui/outline.py +20 -61
 - novelwriter/gui/projtree.py +40 -96
 - novelwriter/gui/search.py +9 -24
 - novelwriter/gui/sidebar.py +54 -22
 - novelwriter/gui/statusbar.py +7 -22
 - novelwriter/gui/theme.py +482 -368
 - novelwriter/guimain.py +87 -101
 - novelwriter/shared.py +79 -48
 - novelwriter/splash.py +9 -5
 - novelwriter/text/comments.py +1 -1
 - novelwriter/text/counting.py +9 -5
 - novelwriter/text/patterns.py +20 -15
 - novelwriter/tools/dictionaries.py +18 -16
 - novelwriter/tools/lipsum.py +15 -17
 - novelwriter/tools/manusbuild.py +25 -45
 - novelwriter/tools/manuscript.py +94 -95
 - novelwriter/tools/manussettings.py +149 -104
 - novelwriter/tools/noveldetails.py +10 -24
 - novelwriter/tools/welcome.py +24 -72
 - novelwriter/tools/writingstats.py +17 -26
 - novelwriter/types.py +23 -13
 - {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/METADATA +7 -7
 - novelwriter-2.8b1.dist-info/RECORD +212 -0
 - novelwriter/assets/images/welcome-dark.jpg +0 -0
 - novelwriter/assets/images/welcome-light.jpg +0 -0
 - novelwriter/assets/syntax/cyberpunk_night.conf +0 -28
 - novelwriter/assets/syntax/default_dark.conf +0 -42
 - novelwriter/assets/syntax/default_light.conf +0 -42
 - novelwriter/assets/syntax/dracula.conf +0 -44
 - novelwriter/assets/syntax/grey_dark.conf +0 -29
 - novelwriter/assets/syntax/grey_light.conf +0 -29
 - novelwriter/assets/syntax/light_owl.conf +0 -49
 - novelwriter/assets/syntax/night_owl.conf +0 -49
 - novelwriter/assets/syntax/snazzy.conf +0 -42
 - novelwriter/assets/syntax/solarized_dark.conf +0 -29
 - novelwriter/assets/syntax/solarized_light.conf +0 -29
 - novelwriter/assets/syntax/tango.conf +0 -39
 - novelwriter/assets/syntax/tomorrow.conf +0 -49
 - novelwriter/assets/syntax/tomorrow_night.conf +0 -49
 - novelwriter/assets/syntax/tomorrow_night_blue.conf +0 -49
 - novelwriter/assets/syntax/tomorrow_night_bright.conf +0 -49
 - novelwriter/assets/syntax/tomorrow_night_eighties.conf +0 -49
 - novelwriter/assets/themes/default.conf +0 -3
 - novelwriter-2.7.5.dist-info/RECORD +0 -163
 - {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/WHEEL +0 -0
 - {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/entry_points.txt +0 -0
 - {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/licenses/LICENSE.md +0 -0
 - {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/licenses/setup/LICENSE-Apache-2.0.txt +0 -0
 - {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/top_level.txt +0 -0
 
    
        novelwriter/shared.py
    CHANGED
    
    | 
         @@ -21,10 +21,11 @@ General Public License for more details. 
     | 
|
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
            You should have received a copy of the GNU General Public License
         
     | 
| 
       23 
23 
     | 
    
         
             
            along with this program. If not, see <https://www.gnu.org/licenses/>.
         
     | 
| 
       24 
     | 
    
         
            -
            """
         
     | 
| 
      
 24 
     | 
    
         
            +
            """  # noqa
         
     | 
| 
       25 
25 
     | 
    
         
             
            from __future__ import annotations
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
27 
     | 
    
         
             
            import logging
         
     | 
| 
      
 28 
     | 
    
         
            +
            import re
         
     | 
| 
       28 
29 
     | 
    
         | 
| 
       29 
30 
     | 
    
         
             
            from enum import Enum
         
     | 
| 
       30 
31 
     | 
    
         
             
            from pathlib import Path
         
     | 
| 
         @@ -32,15 +33,17 @@ from time import time 
     | 
|
| 
       32 
33 
     | 
    
         
             
            from typing import TYPE_CHECKING, TypeVar
         
     | 
| 
       33 
34 
     | 
    
         | 
| 
       34 
35 
     | 
    
         
             
            from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
         
     | 
| 
       35 
     | 
    
         
            -
            from PyQt6.QtGui import QDesktopServices, QFont
         
     | 
| 
       36 
     | 
    
         
            -
            from PyQt6.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
         
     | 
| 
      
 36 
     | 
    
         
            +
            from PyQt6.QtGui import QDesktopServices, QFont, QScreen
         
     | 
| 
      
 37 
     | 
    
         
            +
            from PyQt6.QtWidgets import QApplication, QFileDialog, QFontDialog, QMessageBox, QWidget
         
     | 
| 
       37 
38 
     | 
    
         | 
| 
       38 
39 
     | 
    
         
             
            from novelwriter.common import formatFileFilter
         
     | 
| 
       39 
40 
     | 
    
         
             
            from novelwriter.constants import nwFiles
         
     | 
| 
       40 
41 
     | 
    
         
             
            from novelwriter.core.spellcheck import NWSpellEnchant
         
     | 
| 
       41 
     | 
    
         
            -
            from novelwriter.enum import nwChange, nwItemClass
         
     | 
| 
      
 42 
     | 
    
         
            +
            from novelwriter.enum import nwChange, nwItemClass, nwStandardButton
         
     | 
| 
       42 
43 
     | 
    
         | 
| 
       43 
44 
     | 
    
         
             
            if TYPE_CHECKING:
         
     | 
| 
      
 45 
     | 
    
         
            +
                from collections.abc import Callable
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
       44 
47 
     | 
    
         
             
                from novelwriter.core.project import NWProject
         
     | 
| 
       45 
48 
     | 
    
         
             
                from novelwriter.core.status import T_StatusKind
         
     | 
| 
       46 
49 
     | 
    
         
             
                from novelwriter.gui.theme import GuiTheme
         
     | 
| 
         @@ -50,8 +53,16 @@ logger = logging.getLogger(__name__) 
     | 
|
| 
       50 
53 
     | 
    
         | 
| 
       51 
54 
     | 
    
         
             
            NWWidget = TypeVar("NWWidget", bound=QWidget)
         
     | 
| 
       52 
55 
     | 
    
         | 
| 
      
 56 
     | 
    
         
            +
            RX_HTML = re.compile(r"<.*?>")
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
       53 
58 
     | 
    
         | 
| 
       54 
59 
     | 
    
         
             
            class SharedData(QObject):
         
     | 
| 
      
 60 
     | 
    
         
            +
                """Shared Data Singleton.
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                This is the class instantiated as the SHARED singleton. It holds
         
     | 
| 
      
 63 
     | 
    
         
            +
                various globally needed data and pointers to important objects like
         
     | 
| 
      
 64 
     | 
    
         
            +
                the main GUI, the current project, and the GUI theme.
         
     | 
| 
      
 65 
     | 
    
         
            +
                """
         
     | 
| 
       55 
66 
     | 
    
         | 
| 
       56 
67 
     | 
    
         
             
                __slots__ = (
         
     | 
| 
       57 
68 
     | 
    
         
             
                    "_gui", "_idleRefTime", "_idleTime", "_lastAlert", "_lockedBy",
         
     | 
| 
         @@ -91,8 +102,6 @@ class SharedData(QObject): 
     | 
|
| 
       91 
102 
     | 
    
         
             
                    self._clock.setInterval(1000)
         
     | 
| 
       92 
103 
     | 
    
         
             
                    self._clock.timeout.connect(lambda: self.mainClockTick.emit())
         
     | 
| 
       93 
104 
     | 
    
         | 
| 
       94 
     | 
    
         
            -
                    return
         
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
       96 
105 
     | 
    
         
             
                ##
         
     | 
| 
       97 
106 
     | 
    
         
             
                #  Properties
         
     | 
| 
       98 
107 
     | 
    
         
             
                ##
         
     | 
| 
         @@ -150,6 +159,11 @@ class SharedData(QObject): 
     | 
|
| 
       150 
159 
     | 
    
         
             
                    """Return the last alert message."""
         
     | 
| 
       151 
160 
     | 
    
         
             
                    return self._lastAlert
         
     | 
| 
       152 
161 
     | 
    
         | 
| 
      
 162 
     | 
    
         
            +
                @property
         
     | 
| 
      
 163 
     | 
    
         
            +
                def mainScreen(self) -> QScreen | None:
         
     | 
| 
      
 164 
     | 
    
         
            +
                    """Return the screen of the main window."""
         
     | 
| 
      
 165 
     | 
    
         
            +
                    return QApplication.screenAt(self.mainGui.rect().center())
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
       153 
167 
     | 
    
         
             
                ##
         
     | 
| 
       154 
168 
     | 
    
         
             
                #  Setters
         
     | 
| 
       155 
169 
     | 
    
         
             
                ##
         
     | 
| 
         @@ -159,7 +173,6 @@ class SharedData(QObject): 
     | 
|
| 
       159 
173 
     | 
    
         
             
                    if state is not self._focusMode:
         
     | 
| 
       160 
174 
     | 
    
         
             
                        self._focusMode = state
         
     | 
| 
       161 
175 
     | 
    
         
             
                        self.focusModeChanged.emit(state)
         
     | 
| 
       162 
     | 
    
         
            -
                    return
         
     | 
| 
       163 
176 
     | 
    
         | 
| 
       164 
177 
     | 
    
         
             
                ##
         
     | 
| 
       165 
178 
     | 
    
         
             
                #  Methods
         
     | 
| 
         @@ -170,7 +183,7 @@ class SharedData(QObject): 
     | 
|
| 
       170 
183 
     | 
    
         
             
                    is created.
         
     | 
| 
       171 
184 
     | 
    
         
             
                    """
         
     | 
| 
       172 
185 
     | 
    
         
             
                    self._theme = theme
         
     | 
| 
       173 
     | 
    
         
            -
                     
     | 
| 
      
 186 
     | 
    
         
            +
                    self._theme.initThemes()
         
     | 
| 
       174 
187 
     | 
    
         | 
| 
       175 
188 
     | 
    
         
             
                def initSharedData(self, gui: GuiMain) -> None:
         
     | 
| 
       176 
189 
     | 
    
         
             
                    """Initialise the SharedData instance. This must be called as
         
     | 
| 
         @@ -183,7 +196,6 @@ class SharedData(QObject): 
     | 
|
| 
       183 
196 
     | 
    
         
             
                    logger.debug("Ready: SharedData")
         
     | 
| 
       184 
197 
     | 
    
         
             
                    if pool := QThreadPool.globalInstance():
         
     | 
| 
       185 
198 
     | 
    
         
             
                        logger.debug("Thread Pool Max Count: %d", pool.maxThreadCount())
         
     | 
| 
       186 
     | 
    
         
            -
                    return
         
     | 
| 
       187 
199 
     | 
    
         | 
| 
       188 
200 
     | 
    
         
             
                def closeDocument(self, tHandle: str | None = None) -> None:
         
     | 
| 
       189 
201 
     | 
    
         
             
                    """Close the document editor, optionally a specific document."""
         
     | 
| 
         @@ -191,7 +203,6 @@ class SharedData(QObject): 
     | 
|
| 
       191 
203 
     | 
    
         
             
                        self.mainGui.closeDocument()
         
     | 
| 
       192 
204 
     | 
    
         
             
                    if tHandle is None or tHandle == self.mainGui.docViewer.docHandle:
         
     | 
| 
       193 
205 
     | 
    
         
             
                        self.mainGui.closeViewerPanel()
         
     | 
| 
       194 
     | 
    
         
            -
                    return
         
     | 
| 
       195 
206 
     | 
    
         | 
| 
       196 
207 
     | 
    
         
             
                def saveEditor(self, tHandle: str | None = None) -> None:
         
     | 
| 
       197 
208 
     | 
    
         
             
                    """Save the editor content, optionally a specific document."""
         
     | 
| 
         @@ -202,7 +213,6 @@ class SharedData(QObject): 
     | 
|
| 
       202 
213 
     | 
    
         
             
                    ):
         
     | 
| 
       203 
214 
     | 
    
         
             
                        logger.debug("Saving editor document before action")
         
     | 
| 
       204 
215 
     | 
    
         
             
                        docEditor.saveText()
         
     | 
| 
       205 
     | 
    
         
            -
                    return
         
     | 
| 
       206 
216 
     | 
    
         | 
| 
       207 
217 
     | 
    
         
             
                def openProject(self, path: str | Path, clearLock: bool = False) -> bool:
         
     | 
| 
       208 
218 
     | 
    
         
             
                    """Open a project."""
         
     | 
| 
         @@ -235,7 +245,6 @@ class SharedData(QObject): 
     | 
|
| 
       235 
245 
     | 
    
         
             
                    self.project.closeProject(self._idleTime)
         
     | 
| 
       236 
246 
     | 
    
         
             
                    self._resetProject()
         
     | 
| 
       237 
247 
     | 
    
         
             
                    self._resetIdleTimer()
         
     | 
| 
       238 
     | 
    
         
            -
                    return
         
     | 
| 
       239 
248 
     | 
    
         | 
| 
       240 
249 
     | 
    
         
             
                def updateSpellCheckLanguage(self, reload: bool = False) -> None:
         
     | 
| 
       241 
250 
     | 
    
         
             
                    """Update the active spell check language from settings."""
         
     | 
| 
         @@ -245,7 +254,6 @@ class SharedData(QObject): 
     | 
|
| 
       245 
254 
     | 
    
         
             
                        self.spelling.setLanguage(language)
         
     | 
| 
       246 
255 
     | 
    
         
             
                        _, provider = self.spelling.describeDict()
         
     | 
| 
       247 
256 
     | 
    
         
             
                        self.spellLanguageChanged.emit(language, provider)
         
     | 
| 
       248 
     | 
    
         
            -
                    return
         
     | 
| 
       249 
257 
     | 
    
         | 
| 
       250 
258 
     | 
    
         
             
                def updateIdleTime(self, currTime: float, userIdle: bool) -> None:
         
     | 
| 
       251 
259 
     | 
    
         
             
                    """Update the idle time record. If the userIdle flag is True,
         
     | 
| 
         @@ -256,27 +264,40 @@ class SharedData(QObject): 
     | 
|
| 
       256 
264 
     | 
    
         
             
                    if userIdle:
         
     | 
| 
       257 
265 
     | 
    
         
             
                        self._idleTime += currTime - self._idleRefTime
         
     | 
| 
       258 
266 
     | 
    
         
             
                    self._idleRefTime = currTime
         
     | 
| 
       259 
     | 
    
         
            -
             
     | 
| 
      
 267 
     | 
    
         
            +
             
     | 
| 
      
 268 
     | 
    
         
            +
                def initMainProgress(self, maximum: int, inclusive: bool = False) -> None:
         
     | 
| 
      
 269 
     | 
    
         
            +
                    """Start a session for the main progress bar."""
         
     | 
| 
      
 270 
     | 
    
         
            +
                    if gui := self._gui:
         
     | 
| 
      
 271 
     | 
    
         
            +
                        gui.mainProgress.setMaximum(maximum - (1 if inclusive else 0))
         
     | 
| 
      
 272 
     | 
    
         
            +
                        gui.mainProgress.setValue(0)
         
     | 
| 
      
 273 
     | 
    
         
            +
             
     | 
| 
      
 274 
     | 
    
         
            +
                def incMainProgress(self) -> None:
         
     | 
| 
      
 275 
     | 
    
         
            +
                    """Increment the value for the main progress bar."""
         
     | 
| 
      
 276 
     | 
    
         
            +
                    if gui := self._gui:
         
     | 
| 
      
 277 
     | 
    
         
            +
                        gui.mainProgress.setValue(gui.mainProgress.value() + 1)
         
     | 
| 
      
 278 
     | 
    
         
            +
                        QApplication.processEvents()
         
     | 
| 
      
 279 
     | 
    
         
            +
             
     | 
| 
      
 280 
     | 
    
         
            +
                def clearMainProgress(self, delay: float = 1.0) -> None:
         
     | 
| 
      
 281 
     | 
    
         
            +
                    """Clear the main progress bar."""
         
     | 
| 
      
 282 
     | 
    
         
            +
                    if gui := self._gui:
         
     | 
| 
      
 283 
     | 
    
         
            +
                        QTimer.singleShot(int(delay*1000), gui.mainProgress.reset)
         
     | 
| 
       260 
284 
     | 
    
         | 
| 
       261 
285 
     | 
    
         
             
                def newStatusMessage(self, message: str) -> None:
         
     | 
| 
       262 
286 
     | 
    
         
             
                    """Request a new status message. This is a callable function for
         
     | 
| 
       263 
287 
     | 
    
         
             
                    core classes that cannot emit signals on their own.
         
     | 
| 
       264 
288 
     | 
    
         
             
                    """
         
     | 
| 
       265 
289 
     | 
    
         
             
                    self.projectStatusMessage.emit(message)
         
     | 
| 
       266 
     | 
    
         
            -
                    return
         
     | 
| 
       267 
290 
     | 
    
         | 
| 
       268 
291 
     | 
    
         
             
                def setGlobalProjectState(self, state: bool) -> None:
         
     | 
| 
       269 
292 
     | 
    
         
             
                    """Change the global project status. This is a callable function
         
     | 
| 
       270 
293 
     | 
    
         
             
                    for core classes that cannot emit signals on their own.
         
     | 
| 
       271 
294 
     | 
    
         
             
                    """
         
     | 
| 
       272 
295 
     | 
    
         
             
                    self.projectStatusChanged.emit(state)
         
     | 
| 
       273 
     | 
    
         
            -
                    return
         
     | 
| 
       274 
296 
     | 
    
         | 
| 
       275 
297 
     | 
    
         
             
                def runInThreadPool(self, runnable: QRunnable, priority: int = 0) -> None:
         
     | 
| 
       276 
298 
     | 
    
         
             
                    """Queue a runnable in the application thread pool."""
         
     | 
| 
       277 
299 
     | 
    
         
             
                    if pool := QThreadPool.globalInstance():
         
     | 
| 
       278 
300 
     | 
    
         
             
                        pool.start(runnable, priority=priority)
         
     | 
| 
       279 
     | 
    
         
            -
                    return
         
     | 
| 
       280 
301 
     | 
    
         | 
| 
       281 
302 
     | 
    
         
             
                def getProjectPath(
         
     | 
| 
       282 
303 
     | 
    
         
             
                    self, parent: QWidget,
         
     | 
| 
         @@ -315,13 +336,11 @@ class SharedData(QObject): 
     | 
|
| 
       315 
336 
     | 
    
         
             
                def openWebsite(self, url: str) -> None:
         
     | 
| 
       316 
337 
     | 
    
         
             
                    """Open a URL in the system's default browser."""
         
     | 
| 
       317 
338 
     | 
    
         
             
                    QDesktopServices.openUrl(QUrl(url))
         
     | 
| 
       318 
     | 
    
         
            -
                    return
         
     | 
| 
       319 
339 
     | 
    
         | 
| 
       320 
340 
     | 
    
         
             
                @pyqtSlot(str, nwItemClass)
         
     | 
| 
       321 
341 
     | 
    
         
             
                def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
         
     | 
| 
       322 
342 
     | 
    
         
             
                    """Process new note request."""
         
     | 
| 
       323 
343 
     | 
    
         
             
                    self.project.createNewNote(tag, itemClass)
         
     | 
| 
       324 
     | 
    
         
            -
                    return
         
     | 
| 
       325 
344 
     | 
    
         | 
| 
       326 
345 
     | 
    
         
             
                ##
         
     | 
| 
       327 
346 
     | 
    
         
             
                #  Signal Proxies
         
     | 
| 
         @@ -333,37 +352,31 @@ class SharedData(QObject): 
     | 
|
| 
       333 
352 
     | 
    
         
             
                    """Emit the indexChangedTags signal."""
         
     | 
| 
       334 
353 
     | 
    
         
             
                    if self._project and self._project.data.uuid == project.data.uuid:
         
     | 
| 
       335 
354 
     | 
    
         
             
                        self.indexChangedTags.emit(updated, deleted)
         
     | 
| 
       336 
     | 
    
         
            -
                    return
         
     | 
| 
       337 
355 
     | 
    
         | 
| 
       338 
356 
     | 
    
         
             
                def emitIndexCleared(self, project: NWProject) -> None:
         
     | 
| 
       339 
357 
     | 
    
         
             
                    """Emit the indexCleared signal."""
         
     | 
| 
       340 
358 
     | 
    
         
             
                    if self._project and self._project.data.uuid == project.data.uuid:
         
     | 
| 
       341 
359 
     | 
    
         
             
                        self.indexCleared.emit()
         
     | 
| 
       342 
     | 
    
         
            -
                    return
         
     | 
| 
       343 
360 
     | 
    
         | 
| 
       344 
361 
     | 
    
         
             
                def emitIndexAvailable(self, project: NWProject) -> None:
         
     | 
| 
       345 
362 
     | 
    
         
             
                    """Emit the indexAvailable signal."""
         
     | 
| 
       346 
363 
     | 
    
         
             
                    if self._project and self._project.data.uuid == project.data.uuid:
         
     | 
| 
       347 
364 
     | 
    
         
             
                        self.indexAvailable.emit()
         
     | 
| 
       348 
     | 
    
         
            -
                    return
         
     | 
| 
       349 
365 
     | 
    
         | 
| 
       350 
366 
     | 
    
         
             
                def emitStatusLabelsChanged(self, project: NWProject, kind: T_StatusKind) -> None:
         
     | 
| 
       351 
367 
     | 
    
         
             
                    """Emit the statusLabelsChanged signal."""
         
     | 
| 
       352 
368 
     | 
    
         
             
                    if self._project and self._project.data.uuid == project.data.uuid:
         
     | 
| 
       353 
369 
     | 
    
         
             
                        self.statusLabelsChanged.emit(kind)
         
     | 
| 
       354 
     | 
    
         
            -
                    return
         
     | 
| 
       355 
370 
     | 
    
         | 
| 
       356 
371 
     | 
    
         
             
                def emitProjectItemChanged(self, project: NWProject, handle: str, change: nwChange) -> None:
         
     | 
| 
       357 
372 
     | 
    
         
             
                    """Emit the projectItemChanged signal."""
         
     | 
| 
       358 
373 
     | 
    
         
             
                    if self._project and self._project.data.uuid == project.data.uuid:
         
     | 
| 
       359 
374 
     | 
    
         
             
                        self.projectItemChanged.emit(handle, change)
         
     | 
| 
       360 
     | 
    
         
            -
                    return
         
     | 
| 
       361 
375 
     | 
    
         | 
| 
       362 
376 
     | 
    
         
             
                def emitRootFolderChanged(self, project: NWProject, handle: str, change: nwChange) -> None:
         
     | 
| 
       363 
377 
     | 
    
         
             
                    """Emit the rootFolderChanged signal."""
         
     | 
| 
       364 
378 
     | 
    
         
             
                    if self._project and self._project.data.uuid == project.data.uuid:
         
     | 
| 
       365 
379 
     | 
    
         
             
                        self.rootFolderChanged.emit(handle, change)
         
     | 
| 
       366 
     | 
    
         
            -
                    return
         
     | 
| 
       367 
380 
     | 
    
         | 
| 
       368 
381 
     | 
    
         
             
                ##
         
     | 
| 
       369 
382 
     | 
    
         
             
                #  Alert Boxes
         
     | 
| 
         @@ -376,9 +389,8 @@ class SharedData(QObject): 
     | 
|
| 
       376 
389 
     | 
    
         
             
                    alert.setAlertType(_GuiAlert.INFO, False)
         
     | 
| 
       377 
390 
     | 
    
         
             
                    self._lastAlert = alert.logMessage
         
     | 
| 
       378 
391 
     | 
    
         
             
                    if log:
         
     | 
| 
       379 
     | 
    
         
            -
                         
     | 
| 
      
 392 
     | 
    
         
            +
                        self._logMessage(self._lastAlert, logger.info)
         
     | 
| 
       380 
393 
     | 
    
         
             
                    alert.exec()
         
     | 
| 
       381 
     | 
    
         
            -
                    return
         
     | 
| 
       382 
394 
     | 
    
         | 
| 
       383 
395 
     | 
    
         
             
                def warn(self, text: str, info: str = "", details: str = "", log: bool = True) -> None:
         
     | 
| 
       384 
396 
     | 
    
         
             
                    """Open a warning alert box."""
         
     | 
| 
         @@ -387,9 +399,8 @@ class SharedData(QObject): 
     | 
|
| 
       387 
399 
     | 
    
         
             
                    alert.setAlertType(_GuiAlert.WARN, False)
         
     | 
| 
       388 
400 
     | 
    
         
             
                    self._lastAlert = alert.logMessage
         
     | 
| 
       389 
401 
     | 
    
         
             
                    if log:
         
     | 
| 
       390 
     | 
    
         
            -
                         
     | 
| 
      
 402 
     | 
    
         
            +
                        self._logMessage(self._lastAlert, logger.warning)
         
     | 
| 
       391 
403 
     | 
    
         
             
                    alert.exec()
         
     | 
| 
       392 
     | 
    
         
            -
                    return
         
     | 
| 
       393 
404 
     | 
    
         | 
| 
       394 
405 
     | 
    
         
             
                def error(self, text: str, info: str = "", details: str = "", log: bool = True,
         
     | 
| 
       395 
406 
     | 
    
         
             
                          exc: Exception | None = None) -> None:
         
     | 
| 
         @@ -401,9 +412,8 @@ class SharedData(QObject): 
     | 
|
| 
       401 
412 
     | 
    
         
             
                        alert.setException(exc)
         
     | 
| 
       402 
413 
     | 
    
         
             
                    self._lastAlert = alert.logMessage
         
     | 
| 
       403 
414 
     | 
    
         
             
                    if log:
         
     | 
| 
       404 
     | 
    
         
            -
                         
     | 
| 
      
 415 
     | 
    
         
            +
                        self._logMessage(self._lastAlert, logger.error)
         
     | 
| 
       405 
416 
     | 
    
         
             
                    alert.exec()
         
     | 
| 
       406 
     | 
    
         
            -
                    return
         
     | 
| 
       407 
417 
     | 
    
         | 
| 
       408 
418 
     | 
    
         
             
                def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
         
     | 
| 
       409 
419 
     | 
    
         
             
                    """Open a question box."""
         
     | 
| 
         @@ -412,13 +422,17 @@ class SharedData(QObject): 
     | 
|
| 
       412 
422 
     | 
    
         
             
                    alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
         
     | 
| 
       413 
423 
     | 
    
         
             
                    self._lastAlert = alert.logMessage
         
     | 
| 
       414 
424 
     | 
    
         
             
                    alert.exec()
         
     | 
| 
       415 
     | 
    
         
            -
                     
     | 
| 
       416 
     | 
    
         
            -
                    return isYes
         
     | 
| 
      
 425 
     | 
    
         
            +
                    return alert.finalState
         
     | 
| 
       417 
426 
     | 
    
         | 
| 
       418 
427 
     | 
    
         
             
                ##
         
     | 
| 
       419 
428 
     | 
    
         
             
                #  Internal Functions
         
     | 
| 
       420 
429 
     | 
    
         
             
                ##
         
     | 
| 
       421 
430 
     | 
    
         | 
| 
      
 431 
     | 
    
         
            +
                def _logMessage(self, message: str, log: Callable) -> None:
         
     | 
| 
      
 432 
     | 
    
         
            +
                    """Print message to log."""
         
     | 
| 
      
 433 
     | 
    
         
            +
                    for text in message.split("<br>"):
         
     | 
| 
      
 434 
     | 
    
         
            +
                        log(RX_HTML.sub("", text), stacklevel=3)
         
     | 
| 
      
 435 
     | 
    
         
            +
             
     | 
| 
       422 
436 
     | 
    
         
             
                def _resetProject(self) -> None:
         
     | 
| 
       423 
437 
     | 
    
         
             
                    """Create a new project and spell checking instance."""
         
     | 
| 
       424 
438 
     | 
    
         
             
                    from novelwriter.core.project import NWProject
         
     | 
| 
         @@ -430,13 +444,11 @@ class SharedData(QObject): 
     | 
|
| 
       430 
444 
     | 
    
         
             
                    self._spelling = NWSpellEnchant(self._project)
         
     | 
| 
       431 
445 
     | 
    
         
             
                    self.updateSpellCheckLanguage()
         
     | 
| 
       432 
446 
     | 
    
         
             
                    self._focusMode = False
         
     | 
| 
       433 
     | 
    
         
            -
                    return
         
     | 
| 
       434 
447 
     | 
    
         | 
| 
       435 
448 
     | 
    
         
             
                def _resetIdleTimer(self) -> None:
         
     | 
| 
       436 
449 
     | 
    
         
             
                    """Reset the timer data for the idle timer."""
         
     | 
| 
       437 
450 
     | 
    
         
             
                    self._idleRefTime = time()
         
     | 
| 
       438 
451 
     | 
    
         
             
                    self._idleTime = 0.0
         
     | 
| 
       439 
     | 
    
         
            -
                    return
         
     | 
| 
       440 
452 
     | 
    
         | 
| 
       441 
453 
     | 
    
         
             
                def _closeToolDialogs(self) -> None:
         
     | 
| 
       442 
454 
     | 
    
         
             
                    """Close all open tool dialogs."""
         
     | 
| 
         @@ -444,7 +456,6 @@ class SharedData(QObject): 
     | 
|
| 
       444 
456 
     | 
    
         
             
                    for widget in self.mainGui.children():
         
     | 
| 
       445 
457 
     | 
    
         
             
                        if isinstance(widget, NToolDialog):
         
     | 
| 
       446 
458 
     | 
    
         
             
                            widget.close()
         
     | 
| 
       447 
     | 
    
         
            -
                    return
         
     | 
| 
       448 
459 
     | 
    
         | 
| 
       449 
460 
     | 
    
         | 
| 
       450 
461 
     | 
    
         
             
            class _GuiAlert(QMessageBox):
         
     | 
| 
         @@ -458,51 +469,71 @@ class _GuiAlert(QMessageBox): 
     | 
|
| 
       458 
469 
     | 
    
         
             
                    super().__init__(parent=parent)
         
     | 
| 
       459 
470 
     | 
    
         
             
                    self._theme = theme
         
     | 
| 
       460 
471 
     | 
    
         
             
                    self._message = ""
         
     | 
| 
      
 472 
     | 
    
         
            +
                    self._state = False
         
     | 
| 
       461 
473 
     | 
    
         
             
                    logger.debug("Ready: _GuiAlert")
         
     | 
| 
       462 
     | 
    
         
            -
                    return
         
     | 
| 
       463 
474 
     | 
    
         | 
| 
       464 
475 
     | 
    
         
             
                def __del__(self) -> None:  # pragma: no cover
         
     | 
| 
       465 
476 
     | 
    
         
             
                    logger.debug("Delete: _GuiAlert")
         
     | 
| 
       466 
     | 
    
         
            -
                    return
         
     | 
| 
       467 
477 
     | 
    
         | 
| 
       468 
478 
     | 
    
         
             
                @property
         
     | 
| 
       469 
479 
     | 
    
         
             
                def logMessage(self) -> str:
         
     | 
| 
       470 
480 
     | 
    
         
             
                    return self._message
         
     | 
| 
       471 
481 
     | 
    
         | 
| 
      
 482 
     | 
    
         
            +
                @property
         
     | 
| 
      
 483 
     | 
    
         
            +
                def finalState(self) -> bool:
         
     | 
| 
      
 484 
     | 
    
         
            +
                    return self._state
         
     | 
| 
      
 485 
     | 
    
         
            +
             
     | 
| 
       472 
486 
     | 
    
         
             
                def setMessage(self, text: str, info: str, details: str) -> None:
         
     | 
| 
       473 
487 
     | 
    
         
             
                    """Set the alert box message."""
         
     | 
| 
       474 
488 
     | 
    
         
             
                    self._message = " ".join(filter(None, [text, info, details]))
         
     | 
| 
       475 
489 
     | 
    
         
             
                    self.setText(text)
         
     | 
| 
       476 
490 
     | 
    
         
             
                    self.setInformativeText(info)
         
     | 
| 
       477 
491 
     | 
    
         
             
                    self.setDetailedText(details)
         
     | 
| 
       478 
     | 
    
         
            -
                    return
         
     | 
| 
       479 
492 
     | 
    
         | 
| 
       480 
493 
     | 
    
         
             
                def setException(self, exception: Exception) -> None:
         
     | 
| 
       481 
494 
     | 
    
         
             
                    """Add exception details."""
         
     | 
| 
       482 
495 
     | 
    
         
             
                    info = self.informativeText()
         
     | 
| 
       483 
496 
     | 
    
         
             
                    text = f"<b>{type(exception).__name__}</b>: {exception!s}"
         
     | 
| 
       484 
497 
     | 
    
         
             
                    self.setInformativeText(f"{info}<br>{text}" if info else text)
         
     | 
| 
       485 
     | 
    
         
            -
                    return
         
     | 
| 
       486 
498 
     | 
    
         | 
| 
       487 
499 
     | 
    
         
             
                def setAlertType(self, level: int, isYesNo: bool) -> None:
         
     | 
| 
       488 
500 
     | 
    
         
             
                    """Set the type of alert and whether the dialog should have
         
     | 
| 
       489 
501 
     | 
    
         
             
                    Yes/No buttons or just an Ok button.
         
     | 
| 
       490 
502 
     | 
    
         
             
                    """
         
     | 
| 
       491 
503 
     | 
    
         
             
                    if isYesNo:
         
     | 
| 
       492 
     | 
    
         
            -
                        self. 
     | 
| 
      
 504 
     | 
    
         
            +
                        self._btnYes = self._theme.getStandardButton(nwStandardButton.YES, self)
         
     | 
| 
      
 505 
     | 
    
         
            +
                        self._btnYes.clicked.connect(self._onAccept)
         
     | 
| 
      
 506 
     | 
    
         
            +
                        self._btnNo  = self._theme.getStandardButton(nwStandardButton.NO, self)
         
     | 
| 
      
 507 
     | 
    
         
            +
                        self._btnNo.clicked.connect(self._onReject)
         
     | 
| 
      
 508 
     | 
    
         
            +
                        self.addButton(self._btnYes, QMessageBox.ButtonRole.YesRole)
         
     | 
| 
      
 509 
     | 
    
         
            +
                        self.addButton(self._btnNo, QMessageBox.ButtonRole.NoRole)
         
     | 
| 
       493 
510 
     | 
    
         
             
                    else:
         
     | 
| 
       494 
     | 
    
         
            -
                        self. 
     | 
| 
       495 
     | 
    
         
            -
             
     | 
| 
      
 511 
     | 
    
         
            +
                        self._btnOk = self._theme.getStandardButton(nwStandardButton.OK, self)
         
     | 
| 
      
 512 
     | 
    
         
            +
                        self._btnOk.clicked.connect(self._onAccept)
         
     | 
| 
      
 513 
     | 
    
         
            +
                        self.addButton(self._btnOk, QMessageBox.ButtonRole.AcceptRole)
         
     | 
| 
      
 514 
     | 
    
         
            +
             
     | 
| 
      
 515 
     | 
    
         
            +
                    pSz = 2*self._theme.fontPixelSize
         
     | 
| 
       496 
516 
     | 
    
         
             
                    if level == self.INFO:
         
     | 
| 
       497 
     | 
    
         
            -
                        self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz), " 
     | 
| 
      
 517 
     | 
    
         
            +
                        self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz), "info"))
         
     | 
| 
       498 
518 
     | 
    
         
             
                        self.setWindowTitle(self.tr("Information"))
         
     | 
| 
       499 
519 
     | 
    
         
             
                    elif level == self.WARN:
         
     | 
| 
       500 
     | 
    
         
            -
                        self.setIconPixmap(self._theme.getPixmap("alert_warn", (pSz, pSz), " 
     | 
| 
      
 520 
     | 
    
         
            +
                        self.setIconPixmap(self._theme.getPixmap("alert_warn", (pSz, pSz), "warning"))
         
     | 
| 
       501 
521 
     | 
    
         
             
                        self.setWindowTitle(self.tr("Warning"))
         
     | 
| 
       502 
522 
     | 
    
         
             
                    elif level == self.ERROR:
         
     | 
| 
       503 
     | 
    
         
            -
                        self.setIconPixmap(self._theme.getPixmap("alert_error", (pSz, pSz), " 
     | 
| 
      
 523 
     | 
    
         
            +
                        self.setIconPixmap(self._theme.getPixmap("alert_error", (pSz, pSz), "error"))
         
     | 
| 
       504 
524 
     | 
    
         
             
                        self.setWindowTitle(self.tr("Error"))
         
     | 
| 
       505 
525 
     | 
    
         
             
                    elif level == self.ASK:
         
     | 
| 
       506 
     | 
    
         
            -
                        self.setIconPixmap(self._theme.getPixmap("alert_question", (pSz, pSz), " 
     | 
| 
      
 526 
     | 
    
         
            +
                        self.setIconPixmap(self._theme.getPixmap("alert_question", (pSz, pSz), "info"))
         
     | 
| 
       507 
527 
     | 
    
         
             
                        self.setWindowTitle(self.tr("Question"))
         
     | 
| 
       508 
     | 
    
         
            -
             
     | 
| 
      
 528 
     | 
    
         
            +
             
     | 
| 
      
 529 
     | 
    
         
            +
                @pyqtSlot()
         
     | 
| 
      
 530 
     | 
    
         
            +
                def _onAccept(self) -> None:
         
     | 
| 
      
 531 
     | 
    
         
            +
                    """Process accepted state."""
         
     | 
| 
      
 532 
     | 
    
         
            +
                    self._state = True
         
     | 
| 
      
 533 
     | 
    
         
            +
                    self.close()
         
     | 
| 
      
 534 
     | 
    
         
            +
             
     | 
| 
      
 535 
     | 
    
         
            +
                @pyqtSlot()
         
     | 
| 
      
 536 
     | 
    
         
            +
                def _onReject(self) -> None:
         
     | 
| 
      
 537 
     | 
    
         
            +
                    """Process rejected state."""
         
     | 
| 
      
 538 
     | 
    
         
            +
                    self._state = False
         
     | 
| 
      
 539 
     | 
    
         
            +
                    self.close()
         
     | 
    
        novelwriter/splash.py
    CHANGED
    
    | 
         @@ -20,7 +20,7 @@ General Public License for more details. 
     | 
|
| 
       20 
20 
     | 
    
         | 
| 
       21 
21 
     | 
    
         
             
            You should have received a copy of the GNU General Public License
         
     | 
| 
       22 
22 
     | 
    
         
             
            along with this program. If not, see <https://www.gnu.org/licenses/>.
         
     | 
| 
       23 
     | 
    
         
            -
            """
         
     | 
| 
      
 23 
     | 
    
         
            +
            """  # noqa
         
     | 
| 
       24 
24 
     | 
    
         
             
            from __future__ import annotations
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
26 
     | 
    
         
             
            import logging
         
     | 
| 
         @@ -38,6 +38,14 @@ SPLASH_IMG = Path(__file__).parent / "assets" / "images" / "splash.png" 
     | 
|
| 
       38 
38 
     | 
    
         | 
| 
       39 
39 
     | 
    
         | 
| 
       40 
40 
     | 
    
         
             
            class NSplashScreen(QSplashScreen):
         
     | 
| 
      
 41 
     | 
    
         
            +
                """GUI: App Launch Splash Screen.
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                A small splash screen that is shown as novelWriter starts up. Its
         
     | 
| 
      
 44 
     | 
    
         
            +
                primary purpose is to provide user feedback that the app is being
         
     | 
| 
      
 45 
     | 
    
         
            +
                initiated when there are delays in the process while Qt waits for
         
     | 
| 
      
 46 
     | 
    
         
            +
                responses from the OS, or has to load particularly large data sets
         
     | 
| 
      
 47 
     | 
    
         
            +
                like when the system has a lot of fonts installed.
         
     | 
| 
      
 48 
     | 
    
         
            +
                """
         
     | 
| 
       41 
49 
     | 
    
         | 
| 
       42 
50 
     | 
    
         
             
                __slots__ = ("_color", "_rect", "_text")
         
     | 
| 
       43 
51 
     | 
    
         | 
| 
         @@ -52,17 +60,14 @@ class NSplashScreen(QSplashScreen): 
     | 
|
| 
       52 
60 
     | 
    
         
             
                    self._color = QColor(26, 52, 78)
         
     | 
| 
       53 
61 
     | 
    
         
             
                    self._rect = QRect(144, 110, 440, 30)
         
     | 
| 
       54 
62 
     | 
    
         
             
                    self._text = ""
         
     | 
| 
       55 
     | 
    
         
            -
                    return
         
     | 
| 
       56 
63 
     | 
    
         | 
| 
       57 
64 
     | 
    
         
             
                def __del__(self) -> None:  # pragma: no cover
         
     | 
| 
       58 
65 
     | 
    
         
             
                    logger.debug("Delete: NSplashScreen")
         
     | 
| 
       59 
     | 
    
         
            -
                    return
         
     | 
| 
       60 
66 
     | 
    
         | 
| 
       61 
67 
     | 
    
         
             
                def drawContents(self, painter: QPainter) -> None:
         
     | 
| 
       62 
68 
     | 
    
         
             
                    """Draw the text message."""
         
     | 
| 
       63 
69 
     | 
    
         
             
                    painter.setPen(self._color)
         
     | 
| 
       64 
70 
     | 
    
         
             
                    painter.drawText(self._rect, Qt.AlignmentFlag.AlignLeft, self._text)
         
     | 
| 
       65 
     | 
    
         
            -
                    return
         
     | 
| 
       66 
71 
     | 
    
         | 
| 
       67 
72 
     | 
    
         
             
                def showStatus(self, message: str) -> None:
         
     | 
| 
       68 
73 
     | 
    
         
             
                    """Update the status message."""
         
     | 
| 
         @@ -71,4 +76,3 @@ class NSplashScreen(QSplashScreen): 
     | 
|
| 
       71 
76 
     | 
    
         
             
                    if message:
         
     | 
| 
       72 
77 
     | 
    
         
             
                        logger.info("[Splash] %s", message)
         
     | 
| 
       73 
78 
     | 
    
         
             
                    sleep(0.025)
         
     | 
| 
       74 
     | 
    
         
            -
                    return
         
     | 
    
        novelwriter/text/comments.py
    CHANGED
    
    | 
         @@ -21,7 +21,7 @@ General Public License for more details. 
     | 
|
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
            You should have received a copy of the GNU General Public License
         
     | 
| 
       23 
23 
     | 
    
         
             
            along with this program. If not, see <https://www.gnu.org/licenses/>.
         
     | 
| 
       24 
     | 
    
         
            -
            """
         
     | 
| 
      
 24 
     | 
    
         
            +
            """  # noqa
         
     | 
| 
       25 
25 
     | 
    
         
             
            from __future__ import annotations
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
27 
     | 
    
         
             
            from novelwriter.enum import nwComment
         
     | 
    
        novelwriter/text/counting.py
    CHANGED
    
    | 
         @@ -22,7 +22,7 @@ General Public License for more details. 
     | 
|
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
            You should have received a copy of the GNU General Public License
         
     | 
| 
       24 
24 
     | 
    
         
             
            along with this program. If not, see <https://www.gnu.org/licenses/>.
         
     | 
| 
       25 
     | 
    
         
            -
            """
         
     | 
| 
      
 25 
     | 
    
         
            +
            """  # noqa
         
     | 
| 
       26 
26 
     | 
    
         
             
            from __future__ import annotations
         
     | 
| 
       27 
27 
     | 
    
         | 
| 
       28 
28 
     | 
    
         
             
            import re
         
     | 
| 
         @@ -74,9 +74,11 @@ def preProcessText(text: str, keepHeaders: bool = True) -> list[str]: 
     | 
|
| 
       74 
74 
     | 
    
         | 
| 
       75 
75 
     | 
    
         | 
| 
       76 
76 
     | 
    
         
             
            def standardCounter(text: str) -> tuple[int, int, int]:
         
     | 
| 
       77 
     | 
    
         
            -
                """ 
     | 
| 
       78 
     | 
    
         
            -
             
     | 
| 
       79 
     | 
    
         
            -
                 
     | 
| 
      
 77 
     | 
    
         
            +
                """Return a standard count.
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                A counter that counts paragraphs, words and characters. This is the
         
     | 
| 
      
 80 
     | 
    
         
            +
                standard counter that includes headings in the word and character
         
     | 
| 
      
 81 
     | 
    
         
            +
                counts.
         
     | 
| 
       80 
82 
     | 
    
         
             
                """
         
     | 
| 
       81 
83 
     | 
    
         
             
                cCount = 0
         
     | 
| 
       82 
84 
     | 
    
         
             
                wCount = 0
         
     | 
| 
         @@ -124,7 +126,9 @@ def standardCounter(text: str) -> tuple[int, int, int]: 
     | 
|
| 
       124 
126 
     | 
    
         | 
| 
       125 
127 
     | 
    
         | 
| 
       126 
128 
     | 
    
         
             
            def bodyTextCounter(text: str) -> tuple[int, int, int]:
         
     | 
| 
       127 
     | 
    
         
            -
                """ 
     | 
| 
      
 129 
     | 
    
         
            +
                """Return a body text count.
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                A counter that counts body text words, characters, and characters
         
     | 
| 
       128 
132 
     | 
    
         
             
                without white spaces.
         
     | 
| 
       129 
133 
     | 
    
         
             
                """
         
     | 
| 
       130 
134 
     | 
    
         
             
                wCount = 0
         
     | 
    
        novelwriter/text/patterns.py
    CHANGED
    
    | 
         @@ -21,7 +21,7 @@ General Public License for more details. 
     | 
|
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
            You should have received a copy of the GNU General Public License
         
     | 
| 
       23 
23 
     | 
    
         
             
            along with this program. If not, see <https://www.gnu.org/licenses/>.
         
     | 
| 
       24 
     | 
    
         
            -
            """
         
     | 
| 
      
 24 
     | 
    
         
            +
            """  # noqa
         
     | 
| 
       25 
25 
     | 
    
         
             
            from __future__ import annotations
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
27 
     | 
    
         
             
            import re
         
     | 
| 
         @@ -32,18 +32,20 @@ from novelwriter.constants import nwRegEx, nwUnicode 
     | 
|
| 
       32 
32 
     | 
    
         | 
| 
       33 
33 
     | 
    
         | 
| 
       34 
34 
     | 
    
         
             
            class RegExPatterns:
         
     | 
| 
      
 35 
     | 
    
         
            +
                """Compiled RegEx Patterns."""
         
     | 
| 
       35 
36 
     | 
    
         | 
| 
       36 
37 
     | 
    
         
             
                AMBIGUOUS = (nwUnicode.U_APOS, nwUnicode.U_RSQUO)
         
     | 
| 
       37 
38 
     | 
    
         | 
| 
       38 
39 
     | 
    
         
             
                # Static RegExes
         
     | 
| 
       39 
40 
     | 
    
         
             
                _rxUrl     = re.compile(nwRegEx.URL, re.ASCII)
         
     | 
| 
       40 
     | 
    
         
            -
                _rxWords   = re.compile(nwRegEx.WORDS 
     | 
| 
       41 
     | 
    
         
            -
                _rxBreak   = re.compile(nwRegEx.BREAK 
     | 
| 
       42 
     | 
    
         
            -
                _rxItalic  = re.compile(nwRegEx.FMT_EI 
     | 
| 
       43 
     | 
    
         
            -
                _rxBold    = re.compile(nwRegEx.FMT_EB 
     | 
| 
       44 
     | 
    
         
            -
                _rxStrike  = re.compile(nwRegEx.FMT_ST 
     | 
| 
       45 
     | 
    
         
            -
                 
     | 
| 
       46 
     | 
    
         
            -
                 
     | 
| 
      
 41 
     | 
    
         
            +
                _rxWords   = re.compile(nwRegEx.WORDS)
         
     | 
| 
      
 42 
     | 
    
         
            +
                _rxBreak   = re.compile(nwRegEx.BREAK)
         
     | 
| 
      
 43 
     | 
    
         
            +
                _rxItalic  = re.compile(nwRegEx.FMT_EI)
         
     | 
| 
      
 44 
     | 
    
         
            +
                _rxBold    = re.compile(nwRegEx.FMT_EB)
         
     | 
| 
      
 45 
     | 
    
         
            +
                _rxStrike  = re.compile(nwRegEx.FMT_ST)
         
     | 
| 
      
 46 
     | 
    
         
            +
                _rxMark    = re.compile(nwRegEx.FMT_HL)
         
     | 
| 
      
 47 
     | 
    
         
            +
                _rxSCPlain = re.compile(nwRegEx.FMT_SC)
         
     | 
| 
      
 48 
     | 
    
         
            +
                _rxSCValue = re.compile(nwRegEx.FMT_SV)
         
     | 
| 
       47 
49 
     | 
    
         | 
| 
       48 
50 
     | 
    
         
             
                @property
         
     | 
| 
       49 
51 
     | 
    
         
             
                def url(self) -> re.Pattern:
         
     | 
| 
         @@ -75,6 +77,11 @@ class RegExPatterns: 
     | 
|
| 
       75 
77 
     | 
    
         
             
                    """Markdown strikethrough style."""
         
     | 
| 
       76 
78 
     | 
    
         
             
                    return self._rxStrike
         
     | 
| 
       77 
79 
     | 
    
         | 
| 
      
 80 
     | 
    
         
            +
                @property
         
     | 
| 
      
 81 
     | 
    
         
            +
                def markdownMark(self) -> re.Pattern:
         
     | 
| 
      
 82 
     | 
    
         
            +
                    """Markdown highlight style."""
         
     | 
| 
      
 83 
     | 
    
         
            +
                    return self._rxMark
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
       78 
85 
     | 
    
         
             
                @property
         
     | 
| 
       79 
86 
     | 
    
         
             
                def shortcodePlain(self) -> re.Pattern:
         
     | 
| 
       80 
87 
     | 
    
         
             
                    """Plain shortcode style."""
         
     | 
| 
         @@ -108,7 +115,7 @@ class RegExPatterns: 
     | 
|
| 
       108 
115 
     | 
    
         
             
                                rx.append(f"(?:{qO}[^{qO}]+{qC})")
         
     | 
| 
       109 
116 
     | 
    
         
             
                            if CONFIG.allowOpenDial:
         
     | 
| 
       110 
117 
     | 
    
         
             
                                rx.append(f"(?:{qO}.+?$)")
         
     | 
| 
       111 
     | 
    
         
            -
                        return re.compile("|".join(rx) 
     | 
| 
      
 118 
     | 
    
         
            +
                        return re.compile("|".join(rx))
         
     | 
| 
       112 
119 
     | 
    
         
             
                    return None
         
     | 
| 
       113 
120 
     | 
    
         | 
| 
       114 
121 
     | 
    
         
             
                @property
         
     | 
| 
         @@ -118,7 +125,7 @@ class RegExPatterns: 
     | 
|
| 
       118 
125 
     | 
    
         
             
                        qO = re.escape(compact(CONFIG.altDialogOpen))
         
     | 
| 
       119 
126 
     | 
    
         
             
                        qC = re.escape(compact(CONFIG.altDialogClose))
         
     | 
| 
       120 
127 
     | 
    
         
             
                        qB = r"\B" if (qO == qC or qC in self.AMBIGUOUS) else ""
         
     | 
| 
       121 
     | 
    
         
            -
                        return re.compile(f"{qO}.*?{qC}{qB}" 
     | 
| 
      
 128 
     | 
    
         
            +
                        return re.compile(f"{qO}.*?{qC}{qB}")
         
     | 
| 
       122 
129 
     | 
    
         
             
                    return None
         
     | 
| 
       123 
130 
     | 
    
         | 
| 
       124 
131 
     | 
    
         | 
| 
         @@ -126,6 +133,7 @@ REGEX_PATTERNS = RegExPatterns() 
     | 
|
| 
       126 
133 
     | 
    
         | 
| 
       127 
134 
     | 
    
         | 
| 
       128 
135 
     | 
    
         
             
            class DialogParser:
         
     | 
| 
      
 136 
     | 
    
         
            +
                """A callable parser for finding dialog regions in text."""
         
     | 
| 
       129 
137 
     | 
    
         | 
| 
       130 
138 
     | 
    
         
             
                __slots__ = (
         
     | 
| 
       131 
139 
     | 
    
         
             
                    "_alternate", "_breakD", "_breakQ", "_dialog", "_enabled", "_mode",
         
     | 
| 
         @@ -141,7 +149,6 @@ class DialogParser: 
     | 
|
| 
       141 
149 
     | 
    
         
             
                    self._breakD = None
         
     | 
| 
       142 
150 
     | 
    
         
             
                    self._breakQ = None
         
     | 
| 
       143 
151 
     | 
    
         
             
                    self._mode = ""
         
     | 
| 
       144 
     | 
    
         
            -
                    return
         
     | 
| 
       145 
152 
     | 
    
         | 
| 
       146 
153 
     | 
    
         
             
                @property
         
     | 
| 
       147 
154 
     | 
    
         
             
                def enabled(self) -> bool:
         
     | 
| 
         @@ -163,13 +170,11 @@ class DialogParser: 
     | 
|
| 
       163 
170 
     | 
    
         
             
                    # Build narrator break RegExes
         
     | 
| 
       164 
171 
     | 
    
         
             
                    if narrator := CONFIG.narratorBreak.strip()[:1]:
         
     | 
| 
       165 
172 
     | 
    
         
             
                        punct = re.escape(".,:;!?")
         
     | 
| 
       166 
     | 
    
         
            -
                        self._breakD = re.compile(f"{narrator}.*?(?:{narrator}[{punct}]?|$)" 
     | 
| 
       167 
     | 
    
         
            -
                        self._breakQ = re.compile(f"{narrator}.*?(?:{narrator}[{punct}]?)" 
     | 
| 
      
 173 
     | 
    
         
            +
                        self._breakD = re.compile(f"{narrator}.*?(?:{narrator}[{punct}]?|$)")
         
     | 
| 
      
 174 
     | 
    
         
            +
                        self._breakQ = re.compile(f"{narrator}.*?(?:{narrator}[{punct}]?)")
         
     | 
| 
       168 
175 
     | 
    
         
             
                        self._narrator = narrator
         
     | 
| 
       169 
176 
     | 
    
         
             
                        self._mode = f" {narrator}"
         
     | 
| 
       170 
177 
     | 
    
         | 
| 
       171 
     | 
    
         
            -
                    return
         
     | 
| 
       172 
     | 
    
         
            -
             
     | 
| 
       173 
178 
     | 
    
         
             
                def __call__(self, text: str) -> list[tuple[int, int]]:
         
     | 
| 
       174 
179 
     | 
    
         
             
                    """Caller wrapper for dialogue processing."""
         
     | 
| 
       175 
180 
     | 
    
         
             
                    temp: list[int] = []
         
     | 
| 
         @@ -20,7 +20,7 @@ General Public License for more details. 
     | 
|
| 
       20 
20 
     | 
    
         | 
| 
       21 
21 
     | 
    
         
             
            You should have received a copy of the GNU General Public License
         
     | 
| 
       22 
22 
     | 
    
         
             
            along with this program. If not, see <https://www.gnu.org/licenses/>.
         
     | 
| 
       23 
     | 
    
         
            -
            """
         
     | 
| 
      
 23 
     | 
    
         
            +
            """  # noqa
         
     | 
| 
       24 
24 
     | 
    
         
             
            from __future__ import annotations
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
26 
     | 
    
         
             
            import logging
         
     | 
| 
         @@ -37,14 +37,21 @@ from PyQt6.QtWidgets import ( 
     | 
|
| 
       37 
37 
     | 
    
         | 
| 
       38 
38 
     | 
    
         
             
            from novelwriter import CONFIG, SHARED
         
     | 
| 
       39 
39 
     | 
    
         
             
            from novelwriter.common import formatFileFilter, formatInt, getFileSize, openExternalPath
         
     | 
| 
      
 40 
     | 
    
         
            +
            from novelwriter.enum import nwStandardButton
         
     | 
| 
       40 
41 
     | 
    
         
             
            from novelwriter.error import formatException
         
     | 
| 
       41 
42 
     | 
    
         
             
            from novelwriter.extensions.modified import NIconToolButton, NNonBlockingDialog
         
     | 
| 
       42 
     | 
    
         
            -
            from novelwriter.types import  
     | 
| 
      
 43 
     | 
    
         
            +
            from novelwriter.types import QtHexArgb, QtRoleDestruct
         
     | 
| 
       43 
44 
     | 
    
         | 
| 
       44 
45 
     | 
    
         
             
            logger = logging.getLogger(__name__)
         
     | 
| 
       45 
46 
     | 
    
         | 
| 
       46 
47 
     | 
    
         | 
| 
       47 
48 
     | 
    
         
             
            class GuiDictionaries(NNonBlockingDialog):
         
     | 
| 
      
 49 
     | 
    
         
            +
                """GUI: Spell Check Dictionary Tool.
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                A helper tool for downloading and extracting dictionaries to a
         
     | 
| 
      
 52 
     | 
    
         
            +
                location where Enchant can find them. This tool is only needed on
         
     | 
| 
      
 53 
     | 
    
         
            +
                Windows.
         
     | 
| 
      
 54 
     | 
    
         
            +
                """
         
     | 
| 
       48 
55 
     | 
    
         | 
| 
       49 
56 
     | 
    
         
             
                def __init__(self, parent: QWidget) -> None:
         
     | 
| 
       50 
57 
     | 
    
         
             
                    super().__init__(parent=parent)
         
     | 
| 
         @@ -72,10 +79,10 @@ class GuiDictionaries(NNonBlockingDialog): 
     | 
|
| 
       72 
79 
     | 
    
         
             
                    self.huInfo.setOpenExternalLinks(True)
         
     | 
| 
       73 
80 
     | 
    
         
             
                    self.huInfo.setWordWrap(True)
         
     | 
| 
       74 
81 
     | 
    
         
             
                    self.huInput = QLineEdit(self)
         
     | 
| 
       75 
     | 
    
         
            -
                    self.huBrowse = NIconToolButton(self, iSz, "browse")
         
     | 
| 
      
 82 
     | 
    
         
            +
                    self.huBrowse = NIconToolButton(self, iSz, "browse", "systemio")
         
     | 
| 
       76 
83 
     | 
    
         
             
                    self.huBrowse.clicked.connect(self._doBrowseHunspell)
         
     | 
| 
       77 
84 
     | 
    
         
             
                    self.huImport = QPushButton(self.tr("Add Dictionary"), self)
         
     | 
| 
       78 
     | 
    
         
            -
                    self.huImport.setIcon(SHARED.theme.getIcon("add", " 
     | 
| 
      
 85 
     | 
    
         
            +
                    self.huImport.setIcon(SHARED.theme.getIcon("add", "add"))
         
     | 
| 
       79 
86 
     | 
    
         
             
                    self.huImport.clicked.connect(self._doImportHunspell)
         
     | 
| 
       80 
87 
     | 
    
         | 
| 
       81 
88 
     | 
    
         
             
                    self.huPathBox = QHBoxLayout()
         
     | 
| 
         @@ -90,7 +97,7 @@ class GuiDictionaries(NNonBlockingDialog): 
     | 
|
| 
       90 
97 
     | 
    
         
             
                    self.inInfo = QLabel(self.tr("Dictionary install location"), self)
         
     | 
| 
       91 
98 
     | 
    
         
             
                    self.inPath = QLineEdit(self)
         
     | 
| 
       92 
99 
     | 
    
         
             
                    self.inPath.setReadOnly(True)
         
     | 
| 
       93 
     | 
    
         
            -
                    self.inBrowse = NIconToolButton(self, iSz, "browse")
         
     | 
| 
      
 100 
     | 
    
         
            +
                    self.inBrowse = NIconToolButton(self, iSz, "browse", "systemio")
         
     | 
| 
       94 
101 
     | 
    
         
             
                    self.inBrowse.clicked.connect(self._doOpenInstallLocation)
         
     | 
| 
       95 
102 
     | 
    
         | 
| 
       96 
103 
     | 
    
         
             
                    self.inBox = QHBoxLayout()
         
     | 
| 
         @@ -104,8 +111,11 @@ class GuiDictionaries(NNonBlockingDialog): 
     | 
|
| 
       104 
111 
     | 
    
         
             
                    self.infoBox.setFrameStyle(QFrame.Shape.NoFrame)
         
     | 
| 
       105 
112 
     | 
    
         | 
| 
       106 
113 
     | 
    
         
             
                    # Buttons
         
     | 
| 
       107 
     | 
    
         
            -
                    self. 
     | 
| 
       108 
     | 
    
         
            -
                    self. 
     | 
| 
      
 114 
     | 
    
         
            +
                    self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self)
         
     | 
| 
      
 115 
     | 
    
         
            +
                    self.btnClose.clicked.connect(self.reject)
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                    self.btnBox = QDialogButtonBox(self)
         
     | 
| 
      
 118 
     | 
    
         
            +
                    self.btnBox.addButton(self.btnClose, QtRoleDestruct)
         
     | 
| 
       109 
119 
     | 
    
         | 
| 
       110 
120 
     | 
    
         
             
                    # Assemble
         
     | 
| 
       111 
121 
     | 
    
         
             
                    self.outerBox = QVBoxLayout()
         
     | 
| 
         @@ -117,17 +127,14 @@ class GuiDictionaries(NNonBlockingDialog): 
     | 
|
| 
       117 
127 
     | 
    
         
             
                    self.outerBox.addLayout(self.inBox, 0)
         
     | 
| 
       118 
128 
     | 
    
         
             
                    self.outerBox.addWidget(self.infoBox, 1)
         
     | 
| 
       119 
129 
     | 
    
         
             
                    self.outerBox.addSpacing(8)
         
     | 
| 
       120 
     | 
    
         
            -
                    self.outerBox.addWidget(self. 
     | 
| 
      
 130 
     | 
    
         
            +
                    self.outerBox.addWidget(self.btnBox, 0)
         
     | 
| 
       121 
131 
     | 
    
         | 
| 
       122 
132 
     | 
    
         
             
                    self.setLayout(self.outerBox)
         
     | 
| 
       123 
133 
     | 
    
         | 
| 
       124 
134 
     | 
    
         
             
                    logger.debug("Ready: GuiDictionaries")
         
     | 
| 
       125 
135 
     | 
    
         | 
| 
       126 
     | 
    
         
            -
                    return
         
     | 
| 
       127 
     | 
    
         
            -
             
     | 
| 
       128 
136 
     | 
    
         
             
                def __del__(self) -> None:  # pragma: no cover
         
     | 
| 
       129 
137 
     | 
    
         
             
                    logger.debug("Delete: GuiDictionaries")
         
     | 
| 
       130 
     | 
    
         
            -
                    return
         
     | 
| 
       131 
138 
     | 
    
         | 
| 
       132 
139 
     | 
    
         
             
                def initDialog(self) -> bool:
         
     | 
| 
       133 
140 
     | 
    
         
             
                    """Prepare and check that we can proceed."""
         
     | 
| 
         @@ -164,7 +171,6 @@ class GuiDictionaries(NNonBlockingDialog): 
     | 
|
| 
       164 
171 
     | 
    
         
             
                    """Capture the user closing the window."""
         
     | 
| 
       165 
172 
     | 
    
         
             
                    event.accept()
         
     | 
| 
       166 
173 
     | 
    
         
             
                    self.softDelete()
         
     | 
| 
       167 
     | 
    
         
            -
                    return
         
     | 
| 
       168 
174 
     | 
    
         | 
| 
       169 
175 
     | 
    
         
             
                ##
         
     | 
| 
       170 
176 
     | 
    
         
             
                #  Private Slots
         
     | 
| 
         @@ -182,7 +188,6 @@ class GuiDictionaries(NNonBlockingDialog): 
     | 
|
| 
       182 
188 
     | 
    
         
             
                    if soxFile:
         
     | 
| 
       183 
189 
     | 
    
         
             
                        path = Path(soxFile).absolute()
         
     | 
| 
       184 
190 
     | 
    
         
             
                        self.huInput.setText(str(path))
         
     | 
| 
       185 
     | 
    
         
            -
                    return
         
     | 
| 
       186 
191 
     | 
    
         | 
| 
       187 
192 
     | 
    
         
             
                @pyqtSlot()
         
     | 
| 
       188 
193 
     | 
    
         
             
                def _doImportHunspell(self) -> None:
         
     | 
| 
         @@ -202,14 +207,12 @@ class GuiDictionaries(NNonBlockingDialog): 
     | 
|
| 
       202 
207 
     | 
    
         
             
                                self._appendLog(formatException(exc), err=True)
         
     | 
| 
       203 
208 
     | 
    
         
             
                        else:
         
     | 
| 
       204 
209 
     | 
    
         
             
                            self._appendLog(procErr, err=True)
         
     | 
| 
       205 
     | 
    
         
            -
                    return
         
     | 
| 
       206 
210 
     | 
    
         | 
| 
       207 
211 
     | 
    
         
             
                @pyqtSlot()
         
     | 
| 
       208 
212 
     | 
    
         
             
                def _doOpenInstallLocation(self) -> None:
         
     | 
| 
       209 
213 
     | 
    
         
             
                    """Open the dictionary folder."""
         
     | 
| 
       210 
214 
     | 
    
         
             
                    if not openExternalPath(Path(self.inPath.text())):
         
     | 
| 
       211 
215 
     | 
    
         
             
                        SHARED.error("Path not found.")
         
     | 
| 
       212 
     | 
    
         
            -
                    return
         
     | 
| 
       213 
216 
     | 
    
         | 
| 
       214 
217 
     | 
    
         
             
                ##
         
     | 
| 
       215 
218 
     | 
    
         
             
                #  Internal Functions
         
     | 
| 
         @@ -247,4 +250,3 @@ class GuiDictionaries(NNonBlockingDialog): 
     | 
|
| 
       247 
250 
     | 
    
         
             
                    cursor.movePosition(QTextCursor.MoveOperation.End)
         
     | 
| 
       248 
251 
     | 
    
         
             
                    cursor.deleteChar()
         
     | 
| 
       249 
252 
     | 
    
         
             
                    self.infoBox.setTextCursor(cursor)
         
     | 
| 
       250 
     | 
    
         
            -
                    return
         
     |