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/core/projectdata.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
         
     | 
| 
         @@ -41,7 +41,7 @@ logger = logging.getLogger(__name__) 
     | 
|
| 
       41 
41 
     | 
    
         | 
| 
       42 
42 
     | 
    
         | 
| 
       43 
43 
     | 
    
         
             
            class NWProjectData:
         
     | 
| 
       44 
     | 
    
         
            -
                """Core: Project Data Class
         
     | 
| 
      
 44 
     | 
    
         
            +
                """Core: Project Data Class.
         
     | 
| 
       45 
45 
     | 
    
         | 
| 
       46 
46 
     | 
    
         
             
                The class holds all project data from the main XML file, aside from
         
     | 
| 
       47 
47 
     | 
    
         
             
                the list of project items.
         
     | 
| 
         @@ -86,8 +86,6 @@ class NWProjectData: 
     | 
|
| 
       86 
86 
     | 
    
         
             
                    self._status = NWStatus(NWStatus.STATUS)
         
     | 
| 
       87 
87 
     | 
    
         
             
                    self._import = NWStatus(NWStatus.IMPORT)
         
     | 
| 
       88 
88 
     | 
    
         | 
| 
       89 
     | 
    
         
            -
                    return
         
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
       91 
89 
     | 
    
         
             
                ##
         
     | 
| 
       92 
90 
     | 
    
         
             
                #  Properties
         
     | 
| 
       93 
91 
     | 
    
         
             
                ##
         
     | 
| 
         @@ -191,13 +189,11 @@ class NWProjectData: 
     | 
|
| 
       191 
189 
     | 
    
         
             
                    """Increment the save count by one."""
         
     | 
| 
       192 
190 
     | 
    
         
             
                    self._saveCount += 1
         
     | 
| 
       193 
191 
     | 
    
         
             
                    self._project.setProjectChanged(True)
         
     | 
| 
       194 
     | 
    
         
            -
                    return
         
     | 
| 
       195 
192 
     | 
    
         | 
| 
       196 
193 
     | 
    
         
             
                def incAutoCount(self) -> None:
         
     | 
| 
       197 
194 
     | 
    
         
             
                    """Increment the auto save count by one."""
         
     | 
| 
       198 
195 
     | 
    
         
             
                    self._autoCount += 1
         
     | 
| 
       199 
196 
     | 
    
         
             
                    self._project.setProjectChanged(True)
         
     | 
| 
       200 
     | 
    
         
            -
                    return
         
     | 
| 
       201 
197 
     | 
    
         | 
| 
       202 
198 
     | 
    
         
             
                ##
         
     | 
| 
       203 
199 
     | 
    
         
             
                #  Getters
         
     | 
| 
         @@ -219,67 +215,57 @@ class NWProjectData: 
     | 
|
| 
       219 
215 
     | 
    
         
             
                    elif value != self._uuid:
         
     | 
| 
       220 
216 
     | 
    
         
             
                        self._uuid = value
         
     | 
| 
       221 
217 
     | 
    
         
             
                        self._project.setProjectChanged(True)
         
     | 
| 
       222 
     | 
    
         
            -
                    return
         
     | 
| 
       223 
218 
     | 
    
         | 
| 
       224 
219 
     | 
    
         
             
                def setName(self, value: str | None) -> None:
         
     | 
| 
       225 
220 
     | 
    
         
             
                    """Set a new project name."""
         
     | 
| 
       226 
221 
     | 
    
         
             
                    if value != self._name:
         
     | 
| 
       227 
222 
     | 
    
         
             
                        self._name = simplified(str(value or ""))
         
     | 
| 
       228 
223 
     | 
    
         
             
                        self._project.setProjectChanged(True)
         
     | 
| 
       229 
     | 
    
         
            -
                    return
         
     | 
| 
       230 
224 
     | 
    
         | 
| 
       231 
225 
     | 
    
         
             
                def setAuthor(self, value: str | None) -> None:
         
     | 
| 
       232 
226 
     | 
    
         
             
                    """Set the author value."""
         
     | 
| 
       233 
227 
     | 
    
         
             
                    if value != self._author:
         
     | 
| 
       234 
228 
     | 
    
         
             
                        self._author = simplified(str(value or ""))
         
     | 
| 
       235 
229 
     | 
    
         
             
                        self._project.setProjectChanged(True)
         
     | 
| 
       236 
     | 
    
         
            -
                    return
         
     | 
| 
       237 
230 
     | 
    
         | 
| 
       238 
231 
     | 
    
         
             
                def setSaveCount(self, value: Any) -> None:
         
     | 
| 
       239 
232 
     | 
    
         
             
                    """Set the save count from last session."""
         
     | 
| 
       240 
233 
     | 
    
         
             
                    self._saveCount = checkInt(value, 0)
         
     | 
| 
       241 
234 
     | 
    
         
             
                    self._project.setProjectChanged(True)
         
     | 
| 
       242 
     | 
    
         
            -
                    return
         
     | 
| 
       243 
235 
     | 
    
         | 
| 
       244 
236 
     | 
    
         
             
                def setAutoCount(self, value: Any) -> None:
         
     | 
| 
       245 
237 
     | 
    
         
             
                    """Set the auto save count from last session."""
         
     | 
| 
       246 
238 
     | 
    
         
             
                    self._autoCount = checkInt(value, 0)
         
     | 
| 
       247 
239 
     | 
    
         
             
                    self._project.setProjectChanged(True)
         
     | 
| 
       248 
     | 
    
         
            -
                    return
         
     | 
| 
       249 
240 
     | 
    
         | 
| 
       250 
241 
     | 
    
         
             
                def setEditTime(self, value: Any) -> None:
         
     | 
| 
       251 
242 
     | 
    
         
             
                    """Set the edit time from last session."""
         
     | 
| 
       252 
243 
     | 
    
         
             
                    self._editTime = checkInt(value, 0)
         
     | 
| 
       253 
244 
     | 
    
         
             
                    self._project.setProjectChanged(True)
         
     | 
| 
       254 
     | 
    
         
            -
                    return
         
     | 
| 
       255 
245 
     | 
    
         | 
| 
       256 
246 
     | 
    
         
             
                def setDoBackup(self, value: Any) -> None:
         
     | 
| 
       257 
247 
     | 
    
         
             
                    """Set the do write backup flag."""
         
     | 
| 
       258 
248 
     | 
    
         
             
                    if value != self._doBackup:
         
     | 
| 
       259 
249 
     | 
    
         
             
                        self._doBackup = checkBool(value, False)
         
     | 
| 
       260 
250 
     | 
    
         
             
                        self._project.setProjectChanged(True)
         
     | 
| 
       261 
     | 
    
         
            -
                    return
         
     | 
| 
       262 
251 
     | 
    
         | 
| 
       263 
252 
     | 
    
         
             
                def setLanguage(self, value: str | None) -> None:
         
     | 
| 
       264 
253 
     | 
    
         
             
                    """Set the project language."""
         
     | 
| 
       265 
254 
     | 
    
         
             
                    if value != self._language:
         
     | 
| 
       266 
255 
     | 
    
         
             
                        self._language = checkStringNone(value, None)
         
     | 
| 
       267 
256 
     | 
    
         
             
                        self._project.setProjectChanged(True)
         
     | 
| 
       268 
     | 
    
         
            -
                    return
         
     | 
| 
       269 
257 
     | 
    
         | 
| 
       270 
258 
     | 
    
         
             
                def setSpellCheck(self, value: Any) -> None:
         
     | 
| 
       271 
259 
     | 
    
         
             
                    """Set the spell check flag."""
         
     | 
| 
       272 
260 
     | 
    
         
             
                    if value != self._spellCheck:
         
     | 
| 
       273 
261 
     | 
    
         
             
                        self._spellCheck = checkBool(value, False)
         
     | 
| 
       274 
262 
     | 
    
         
             
                        self._project.setProjectChanged(True)
         
     | 
| 
       275 
     | 
    
         
            -
                    return
         
     | 
| 
       276 
263 
     | 
    
         | 
| 
       277 
264 
     | 
    
         
             
                def setSpellLang(self, value: str | None) -> None:
         
     | 
| 
       278 
265 
     | 
    
         
             
                    """Set the spell check language."""
         
     | 
| 
       279 
266 
     | 
    
         
             
                    if value != self._spellLang:
         
     | 
| 
       280 
267 
     | 
    
         
             
                        self._spellLang = checkStringNone(value, None)
         
     | 
| 
       281 
268 
     | 
    
         
             
                        self._project.setProjectChanged(True)
         
     | 
| 
       282 
     | 
    
         
            -
                    return
         
     | 
| 
       283 
269 
     | 
    
         | 
| 
       284 
270 
     | 
    
         
             
                def setLastHandle(self, value: str | None, component: str) -> None:
         
     | 
| 
       285 
271 
     | 
    
         
             
                    """Set a last used handle into the handle registry for a given
         
     | 
| 
         @@ -288,7 +274,6 @@ class NWProjectData: 
     | 
|
| 
       288 
274 
     | 
    
         
             
                    if isinstance(component, str):
         
     | 
| 
       289 
275 
     | 
    
         
             
                        self._lastHandle[component] = checkStringNone(value, None)
         
     | 
| 
       290 
276 
     | 
    
         
             
                        self._project.setProjectChanged(True)
         
     | 
| 
       291 
     | 
    
         
            -
                    return
         
     | 
| 
       292 
277 
     | 
    
         | 
| 
       293 
278 
     | 
    
         
             
                def setLastHandles(self, value: dict) -> None:
         
     | 
| 
       294 
279 
     | 
    
         
             
                    """Set the full last handles dictionary to a new set of values.
         
     | 
| 
         @@ -299,7 +284,6 @@ class NWProjectData: 
     | 
|
| 
       299 
284 
     | 
    
         
             
                            if key in self._lastHandle:
         
     | 
| 
       300 
285 
     | 
    
         
             
                                self._lastHandle[key] = str(entry) if isHandle(entry) else None
         
     | 
| 
       301 
286 
     | 
    
         
             
                        self._project.setProjectChanged(True)
         
     | 
| 
       302 
     | 
    
         
            -
                    return
         
     | 
| 
       303 
287 
     | 
    
         | 
| 
       304 
288 
     | 
    
         
             
                def setInitCounts(
         
     | 
| 
       305 
289 
     | 
    
         
             
                    self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None
         
     | 
| 
         @@ -321,7 +305,6 @@ class NWProjectData: 
     | 
|
| 
       321 
305 
     | 
    
         
             
                        count = checkInt(cNotes, 0)
         
     | 
| 
       322 
306 
     | 
    
         
             
                        self._initCounts[3] = count
         
     | 
| 
       323 
307 
     | 
    
         
             
                        self._currCounts[3] = count
         
     | 
| 
       324 
     | 
    
         
            -
                    return
         
     | 
| 
       325 
308 
     | 
    
         | 
| 
       326 
309 
     | 
    
         
             
                def setCurrCounts(
         
     | 
| 
       327 
310 
     | 
    
         
             
                    self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None
         
     | 
| 
         @@ -335,7 +318,6 @@ class NWProjectData: 
     | 
|
| 
       335 
318 
     | 
    
         
             
                        self._currCounts[2] = checkInt(cNovel, 0)
         
     | 
| 
       336 
319 
     | 
    
         
             
                    if cNotes is not None:
         
     | 
| 
       337 
320 
     | 
    
         
             
                        self._currCounts[3] = checkInt(cNotes, 0)
         
     | 
| 
       338 
     | 
    
         
            -
                    return
         
     | 
| 
       339 
321 
     | 
    
         | 
| 
       340 
322 
     | 
    
         
             
                def setAutoReplace(self, value: dict) -> None:
         
     | 
| 
       341 
323 
     | 
    
         
             
                    """Set the auto-replace dictionary."""
         
     | 
| 
         @@ -345,4 +327,3 @@ class NWProjectData: 
     | 
|
| 
       345 
327 
     | 
    
         
             
                            if isinstance(entry, str):
         
     | 
| 
       346 
328 
     | 
    
         
             
                                self._autoReplace[key] = simplified(entry)
         
     | 
| 
       347 
329 
     | 
    
         
             
                        self._project.setProjectChanged(True)
         
     | 
| 
       348 
     | 
    
         
            -
                    return
         
     | 
    
        novelwriter/core/projectxml.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 logging
         
     | 
| 
         @@ -46,7 +46,7 @@ if TYPE_CHECKING: 
     | 
|
| 
       46 
46 
     | 
    
         
             
            logger = logging.getLogger(__name__)
         
     | 
| 
       47 
47 
     | 
    
         | 
| 
       48 
48 
     | 
    
         
             
            FILE_VERSION = "1.5"  # The current project file format version
         
     | 
| 
       49 
     | 
    
         
            -
            FILE_REVISION = " 
     | 
| 
      
 49 
     | 
    
         
            +
            FILE_REVISION = "6"   # The current project file format revision
         
     | 
| 
       50 
50 
     | 
    
         
             
            HEX_VERSION = 0x0105
         
     | 
| 
       51 
51 
     | 
    
         | 
| 
       52 
52 
     | 
    
         
             
            NUM_VERSION = {
         
     | 
| 
         @@ -72,7 +72,7 @@ class XMLReadState(Enum): 
     | 
|
| 
       72 
72 
     | 
    
         | 
| 
       73 
73 
     | 
    
         | 
| 
       74 
74 
     | 
    
         
             
            class ProjectXMLReader:
         
     | 
| 
       75 
     | 
    
         
            -
                """Core: Project XML Reader
         
     | 
| 
      
 75 
     | 
    
         
            +
                """Core: Project XML Reader.
         
     | 
| 
       76 
76 
     | 
    
         | 
| 
       77 
77 
     | 
    
         
             
                All data is read into a NWProjectData instance, which must be
         
     | 
| 
       78 
78 
     | 
    
         
             
                provided.
         
     | 
| 
         @@ -111,6 +111,8 @@ class ProjectXMLReader: 
     | 
|
| 
       111 
111 
     | 
    
         
             
                           nodes. 2.5.
         
     | 
| 
       112 
112 
     | 
    
         
             
                    Rev 5: Added novelChars and notesChars attributes to content
         
     | 
| 
       113 
113 
     | 
    
         
             
                           node. 2.7 RC 1.
         
     | 
| 
      
 114 
     | 
    
         
            +
                    Rev 6: Replaced red, green and blue attributes with a single
         
     | 
| 
      
 115 
     | 
    
         
            +
                           color attribute. 2.8 Beta 1.
         
     | 
| 
       114 
116 
     | 
    
         
             
                """
         
     | 
| 
       115 
117 
     | 
    
         | 
| 
       116 
118 
     | 
    
         
             
                def __init__(self, path: str | Path) -> None:
         
     | 
| 
         @@ -122,7 +124,6 @@ class ProjectXMLReader: 
     | 
|
| 
       122 
124 
     | 
    
         
             
                    self._appVersion = ""
         
     | 
| 
       123 
125 
     | 
    
         
             
                    self._hexVersion = 0x0
         
     | 
| 
       124 
126 
     | 
    
         
             
                    self._timeStamp = ""
         
     | 
| 
       125 
     | 
    
         
            -
                    return
         
     | 
| 
       126 
127 
     | 
    
         | 
| 
       127 
128 
     | 
    
         
             
                ##
         
     | 
| 
       128 
129 
     | 
    
         
             
                #  Properties
         
     | 
| 
         @@ -252,8 +253,6 @@ class ProjectXMLReader: 
     | 
|
| 
       252 
253 
     | 
    
         
             
                            elif xItem.tag == "editTime":  # Moved to attribute in 1.5
         
     | 
| 
       253 
254 
     | 
    
         
             
                                data.setEditTime(xItem.text)
         
     | 
| 
       254 
255 
     | 
    
         | 
| 
       255 
     | 
    
         
            -
                    return
         
     | 
| 
       256 
     | 
    
         
            -
             
     | 
| 
       257 
256 
     | 
    
         
             
                def _parseProjectSettings(self, xSection: ET.Element, data: NWProjectData) -> None:
         
     | 
| 
       258 
257 
     | 
    
         
             
                    """Parse the settings section of the XML file."""
         
     | 
| 
       259 
258 
     | 
    
         
             
                    logger.debug("Parsing <settings> section")
         
     | 
| 
         @@ -292,8 +291,6 @@ class ProjectXMLReader: 
     | 
|
| 
       292 
291 
     | 
    
         
             
                            elif xItem.tag == "notesWordCount":  # Moved to content attribute in 1.5
         
     | 
| 
       293 
292 
     | 
    
         
             
                                data.setInitCounts(wNotes=xItem.text)
         
     | 
| 
       294 
293 
     | 
    
         | 
| 
       295 
     | 
    
         
            -
                    return
         
     | 
| 
       296 
     | 
    
         
            -
             
     | 
| 
       297 
294 
     | 
    
         
             
                def _parseProjectContent(
         
     | 
| 
       298 
295 
     | 
    
         
             
                    self, xSection: ET.Element, data: NWProjectData, content: list
         
     | 
| 
       299 
296 
     | 
    
         
             
                ) -> None:
         
     | 
| 
         @@ -354,8 +351,6 @@ class ProjectXMLReader: 
     | 
|
| 
       354 
351 
     | 
    
         
             
                            "nameAttr": name,
         
     | 
| 
       355 
352 
     | 
    
         
             
                        })
         
     | 
| 
       356 
353 
     | 
    
         | 
| 
       357 
     | 
    
         
            -
                    return
         
     | 
| 
       358 
     | 
    
         
            -
             
     | 
| 
       359 
354 
     | 
    
         
             
                def _parseProjectContentLegacy(
         
     | 
| 
       360 
355 
     | 
    
         
             
                    self, xSection: ET.Element, data: NWProjectData, content: list
         
     | 
| 
       361 
356 
     | 
    
         
             
                ) -> None:
         
     | 
| 
         @@ -432,20 +427,20 @@ class ProjectXMLReader: 
     | 
|
| 
       432 
427 
     | 
    
         
             
                            "nameAttr": name,
         
     | 
| 
       433 
428 
     | 
    
         
             
                        })
         
     | 
| 
       434 
429 
     | 
    
         | 
| 
       435 
     | 
    
         
            -
                    return
         
     | 
| 
       436 
     | 
    
         
            -
             
     | 
| 
       437 
430 
     | 
    
         
             
                def _parseStatusImport(self, xItem: ET.Element, sObject: NWStatus) -> None:
         
     | 
| 
       438 
431 
     | 
    
         
             
                    """Parse a status or importance entry."""
         
     | 
| 
       439 
432 
     | 
    
         
             
                    for xEntry in xItem:
         
     | 
| 
       440 
433 
     | 
    
         
             
                        if xEntry.tag == "entry":
         
     | 
| 
       441 
434 
     | 
    
         
             
                            key   = xEntry.attrib.get("key", None)
         
     | 
| 
       442 
     | 
    
         
            -
                            red   = checkInt(xEntry.attrib.get("red", 0), 0)
         
     | 
| 
       443 
     | 
    
         
            -
                            green = checkInt(xEntry.attrib.get("green", 0), 0)
         
     | 
| 
       444 
     | 
    
         
            -
                            blue  = checkInt(xEntry.attrib.get("blue", 0), 0)
         
     | 
| 
      
 435 
     | 
    
         
            +
                            red   = checkInt(xEntry.attrib.get("red", 0), 0)    # Removed in 1.5 R6
         
     | 
| 
      
 436 
     | 
    
         
            +
                            green = checkInt(xEntry.attrib.get("green", 0), 0)  # Removed in 1.5 R6
         
     | 
| 
      
 437 
     | 
    
         
            +
                            blue  = checkInt(xEntry.attrib.get("blue", 0), 0)   # Removed in 1.5 R6
         
     | 
| 
      
 438 
     | 
    
         
            +
                            color = xEntry.attrib.get("color")  # Added in 1.5 R6
         
     | 
| 
       445 
439 
     | 
    
         
             
                            count = checkInt(xEntry.attrib.get("count", 0), 0)
         
     | 
| 
       446 
440 
     | 
    
         
             
                            shape = xEntry.attrib.get("shape", "")
         
     | 
| 
       447 
     | 
    
         
            -
                             
     | 
| 
       448 
     | 
    
         
            -
             
     | 
| 
      
 441 
     | 
    
         
            +
                            if color is None:
         
     | 
| 
      
 442 
     | 
    
         
            +
                                color = f"{red}, {green}, {blue}"
         
     | 
| 
      
 443 
     | 
    
         
            +
                            sObject.add(key, xEntry.text or "", color, shape, count)
         
     | 
| 
       449 
444 
     | 
    
         | 
| 
       450 
445 
     | 
    
         
             
                def _parseDictKeyText(self, xItem: ET.Element) -> dict:
         
     | 
| 
       451 
446 
     | 
    
         
             
                    """Parse a dictionary stored with key as an attribute and the
         
     | 
| 
         @@ -465,7 +460,7 @@ class ProjectXMLReader: 
     | 
|
| 
       465 
460 
     | 
    
         | 
| 
       466 
461 
     | 
    
         | 
| 
       467 
462 
     | 
    
         
             
            class ProjectXMLWriter:
         
     | 
| 
       468 
     | 
    
         
            -
                """Core: Project XML Writer
         
     | 
| 
      
 463 
     | 
    
         
            +
                """Core: Project XML Writer.
         
     | 
| 
       469 
464 
     | 
    
         | 
| 
       470 
465 
     | 
    
         
             
                The project writer class will only write a file according to the
         
     | 
| 
       471 
466 
     | 
    
         
             
                very latest spec.
         
     | 
| 
         @@ -474,7 +469,6 @@ class ProjectXMLWriter: 
     | 
|
| 
       474 
469 
     | 
    
         
             
                def __init__(self, path: str | Path) -> None:
         
     | 
| 
       475 
470 
     | 
    
         
             
                    self._path = Path(path)
         
     | 
| 
       476 
471 
     | 
    
         
             
                    self._error = None
         
     | 
| 
       477 
     | 
    
         
            -
                    return
         
     | 
| 
       478 
472 
     | 
    
         | 
| 
       479 
473 
     | 
    
         
             
                ##
         
     | 
| 
       480 
474 
     | 
    
         
             
                #  Properties
         
     | 
| 
         @@ -575,7 +569,6 @@ class ProjectXMLWriter: 
     | 
|
| 
       575 
569 
     | 
    
         
             
                    """Pack a single value into an XML element."""
         
     | 
| 
       576 
570 
     | 
    
         
             
                    xItem = ET.SubElement(xParent, name, attrib=attrib or {})
         
     | 
| 
       577 
571 
     | 
    
         
             
                    xItem.text = str(value) or ""
         
     | 
| 
       578 
     | 
    
         
            -
                    return
         
     | 
| 
       579 
572 
     | 
    
         | 
| 
       580 
573 
     | 
    
         
             
                def _packDictKeyValue(self, xParent: ET.Element, name: str, data: dict) -> None:
         
     | 
| 
       581 
574 
     | 
    
         
             
                    """Pack the entries of a dictionary into an XML element."""
         
     | 
| 
         @@ -584,4 +577,3 @@ class ProjectXMLWriter: 
     | 
|
| 
       584 
577 
     | 
    
         
             
                        if len(key) > 0:
         
     | 
| 
       585 
578 
     | 
    
         
             
                            xEntry = ET.SubElement(xItem, "entry", attrib={"key": key})
         
     | 
| 
       586 
579 
     | 
    
         
             
                            xEntry.text = str(value) or ""
         
     | 
| 
       587 
     | 
    
         
            -
                    return
         
     | 
    
        novelwriter/core/sessions.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 json
         
     | 
| 
         @@ -43,7 +43,7 @@ logger = logging.getLogger(__name__) 
     | 
|
| 
       43 
43 
     | 
    
         | 
| 
       44 
44 
     | 
    
         | 
| 
       45 
45 
     | 
    
         
             
            class NWSessionLog:
         
     | 
| 
       46 
     | 
    
         
            -
                """Core: Session JSON Lines Log File
         
     | 
| 
      
 46 
     | 
    
         
            +
                """Core: Session JSON Lines Log File.
         
     | 
| 
       47 
47 
     | 
    
         | 
| 
       48 
48 
     | 
    
         
             
                The class that wraps the session log file, which is in JSON Lines
         
     | 
| 
       49 
49 
     | 
    
         
             
                format. That is, one JSON object per line.
         
     | 
| 
         @@ -52,7 +52,6 @@ class NWSessionLog: 
     | 
|
| 
       52 
52 
     | 
    
         
             
                def __init__(self, project: NWProject) -> None:
         
     | 
| 
       53 
53 
     | 
    
         
             
                    self._project = project
         
     | 
| 
       54 
54 
     | 
    
         
             
                    self._start = 0.0
         
     | 
| 
       55 
     | 
    
         
            -
                    return
         
     | 
| 
       56 
55 
     | 
    
         | 
| 
       57 
56 
     | 
    
         
             
                ##
         
     | 
| 
       58 
57 
     | 
    
         
             
                #  Properties
         
     | 
| 
         @@ -70,7 +69,6 @@ class NWSessionLog: 
     | 
|
| 
       70 
69 
     | 
    
         
             
                def startSession(self) -> None:
         
     | 
| 
       71 
70 
     | 
    
         
             
                    """Start the writing session."""
         
     | 
| 
       72 
71 
     | 
    
         
             
                    self._start = time()
         
     | 
| 
       73 
     | 
    
         
            -
                    return
         
     | 
| 
       74 
72 
     | 
    
         | 
| 
       75 
73 
     | 
    
         
             
                def appendSession(self, idleTime: float) -> bool:
         
     | 
| 
       76 
74 
     | 
    
         
             
                    """Append session statistics to the sessions log file."""
         
     | 
    
        novelwriter/core/spellcheck.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 json
         
     | 
| 
         @@ -44,7 +44,7 @@ logger = logging.getLogger(__name__) 
     | 
|
| 
       44 
44 
     | 
    
         | 
| 
       45 
45 
     | 
    
         | 
| 
       46 
46 
     | 
    
         
             
            class NWSpellEnchant:
         
     | 
| 
       47 
     | 
    
         
            -
                """Core: Enchant Spell Checking Wrapper
         
     | 
| 
      
 47 
     | 
    
         
            +
                """Core: Enchant Spell Checking Wrapper.
         
     | 
| 
       48 
48 
     | 
    
         | 
| 
       49 
49 
     | 
    
         
             
                This is a rapper class for Enchant to keep the API consistent
         
     | 
| 
       50 
50 
     | 
    
         
             
                between spell check tools.
         
     | 
| 
         @@ -57,11 +57,9 @@ class NWSpellEnchant: 
     | 
|
| 
       57 
57 
     | 
    
         
             
                    self._language = None
         
     | 
| 
       58 
58 
     | 
    
         
             
                    self._broker = None
         
     | 
| 
       59 
59 
     | 
    
         
             
                    logger.debug("Ready: NWSpellEnchant")
         
     | 
| 
       60 
     | 
    
         
            -
                    return
         
     | 
| 
       61 
60 
     | 
    
         | 
| 
       62 
61 
     | 
    
         
             
                def __del__(self) -> None:  # pragma: no cover
         
     | 
| 
       63 
62 
     | 
    
         
             
                    logger.debug("Delete: NWSpellEnchant")
         
     | 
| 
       64 
     | 
    
         
            -
                    return
         
     | 
| 
       65 
63 
     | 
    
         | 
| 
       66 
64 
     | 
    
         
             
                ##
         
     | 
| 
       67 
65 
     | 
    
         
             
                #  Properties
         
     | 
| 
         @@ -106,21 +104,19 @@ class NWSpellEnchant: 
     | 
|
| 
       106 
104 
     | 
    
         
             
                        for word in self._userDict:
         
     | 
| 
       107 
105 
     | 
    
         
             
                            self._enchant.add_to_session(word)
         
     | 
| 
       108 
106 
     | 
    
         | 
| 
       109 
     | 
    
         
            -
                    return
         
     | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
       111 
107 
     | 
    
         
             
                ##
         
     | 
| 
       112 
108 
     | 
    
         
             
                #  Methods
         
     | 
| 
       113 
109 
     | 
    
         
             
                ##
         
     | 
| 
       114 
110 
     | 
    
         | 
| 
       115 
111 
     | 
    
         
             
                def checkWord(self, word: str) -> bool:
         
     | 
| 
       116 
     | 
    
         
            -
                    """ 
     | 
| 
      
 112 
     | 
    
         
            +
                    """Forward check to pyenchant."""
         
     | 
| 
       117 
113 
     | 
    
         
             
                    try:
         
     | 
| 
       118 
114 
     | 
    
         
             
                        return bool(self._enchant.check(word))
         
     | 
| 
       119 
115 
     | 
    
         
             
                    except Exception:
         
     | 
| 
       120 
116 
     | 
    
         
             
                        return True
         
     | 
| 
       121 
117 
     | 
    
         | 
| 
       122 
118 
     | 
    
         
             
                def suggestWords(self, word: str) -> list[str]:
         
     | 
| 
       123 
     | 
    
         
            -
                    """ 
     | 
| 
      
 119 
     | 
    
         
            +
                    """Ask pyenchant for suggestions."""
         
     | 
| 
       124 
120 
     | 
    
         
             
                    try:
         
     | 
| 
       125 
121 
     | 
    
         
             
                        return self._enchant.suggest(word)
         
     | 
| 
       126 
122 
     | 
    
         
             
                    except Exception:
         
     | 
| 
         @@ -172,24 +168,29 @@ class FakeEnchant: 
     | 
|
| 
       172 
168 
     | 
    
         
             
                    self.tag = ""
         
     | 
| 
       173 
169 
     | 
    
         
             
                    self.provider = FakeProvider()
         
     | 
| 
       174 
170 
     | 
    
         | 
| 
       175 
     | 
    
         
            -
                    return
         
     | 
| 
       176 
     | 
    
         
            -
             
     | 
| 
       177 
171 
     | 
    
         
             
                def check(self, word: str) -> bool:
         
     | 
| 
      
 172 
     | 
    
         
            +
                    """Return True for all words."""
         
     | 
| 
       178 
173 
     | 
    
         
             
                    return True
         
     | 
| 
       179 
174 
     | 
    
         | 
| 
       180 
175 
     | 
    
         
             
                def suggest(self, word: str) -> list[str]:
         
     | 
| 
      
 176 
     | 
    
         
            +
                    """Return an empty suggestion list."""
         
     | 
| 
       181 
177 
     | 
    
         
             
                    return []
         
     | 
| 
       182 
178 
     | 
    
         | 
| 
       183 
179 
     | 
    
         
             
                def add_to_session(self, word: str) -> None:
         
     | 
| 
      
 180 
     | 
    
         
            +
                    """Do nothing."""
         
     | 
| 
       184 
181 
     | 
    
         
             
                    return
         
     | 
| 
       185 
182 
     | 
    
         | 
| 
       186 
183 
     | 
    
         | 
| 
       187 
184 
     | 
    
         
             
            class UserDictionary:
         
     | 
| 
      
 185 
     | 
    
         
            +
                """Core: User Word Dictionary.
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                This class holds all the user's own words for spell checking
         
     | 
| 
      
 188 
     | 
    
         
            +
                purposes. The dictionary is per-project.
         
     | 
| 
      
 189 
     | 
    
         
            +
                """
         
     | 
| 
       188 
190 
     | 
    
         | 
| 
       189 
191 
     | 
    
         
             
                def __init__(self, project: NWProject) -> None:
         
     | 
| 
       190 
192 
     | 
    
         
             
                    self._project = project
         
     | 
| 
       191 
193 
     | 
    
         
             
                    self._words = set()
         
     | 
| 
       192 
     | 
    
         
            -
                    return
         
     | 
| 
       193 
194 
     | 
    
         | 
| 
       194 
195 
     | 
    
         
             
                def __contains__(self, word: str) -> bool:
         
     | 
| 
       195 
196 
     | 
    
         
             
                    return word in self._words
         
     | 
| 
         @@ -219,7 +220,6 @@ class UserDictionary: 
     | 
|
| 
       219 
220 
     | 
    
         
             
                        except Exception:
         
     | 
| 
       220 
221 
     | 
    
         
             
                            logger.error("Failed to load user dictionary")
         
     | 
| 
       221 
222 
     | 
    
         
             
                            logException()
         
     | 
| 
       222 
     | 
    
         
            -
                    return
         
     | 
| 
       223 
223 
     | 
    
         | 
| 
       224 
224 
     | 
    
         
             
                def save(self) -> None:
         
     | 
| 
       225 
225 
     | 
    
         
             
                    """Save the user's dictionary."""
         
     | 
| 
         @@ -232,4 +232,3 @@ class UserDictionary: 
     | 
|
| 
       232 
232 
     | 
    
         
             
                        except Exception:
         
     | 
| 
       233 
233 
     | 
    
         
             
                            logger.error("Failed to save user dictionary")
         
     | 
| 
       234 
234 
     | 
    
         
             
                            logException()
         
     | 
| 
       235 
     | 
    
         
            -
                    return
         
     | 
    
        novelwriter/core/status.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 dataclasses
         
     | 
| 
         @@ -35,6 +35,7 @@ from PyQt6.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygon 
     | 
|
| 
       35 
35 
     | 
    
         | 
| 
       36 
36 
     | 
    
         
             
            from novelwriter import SHARED
         
     | 
| 
       37 
37 
     | 
    
         
             
            from novelwriter.common import simplified
         
     | 
| 
      
 38 
     | 
    
         
            +
            from novelwriter.constants import nwLabels
         
     | 
| 
       38 
39 
     | 
    
         
             
            from novelwriter.enum import nwStatusShape
         
     | 
| 
       39 
40 
     | 
    
         
             
            from novelwriter.types import QtPaintAntiAlias, QtTransparent
         
     | 
| 
       40 
41 
     | 
    
         | 
| 
         @@ -43,12 +44,16 @@ if TYPE_CHECKING: 
     | 
|
| 
       43 
44 
     | 
    
         | 
| 
       44 
45 
     | 
    
         
             
            logger = logging.getLogger(__name__)
         
     | 
| 
       45 
46 
     | 
    
         | 
| 
      
 47 
     | 
    
         
            +
            CUSTOM_COL = "custom"
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
       46 
49 
     | 
    
         | 
| 
       47 
50 
     | 
    
         
             
            @dataclasses.dataclass
         
     | 
| 
       48 
51 
     | 
    
         
             
            class StatusEntry:
         
     | 
| 
      
 52 
     | 
    
         
            +
                """DataClass: Status Label Values."""
         
     | 
| 
       49 
53 
     | 
    
         | 
| 
       50 
54 
     | 
    
         
             
                name: str
         
     | 
| 
       51 
55 
     | 
    
         
             
                color: QColor
         
     | 
| 
      
 56 
     | 
    
         
            +
                theme: str
         
     | 
| 
       52 
57 
     | 
    
         
             
                shape: nwStatusShape
         
     | 
| 
       53 
58 
     | 
    
         
             
                icon: QIcon
         
     | 
| 
       54 
59 
     | 
    
         
             
                count: int = 0
         
     | 
| 
         @@ -62,13 +67,14 @@ class StatusEntry: 
     | 
|
| 
       62 
67 
     | 
    
         
             
                    return status
         
     | 
| 
       63 
68 
     | 
    
         | 
| 
       64 
69 
     | 
    
         | 
| 
       65 
     | 
    
         
            -
            NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0)
         
     | 
| 
      
 70 
     | 
    
         
            +
            NO_ENTRY = StatusEntry("", QColor(0, 0, 0), CUSTOM_COL, nwStatusShape.SQUARE, QIcon(), 0)
         
     | 
| 
       66 
71 
     | 
    
         | 
| 
       67 
72 
     | 
    
         
             
            T_UpdateEntry = list[tuple[str | None, StatusEntry]]
         
     | 
| 
       68 
73 
     | 
    
         
             
            T_StatusKind = Literal["s", "i"]
         
     | 
| 
       69 
74 
     | 
    
         | 
| 
       70 
75 
     | 
    
         | 
| 
       71 
76 
     | 
    
         
             
            class NWStatus:
         
     | 
| 
      
 77 
     | 
    
         
            +
                """Core: Status/Importance Label Class."""
         
     | 
| 
       72 
78 
     | 
    
         | 
| 
       73 
79 
     | 
    
         
             
                STATUS = "s"
         
     | 
| 
       74 
80 
     | 
    
         
             
                IMPORT = "i"
         
     | 
| 
         @@ -80,7 +86,6 @@ class NWStatus: 
     | 
|
| 
       80 
86 
     | 
    
         
             
                    self._default = None
         
     | 
| 
       81 
87 
     | 
    
         
             
                    self._prefix = prefix[:1]
         
     | 
| 
       82 
88 
     | 
    
         
             
                    self._height = SHARED.theme.baseIconHeight
         
     | 
| 
       83 
     | 
    
         
            -
                    return
         
     | 
| 
       84 
89 
     | 
    
         | 
| 
       85 
90 
     | 
    
         
             
                def __len__(self) -> int:
         
     | 
| 
       86 
91 
     | 
    
         
             
                    return len(self._store)
         
     | 
| 
         @@ -97,15 +102,12 @@ class NWStatus: 
     | 
|
| 
       97 
102 
     | 
    
         
             
                #  Methods
         
     | 
| 
       98 
103 
     | 
    
         
             
                ##
         
     | 
| 
       99 
104 
     | 
    
         | 
| 
       100 
     | 
    
         
            -
                def add(self, key: str | None, name: str, color:  
     | 
| 
       101 
     | 
    
         
            -
                        shape: str, count: int) -> str:
         
     | 
| 
      
 105 
     | 
    
         
            +
                def add(self, key: str | None, name: str, color: str, shape: str, count: int) -> str:
         
     | 
| 
       102 
106 
     | 
    
         
             
                    """Add or update a status entry. If the key is invalid, a new
         
     | 
| 
       103 
107 
     | 
    
         
             
                    key is generated.
         
     | 
| 
       104 
108 
     | 
    
         
             
                    """
         
     | 
| 
       105 
     | 
    
         
            -
                     
     | 
| 
       106 
     | 
    
         
            -
             
     | 
| 
       107 
     | 
    
         
            -
                    else:
         
     | 
| 
       108 
     | 
    
         
            -
                        qColor = QColor(100, 100, 100)
         
     | 
| 
      
 109 
     | 
    
         
            +
                    qColor = SHARED.theme.parseColor(color)
         
     | 
| 
      
 110 
     | 
    
         
            +
                    theme = color if color in nwLabels.THEME_COLORS else CUSTOM_COL
         
     | 
| 
       109 
111 
     | 
    
         | 
| 
       110 
112 
     | 
    
         
             
                    try:
         
     | 
| 
       111 
113 
     | 
    
         
             
                        iShape = nwStatusShape[shape]
         
     | 
| 
         @@ -115,7 +117,7 @@ class NWStatus: 
     | 
|
| 
       115 
117 
     | 
    
         
             
                    key = self._checkKey(key)
         
     | 
| 
       116 
118 
     | 
    
         
             
                    name = simplified(name)
         
     | 
| 
       117 
119 
     | 
    
         
             
                    icon = self.createIcon(self._height, qColor, iShape)
         
     | 
| 
       118 
     | 
    
         
            -
                    self._store[key] = StatusEntry(name, qColor, iShape, icon, count)
         
     | 
| 
      
 120 
     | 
    
         
            +
                    self._store[key] = StatusEntry(name, qColor, theme, iShape, icon, count)
         
     | 
| 
       119 
121 
     | 
    
         | 
| 
       120 
122 
     | 
    
         
             
                    if self._default is None:
         
     | 
| 
       121 
123 
     | 
    
         
             
                        self._default = key
         
     | 
| 
         @@ -132,8 +134,6 @@ class NWStatus: 
     | 
|
| 
       132 
134 
     | 
    
         
             
                    if self._default not in self._store:
         
     | 
| 
       133 
135 
     | 
    
         
             
                        self._default = next(iter(self._store)) if self._store else None
         
     | 
| 
       134 
136 
     | 
    
         | 
| 
       135 
     | 
    
         
            -
                    return
         
     | 
| 
       136 
     | 
    
         
            -
             
     | 
| 
       137 
137 
     | 
    
         
             
                def check(self, value: str) -> str:
         
     | 
| 
       138 
138 
     | 
    
         
             
                    """Check the key against the stored status names."""
         
     | 
| 
       139 
139 
     | 
    
         
             
                    if self._isKey(value) and value in self._store:
         
     | 
| 
         @@ -146,23 +146,23 @@ class NWStatus: 
     | 
|
| 
       146 
146 
     | 
    
         
             
                    """Clear the counts of references to the status entries."""
         
     | 
| 
       147 
147 
     | 
    
         
             
                    for entry in self._store.values():
         
     | 
| 
       148 
148 
     | 
    
         
             
                        entry.count = 0
         
     | 
| 
       149 
     | 
    
         
            -
                    return
         
     | 
| 
       150 
149 
     | 
    
         | 
| 
       151 
150 
     | 
    
         
             
                def increment(self, key: str | None) -> None:
         
     | 
| 
       152 
151 
     | 
    
         
             
                    """Increment the counter for a given entry."""
         
     | 
| 
       153 
152 
     | 
    
         
             
                    if key and key in self._store:
         
     | 
| 
       154 
153 
     | 
    
         
             
                        self._store[key].count += 1
         
     | 
| 
       155 
     | 
    
         
            -
                    return
         
     | 
| 
       156 
154 
     | 
    
         | 
| 
       157 
155 
     | 
    
         
             
                def pack(self) -> Iterable[tuple[str, dict]]:
         
     | 
| 
       158 
156 
     | 
    
         
             
                    """Pack the status entries into a dictionary."""
         
     | 
| 
       159 
157 
     | 
    
         
             
                    for key, entry in self._store.items():
         
     | 
| 
      
 158 
     | 
    
         
            +
                        if entry.theme == CUSTOM_COL:
         
     | 
| 
      
 159 
     | 
    
         
            +
                            color = entry.color.name(QColor.NameFormat.HexRgb)
         
     | 
| 
      
 160 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 161 
     | 
    
         
            +
                            color = entry.theme
         
     | 
| 
       160 
162 
     | 
    
         
             
                        yield (entry.name, {
         
     | 
| 
       161 
163 
     | 
    
         
             
                            "key":   key,
         
     | 
| 
       162 
164 
     | 
    
         
             
                            "count": str(entry.count),
         
     | 
| 
       163 
     | 
    
         
            -
                            " 
     | 
| 
       164 
     | 
    
         
            -
                            "green": str(entry.color.green()),
         
     | 
| 
       165 
     | 
    
         
            -
                            "blue":  str(entry.color.blue()),
         
     | 
| 
      
 165 
     | 
    
         
            +
                            "color": color,
         
     | 
| 
       166 
166 
     | 
    
         
             
                            "shape": entry.shape.name,
         
     | 
| 
       167 
167 
     | 
    
         
             
                        })
         
     | 
| 
       168 
168 
     | 
    
         
             
                    return
         
     | 
| 
         @@ -179,12 +179,20 @@ class NWStatus: 
     | 
|
| 
       179 
179 
     | 
    
         
             
                    try:
         
     | 
| 
       180 
180 
     | 
    
         
             
                        shape = nwStatusShape[str(data[0])]
         
     | 
| 
       181 
181 
     | 
    
         
             
                        color = QColor(str(data[1]))
         
     | 
| 
      
 182 
     | 
    
         
            +
                        theme = CUSTOM_COL if data[1].startswith("#") else data[1]
         
     | 
| 
       182 
183 
     | 
    
         
             
                        icon = NWStatus.createIcon(self._height, color, shape)
         
     | 
| 
       183 
     | 
    
         
            -
                        return StatusEntry(simplified(data[2]), color, shape, icon)
         
     | 
| 
      
 184 
     | 
    
         
            +
                        return StatusEntry(simplified(data[2]), color, theme, shape, icon)
         
     | 
| 
       184 
185 
     | 
    
         
             
                    except Exception:
         
     | 
| 
       185 
     | 
    
         
            -
                        logger.error("Could not parse entry %s",  
     | 
| 
      
 186 
     | 
    
         
            +
                        logger.error("Could not parse entry %s", data)
         
     | 
| 
       186 
187 
     | 
    
         
             
                    return None
         
     | 
| 
       187 
188 
     | 
    
         | 
| 
      
 189 
     | 
    
         
            +
                def refreshIcons(self) -> None:
         
     | 
| 
      
 190 
     | 
    
         
            +
                    """Refresh all icons."""
         
     | 
| 
      
 191 
     | 
    
         
            +
                    for entry in self._store.values():
         
     | 
| 
      
 192 
     | 
    
         
            +
                        if entry.theme != CUSTOM_COL:
         
     | 
| 
      
 193 
     | 
    
         
            +
                            entry.color = SHARED.theme.parseColor(entry.theme)
         
     | 
| 
      
 194 
     | 
    
         
            +
                        entry.icon = NWStatus.createIcon(self._height, entry.color, entry.shape)
         
     | 
| 
      
 195 
     | 
    
         
            +
             
     | 
| 
       188 
196 
     | 
    
         
             
                @staticmethod
         
     | 
| 
       189 
197 
     | 
    
         
             
                def createIcon(height: int, color: QColor, shape: nwStatusShape) -> QIcon:
         
     | 
| 
       190 
198 
     | 
    
         
             
                    """Generate an icon for a status label."""
         
     | 
| 
         @@ -240,7 +248,6 @@ class _ShapeCache: 
     | 
|
| 
       240 
248 
     | 
    
         | 
| 
       241 
249 
     | 
    
         
             
                def __init__(self) -> None:
         
     | 
| 
       242 
250 
     | 
    
         
             
                    self._cache: dict[nwStatusShape, QPainterPath] = {}
         
     | 
| 
       243 
     | 
    
         
            -
                    return
         
     | 
| 
       244 
251 
     | 
    
         | 
| 
       245 
252 
     | 
    
         
             
                def getShape(self, shape: nwStatusShape) -> QPainterPath:
         
     | 
| 
       246 
253 
     | 
    
         
             
                    """Return a painter shape for an icon."""
         
     | 
    
        novelwriter/core/storage.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 json
         
     | 
| 
         @@ -47,6 +47,7 @@ logger = logging.getLogger(__name__) 
     | 
|
| 
       47 
47 
     | 
    
         | 
| 
       48 
48 
     | 
    
         | 
| 
       49 
49 
     | 
    
         
             
            class NWStorageOpen(Enum):
         
     | 
| 
      
 50 
     | 
    
         
            +
                """The status of a storage location."""
         
     | 
| 
       50 
51 
     | 
    
         | 
| 
       51 
52 
     | 
    
         
             
                UNKOWN    = 0
         
     | 
| 
       52 
53 
     | 
    
         
             
                NOT_FOUND = 1
         
     | 
| 
         @@ -56,6 +57,7 @@ class NWStorageOpen(Enum): 
     | 
|
| 
       56 
57 
     | 
    
         | 
| 
       57 
58 
     | 
    
         | 
| 
       58 
59 
     | 
    
         
             
            class NWStorageCreate(Enum):
         
     | 
| 
      
 60 
     | 
    
         
            +
                """The status of a new storage location."""
         
     | 
| 
       59 
61 
     | 
    
         | 
| 
       60 
62 
     | 
    
         
             
                NOT_EMPTY = 0
         
     | 
| 
       61 
63 
     | 
    
         
             
                OS_ERROR  = 1
         
     | 
| 
         @@ -63,7 +65,7 @@ class NWStorageCreate(Enum): 
     | 
|
| 
       63 
65 
     | 
    
         | 
| 
       64 
66 
     | 
    
         | 
| 
       65 
67 
     | 
    
         
             
            class NWStorage:
         
     | 
| 
       66 
     | 
    
         
            -
                """Core: Project Storage Class
         
     | 
| 
      
 68 
     | 
    
         
            +
                """Core: Project Storage Class.
         
     | 
| 
       67 
69 
     | 
    
         | 
| 
       68 
70 
     | 
    
         
             
                The class that handles all paths related to the project storage.
         
     | 
| 
       69 
71 
     | 
    
         
             
                """
         
     | 
| 
         @@ -81,7 +83,6 @@ class NWStorage: 
     | 
|
| 
       81 
83 
     | 
    
         
             
                    self._openMode = self.MODE_INACTIVE
         
     | 
| 
       82 
84 
     | 
    
         
             
                    self._ready = False
         
     | 
| 
       83 
85 
     | 
    
         
             
                    self._exception = None
         
     | 
| 
       84 
     | 
    
         
            -
                    return
         
     | 
| 
       85 
86 
     | 
    
         | 
| 
       86 
87 
     | 
    
         
             
                def clear(self) -> None:
         
     | 
| 
       87 
88 
     | 
    
         
             
                    """Reset internal variables."""
         
     | 
| 
         @@ -90,7 +91,6 @@ class NWStorage: 
     | 
|
| 
       90 
91 
     | 
    
         
             
                    self._lockFilePath = None
         
     | 
| 
       91 
92 
     | 
    
         
             
                    self._openMode = self.MODE_INACTIVE
         
     | 
| 
       92 
93 
     | 
    
         
             
                    self._ready = False
         
     | 
| 
       93 
     | 
    
         
            -
                    return
         
     | 
| 
       94 
94 
     | 
    
         | 
| 
       95 
95 
     | 
    
         
             
                ##
         
     | 
| 
       96 
96 
     | 
    
         
             
                #  Properties
         
     | 
| 
         @@ -252,13 +252,11 @@ class NWStorage: 
     | 
|
| 
       252 
252 
     | 
    
         
             
                    """Lock the session when the project is successfully opened."""
         
     | 
| 
       253 
253 
     | 
    
         
             
                    if self._ready:
         
     | 
| 
       254 
254 
     | 
    
         
             
                        self._writeLockFile()
         
     | 
| 
       255 
     | 
    
         
            -
                    return
         
     | 
| 
       256 
255 
     | 
    
         | 
| 
       257 
256 
     | 
    
         
             
                def closeSession(self) -> None:
         
     | 
| 
       258 
257 
     | 
    
         
             
                    """Run tasks related to closing the session."""
         
     | 
| 
       259 
258 
     | 
    
         
             
                    self._clearLockFile()
         
     | 
| 
       260 
259 
     | 
    
         
             
                    self.clear()
         
     | 
| 
       261 
     | 
    
         
            -
                    return
         
     | 
| 
       262 
260 
     | 
    
         | 
| 
       263 
261 
     | 
    
         
             
                ##
         
     | 
| 
       264 
262 
     | 
    
         
             
                #  Content Access Methods
         
     | 
| 
         @@ -394,7 +392,7 @@ class NWStorage: 
     | 
|
| 
       394 
392 
     | 
    
         | 
| 
       395 
393 
     | 
    
         | 
| 
       396 
394 
     | 
    
         
             
            class _LegacyStorage:
         
     | 
| 
       397 
     | 
    
         
            -
                """Core: Legacy Storage Converter Utils
         
     | 
| 
      
 395 
     | 
    
         
            +
                """Core: Legacy Storage Converter Utils.
         
     | 
| 
       398 
396 
     | 
    
         | 
| 
       399 
397 
     | 
    
         
             
                A class with various functions to convert old file formats and
         
     | 
| 
       400 
398 
     | 
    
         
             
                file/folder layouts to the current project format.
         
     | 
| 
         @@ -402,7 +400,6 @@ class _LegacyStorage: 
     | 
|
| 
       402 
400 
     | 
    
         | 
| 
       403 
401 
     | 
    
         
             
                def __init__(self, project: NWProject) -> None:
         
     | 
| 
       404 
402 
     | 
    
         
             
                    self._project = project
         
     | 
| 
       405 
     | 
    
         
            -
                    return
         
     | 
| 
       406 
403 
     | 
    
         | 
| 
       407 
404 
     | 
    
         
             
                def legacyDataFolder(self, path: Path, child: Path) -> None:
         
     | 
| 
       408 
405 
     | 
    
         
             
                    """Handle the content of a legacy data folder from a version 1.0
         
     | 
| 
         @@ -484,8 +481,6 @@ class _LegacyStorage: 
     | 
|
| 
       484 
481 
     | 
    
         
             
                            except Exception as exc:
         
     | 
| 
       485 
482 
     | 
    
         
             
                                logger.warning("Failed to delete: %s", item, exc_info=exc)
         
     | 
| 
       486 
483 
     | 
    
         | 
| 
       487 
     | 
    
         
            -
                    return
         
     | 
| 
       488 
     | 
    
         
            -
             
     | 
| 
       489 
484 
     | 
    
         
             
                ##
         
     | 
| 
       490 
485 
     | 
    
         
             
                #  Internal Functions
         
     | 
| 
       491 
486 
     | 
    
         
             
                ##
         
     |