novelWriter 2.7.4__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 +207 -245
 - 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 +25 -13
 - {novelwriter-2.7.4.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.4.dist-info/RECORD +0 -163
 - {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/WHEEL +0 -0
 - {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/entry_points.txt +0 -0
 - {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/licenses/LICENSE.md +0 -0
 - {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/licenses/setup/LICENSE-Apache-2.0.txt +0 -0
 - {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/top_level.txt +0 -0
 
    
        novelwriter/core/item.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
         
     | 
| 
         @@ -44,7 +44,7 @@ logger = logging.getLogger(__name__) 
     | 
|
| 
       44 
44 
     | 
    
         | 
| 
       45 
45 
     | 
    
         | 
| 
       46 
46 
     | 
    
         
             
            class NWItem:
         
     | 
| 
       47 
     | 
    
         
            -
                """Core: Item Data Class
         
     | 
| 
      
 47 
     | 
    
         
            +
                """Core: Item Data Class.
         
     | 
| 
       48 
48 
     | 
    
         | 
| 
       49 
49 
     | 
    
         
             
                This class holds all the project information about a project item.
         
     | 
| 
       50 
50 
     | 
    
         
             
                Each item must be associated with a project and have a valid handle.
         
     | 
| 
         @@ -84,16 +84,14 @@ class NWItem: 
     | 
|
| 
       84 
84 
     | 
    
         
             
                    self._wordInit  = 0     # Initial character count
         
     | 
| 
       85 
85 
     | 
    
         
             
                    self._charInit  = 0     # Initial word count
         
     | 
| 
       86 
86 
     | 
    
         | 
| 
       87 
     | 
    
         
            -
                    return
         
     | 
| 
       88 
     | 
    
         
            -
             
     | 
| 
       89 
87 
     | 
    
         
             
                def __repr__(self) -> str:
         
     | 
| 
       90 
88 
     | 
    
         
             
                    return f"<NWItem handle={self._handle}, parent={self._parent}, name='{self._name}'>"
         
     | 
| 
       91 
89 
     | 
    
         | 
| 
       92 
90 
     | 
    
         
             
                def __bool__(self) -> bool:
         
     | 
| 
       93 
     | 
    
         
            -
                    """ 
     | 
| 
       94 
     | 
    
         
            -
                    to None, but this is no longer the case. It should 
     | 
| 
       95 
     | 
    
         
            -
                    evaluate to True since 2.1-beta1, although unpack and the 
     | 
| 
       96 
     | 
    
         
            -
                    class can leave it as an empty string.
         
     | 
| 
      
 91 
     | 
    
         
            +
                    """Check the truthiness of the class. The handle used to be
         
     | 
| 
      
 92 
     | 
    
         
            +
                    initiated to None, but this is no longer the case. It should
         
     | 
| 
      
 93 
     | 
    
         
            +
                    always evaluate to True since 2.1-beta1, although unpack and the
         
     | 
| 
      
 94 
     | 
    
         
            +
                    NWTree class can leave it as an empty string.
         
     | 
| 
       97 
95 
     | 
    
         
             
                    """
         
     | 
| 
       98 
96 
     | 
    
         
             
                    return bool(self._handle)
         
     | 
| 
       99 
97 
     | 
    
         | 
| 
         @@ -206,15 +204,13 @@ class NWItem: 
     | 
|
| 
       206 
204 
     | 
    
         
             
                        meta["cursorPos"] = str(self._cursorPos)
         
     | 
| 
       207 
205 
     | 
    
         
             
                        name["active"]    = yesNo(self._active)
         
     | 
| 
       208 
206 
     | 
    
         | 
| 
       209 
     | 
    
         
            -
                     
     | 
| 
      
 207 
     | 
    
         
            +
                    return {
         
     | 
| 
       210 
208 
     | 
    
         
             
                        "name": str(self._name),
         
     | 
| 
       211 
209 
     | 
    
         
             
                        "itemAttr": item,
         
     | 
| 
       212 
210 
     | 
    
         
             
                        "metaAttr": meta,
         
     | 
| 
       213 
211 
     | 
    
         
             
                        "nameAttr": name,
         
     | 
| 
       214 
212 
     | 
    
         
             
                    }
         
     | 
| 
       215 
213 
     | 
    
         | 
| 
       216 
     | 
    
         
            -
                    return data
         
     | 
| 
       217 
     | 
    
         
            -
             
     | 
| 
       218 
214 
     | 
    
         
             
                def unpack(self, data: dict) -> bool:
         
     | 
| 
       219 
215 
     | 
    
         
             
                    """Set the values from a data dictionary."""
         
     | 
| 
       220 
216 
     | 
    
         
             
                    item = data.get("itemAttr", {})
         
     | 
| 
         @@ -298,13 +294,11 @@ class NWItem: 
     | 
|
| 
       298 
294 
     | 
    
         
             
                def notifyToRefresh(self) -> None:
         
     | 
| 
       299 
295 
     | 
    
         
             
                    """Notify GUI that item info needs to be refreshed."""
         
     | 
| 
       300 
296 
     | 
    
         
             
                    self._project.tree.refreshItems([self._handle])
         
     | 
| 
       301 
     | 
    
         
            -
                    return
         
     | 
| 
       302 
297 
     | 
    
         | 
| 
       303 
298 
     | 
    
         
             
                def notifyNovelStructureChange(self) -> None:
         
     | 
| 
       304 
299 
     | 
    
         
             
                    """Notify that the structure of a novel has changed."""
         
     | 
| 
       305 
300 
     | 
    
         
             
                    if self._root and self._class == nwItemClass.NOVEL:
         
     | 
| 
       306 
301 
     | 
    
         
             
                        self._project.tree.novelStructureChanged(self._root)
         
     | 
| 
       307 
     | 
    
         
            -
                    return
         
     | 
| 
       308 
302 
     | 
    
         | 
| 
       309 
303 
     | 
    
         
             
                ##
         
     | 
| 
       310 
304 
     | 
    
         
             
                #  Lookup Methods
         
     | 
| 
         @@ -363,13 +357,13 @@ class NWItem: 
     | 
|
| 
       363 
357 
     | 
    
         
             
                    """
         
     | 
| 
       364 
358 
     | 
    
         
             
                    if self.isFileType():
         
     | 
| 
       365 
359 
     | 
    
         
             
                        key = "checked" if self._active else "unchecked"
         
     | 
| 
       366 
     | 
    
         
            -
                        color = " 
     | 
| 
      
 360 
     | 
    
         
            +
                        color = "active" if self._active else "inactive"
         
     | 
| 
       367 
361 
     | 
    
         
             
                        text = trConst(nwLabels.ACTIVE_NAME[key])
         
     | 
| 
       368 
     | 
    
         
            -
                        icon = SHARED.theme.getIcon(key, color)
         
     | 
| 
       369 
362 
     | 
    
         
             
                    else:
         
     | 
| 
      
 363 
     | 
    
         
            +
                        key = "noncheckable"
         
     | 
| 
      
 364 
     | 
    
         
            +
                        color = "disabled"
         
     | 
| 
       370 
365 
     | 
    
         
             
                        text = ""
         
     | 
| 
       371 
     | 
    
         
            -
             
     | 
| 
       372 
     | 
    
         
            -
                    return text, icon
         
     | 
| 
      
 366 
     | 
    
         
            +
                    return text, SHARED.theme.getIcon(key, color)
         
     | 
| 
       373 
367 
     | 
    
         | 
| 
       374 
368 
     | 
    
         
             
                ##
         
     | 
| 
       375 
369 
     | 
    
         
             
                #  Checker Methods
         
     | 
| 
         @@ -457,8 +451,6 @@ class NWItem: 
     | 
|
| 
       457 
451 
     | 
    
         
             
                    if self._import is None:
         
     | 
| 
       458 
452 
     | 
    
         
             
                        self.setImport("New")  # This forces a default value lookup
         
     | 
| 
       459 
453 
     | 
    
         | 
| 
       460 
     | 
    
         
            -
                    return
         
     | 
| 
       461 
     | 
    
         
            -
             
     | 
| 
       462 
454 
     | 
    
         
             
                ##
         
     | 
| 
       463 
455 
     | 
    
         
             
                #  Set Item Values
         
     | 
| 
       464 
456 
     | 
    
         
             
                ##
         
     | 
| 
         @@ -469,7 +461,6 @@ class NWItem: 
     | 
|
| 
       469 
461 
     | 
    
         
             
                        self._name = simplified(name)
         
     | 
| 
       470 
462 
     | 
    
         
             
                    else:
         
     | 
| 
       471 
463 
     | 
    
         
             
                        self._name = ""
         
     | 
| 
       472 
     | 
    
         
            -
                    return
         
     | 
| 
       473 
464 
     | 
    
         | 
| 
       474 
465 
     | 
    
         
             
                def setParent(self, handle: Any) -> None:
         
     | 
| 
       475 
466 
     | 
    
         
             
                    """Set the parent handle, and ensure it is valid."""
         
     | 
| 
         @@ -479,7 +470,6 @@ class NWItem: 
     | 
|
| 
       479 
470 
     | 
    
         
             
                        self._parent = handle
         
     | 
| 
       480 
471 
     | 
    
         
             
                    else:
         
     | 
| 
       481 
472 
     | 
    
         
             
                        self._parent = None
         
     | 
| 
       482 
     | 
    
         
            -
                    return
         
     | 
| 
       483 
473 
     | 
    
         | 
| 
       484 
474 
     | 
    
         
             
                def setRoot(self, handle: Any) -> None:
         
     | 
| 
       485 
475 
     | 
    
         
             
                    """Set the root handle, and ensure it is valid."""
         
     | 
| 
         @@ -489,7 +479,6 @@ class NWItem: 
     | 
|
| 
       489 
479 
     | 
    
         
             
                        self._root = handle
         
     | 
| 
       490 
480 
     | 
    
         
             
                    else:
         
     | 
| 
       491 
481 
     | 
    
         
             
                        self._root = None
         
     | 
| 
       492 
     | 
    
         
            -
                    return
         
     | 
| 
       493 
482 
     | 
    
         | 
| 
       494 
483 
     | 
    
         
             
                def setOrder(self, order: Any) -> None:
         
     | 
| 
       495 
484 
     | 
    
         
             
                    """Set the item order, and ensure that it is valid. This value
         
     | 
| 
         @@ -497,7 +486,6 @@ class NWItem: 
     | 
|
| 
       497 
486 
     | 
    
         
             
                    the moment.
         
     | 
| 
       498 
487 
     | 
    
         
             
                    """
         
     | 
| 
       499 
488 
     | 
    
         
             
                    self._order = checkInt(order, 0)
         
     | 
| 
       500 
     | 
    
         
            -
                    return
         
     | 
| 
       501 
489 
     | 
    
         | 
| 
       502 
490 
     | 
    
         
             
                def setType(self, value: Any) -> None:
         
     | 
| 
       503 
491 
     | 
    
         
             
                    """Set the item type from either a proper nwItemType, or set it
         
     | 
| 
         @@ -510,7 +498,6 @@ class NWItem: 
     | 
|
| 
       510 
498 
     | 
    
         
             
                    else:
         
     | 
| 
       511 
499 
     | 
    
         
             
                        logger.error("Unrecognised item type '%s'", value)
         
     | 
| 
       512 
500 
     | 
    
         
             
                        self._type = nwItemType.NO_TYPE
         
     | 
| 
       513 
     | 
    
         
            -
                    return
         
     | 
| 
       514 
501 
     | 
    
         | 
| 
       515 
502 
     | 
    
         
             
                def setClass(self, value: Any) -> None:
         
     | 
| 
       516 
503 
     | 
    
         
             
                    """Set the item class from either a proper nwItemClass, or set
         
     | 
| 
         @@ -523,7 +510,6 @@ class NWItem: 
     | 
|
| 
       523 
510 
     | 
    
         
             
                    else:
         
     | 
| 
       524 
511 
     | 
    
         
             
                        logger.error("Unrecognised item class '%s'", value)
         
     | 
| 
       525 
512 
     | 
    
         
             
                        self._class = nwItemClass.NO_CLASS
         
     | 
| 
       526 
     | 
    
         
            -
                    return
         
     | 
| 
       527 
513 
     | 
    
         | 
| 
       528 
514 
     | 
    
         
             
                def setLayout(self, value: Any) -> None:
         
     | 
| 
       529 
515 
     | 
    
         
             
                    """Set the item layout from either a proper nwItemLayout, or set
         
     | 
| 
         @@ -536,21 +522,18 @@ class NWItem: 
     | 
|
| 
       536 
522 
     | 
    
         
             
                    else:
         
     | 
| 
       537 
523 
     | 
    
         
             
                        logger.error("Unrecognised item layout '%s'", value)
         
     | 
| 
       538 
524 
     | 
    
         
             
                        self._layout = nwItemLayout.NO_LAYOUT
         
     | 
| 
       539 
     | 
    
         
            -
                    return
         
     | 
| 
       540 
525 
     | 
    
         | 
| 
       541 
526 
     | 
    
         
             
                def setStatus(self, value: Any) -> None:
         
     | 
| 
       542 
527 
     | 
    
         
             
                    """Set the item status by looking it up in the valid status
         
     | 
| 
       543 
528 
     | 
    
         
             
                    items of the current project.
         
     | 
| 
       544 
529 
     | 
    
         
             
                    """
         
     | 
| 
       545 
530 
     | 
    
         
             
                    self._status = self._project.data.itemStatus.check(value)
         
     | 
| 
       546 
     | 
    
         
            -
                    return
         
     | 
| 
       547 
531 
     | 
    
         | 
| 
       548 
532 
     | 
    
         
             
                def setImport(self, value: Any) -> None:
         
     | 
| 
       549 
533 
     | 
    
         
             
                    """Set the item importance by looking it up in the valid import
         
     | 
| 
       550 
534 
     | 
    
         
             
                    items of the current project.
         
     | 
| 
       551 
535 
     | 
    
         
             
                    """
         
     | 
| 
       552 
536 
     | 
    
         
             
                    self._import = self._project.data.itemImport.check(value)
         
     | 
| 
       553 
     | 
    
         
            -
                    return
         
     | 
| 
       554 
537 
     | 
    
         | 
| 
       555 
538 
     | 
    
         
             
                def setActive(self, state: Any) -> None:
         
     | 
| 
       556 
539 
     | 
    
         
             
                    """Set the active flag."""
         
     | 
| 
         @@ -558,7 +541,6 @@ class NWItem: 
     | 
|
| 
       558 
541 
     | 
    
         
             
                        self._active = state
         
     | 
| 
       559 
542 
     | 
    
         
             
                    else:
         
     | 
| 
       560 
543 
     | 
    
         
             
                        self._active = False
         
     | 
| 
       561 
     | 
    
         
            -
                    return
         
     | 
| 
       562 
544 
     | 
    
         | 
| 
       563 
545 
     | 
    
         
             
                def setExpanded(self, state: Any) -> None:
         
     | 
| 
       564 
546 
     | 
    
         
             
                    """Set the expanded status of an item in the project tree."""
         
     | 
| 
         @@ -566,7 +548,6 @@ class NWItem: 
     | 
|
| 
       566 
548 
     | 
    
         
             
                        self._expanded = state
         
     | 
| 
       567 
549 
     | 
    
         
             
                    else:
         
     | 
| 
       568 
550 
     | 
    
         
             
                        self._expanded = False
         
     | 
| 
       569 
     | 
    
         
            -
                    return
         
     | 
| 
       570 
551 
     | 
    
         | 
| 
       571 
552 
     | 
    
         
             
                ##
         
     | 
| 
       572 
553 
     | 
    
         
             
                #  Set Document Meta Data
         
     | 
| 
         @@ -576,7 +557,6 @@ class NWItem: 
     | 
|
| 
       576 
557 
     | 
    
         
             
                    """Set the main heading level."""
         
     | 
| 
       577 
558 
     | 
    
         
             
                    if value in nwStyles.H_LEVEL:
         
     | 
| 
       578 
559 
     | 
    
         
             
                        self._heading = value
         
     | 
| 
       579 
     | 
    
         
            -
                    return
         
     | 
| 
       580 
560 
     | 
    
         | 
| 
       581 
561 
     | 
    
         
             
                def setCharCount(self, count: Any) -> None:
         
     | 
| 
       582 
562 
     | 
    
         
             
                    """Set the character count, and ensure that it is an integer."""
         
     | 
| 
         @@ -584,7 +564,6 @@ class NWItem: 
     | 
|
| 
       584 
564 
     | 
    
         
             
                        self._charCount = max(0, count)
         
     | 
| 
       585 
565 
     | 
    
         
             
                    else:
         
     | 
| 
       586 
566 
     | 
    
         
             
                        self._charCount = 0
         
     | 
| 
       587 
     | 
    
         
            -
                    return
         
     | 
| 
       588 
567 
     | 
    
         | 
| 
       589 
568 
     | 
    
         
             
                def setWordCount(self, count: Any) -> None:
         
     | 
| 
       590 
569 
     | 
    
         
             
                    """Set the word count, and ensure that it is an integer."""
         
     | 
| 
         @@ -592,7 +571,6 @@ class NWItem: 
     | 
|
| 
       592 
571 
     | 
    
         
             
                        self._wordCount = max(0, count)
         
     | 
| 
       593 
572 
     | 
    
         
             
                    else:
         
     | 
| 
       594 
573 
     | 
    
         
             
                        self._wordCount = 0
         
     | 
| 
       595 
     | 
    
         
            -
                    return
         
     | 
| 
       596 
574 
     | 
    
         | 
| 
       597 
575 
     | 
    
         
             
                def setParaCount(self, count: Any) -> None:
         
     | 
| 
       598 
576 
     | 
    
         
             
                    """Set the paragraph count, and ensure that it is an integer."""
         
     | 
| 
         @@ -600,7 +578,6 @@ class NWItem: 
     | 
|
| 
       600 
578 
     | 
    
         
             
                        self._paraCount = max(0, count)
         
     | 
| 
       601 
579 
     | 
    
         
             
                    else:
         
     | 
| 
       602 
580 
     | 
    
         
             
                        self._paraCount = 0
         
     | 
| 
       603 
     | 
    
         
            -
                    return
         
     | 
| 
       604 
581 
     | 
    
         | 
| 
       605 
582 
     | 
    
         
             
                def setCursorPos(self, position: Any) -> None:
         
     | 
| 
       606 
583 
     | 
    
         
             
                    """Set the cursor position, and ensure that it is an integer."""
         
     | 
| 
         @@ -608,4 +585,3 @@ class NWItem: 
     | 
|
| 
       608 
585 
     | 
    
         
             
                        self._cursorPos = max(0, position)
         
     | 
| 
       609 
586 
     | 
    
         
             
                    else:
         
     | 
| 
       610 
587 
     | 
    
         
             
                        self._cursorPos = 0
         
     | 
| 
       611 
     | 
    
         
            -
                    return
         
     | 
    
        novelwriter/core/itemmodel.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 logging
         
     | 
| 
         @@ -66,7 +66,7 @@ T_NodeData = str | QIcon | QFont | Qt.AlignmentFlag | None 
     | 
|
| 
       66 
66 
     | 
    
         | 
| 
       67 
67 
     | 
    
         | 
| 
       68 
68 
     | 
    
         
             
            class ProjectNode:
         
     | 
| 
       69 
     | 
    
         
            -
                """Core: Project Model Node Class
         
     | 
| 
      
 69 
     | 
    
         
            +
                """Core: Project Model Node Class.
         
     | 
| 
       70 
70 
     | 
    
         | 
| 
       71 
71 
     | 
    
         
             
                The project tree structure is saved as nodes in a tree, starting
         
     | 
| 
       72 
72 
     | 
    
         
             
                from a root node. This class makes up these nodes.
         
     | 
| 
         @@ -103,7 +103,6 @@ class ProjectNode: 
     | 
|
| 
       103 
103 
     | 
    
         
             
                    self._count = 0
         
     | 
| 
       104 
104 
     | 
    
         
             
                    self.refresh()
         
     | 
| 
       105 
105 
     | 
    
         
             
                    self.updateCount()
         
     | 
| 
       106 
     | 
    
         
            -
                    return
         
     | 
| 
       107 
106 
     | 
    
         | 
| 
       108 
107 
     | 
    
         
             
                def __repr__(self) -> str:
         
     | 
| 
       109 
108 
     | 
    
         
             
                    return (
         
     | 
| 
         @@ -114,7 +113,7 @@ class ProjectNode: 
     | 
|
| 
       114 
113 
     | 
    
         
             
                    )
         
     | 
| 
       115 
114 
     | 
    
         | 
| 
       116 
115 
     | 
    
         
             
                def __bool__(self) -> bool:
         
     | 
| 
       117 
     | 
    
         
            -
                     
     | 
| 
      
 116 
     | 
    
         
            +
                    # A node should always evaluate to True.
         
     | 
| 
       118 
117 
     | 
    
         
             
                    return True
         
     | 
| 
       119 
118 
     | 
    
         | 
| 
       120 
119 
     | 
    
         
             
                ##
         
     | 
| 
         @@ -162,15 +161,12 @@ class ProjectNode: 
     | 
|
| 
       162 
161 
     | 
    
         
             
                    self._cache[C_STATUS_TIP] = sText
         
     | 
| 
       163 
162 
     | 
    
         
             
                    self._cache[C_STATUS_ACCESS] = sText
         
     | 
| 
       164 
163 
     | 
    
         | 
| 
       165 
     | 
    
         
            -
                    return
         
     | 
| 
       166 
     | 
    
         
            -
             
     | 
| 
       167 
164 
     | 
    
         
             
                def updateCount(self, propagate: bool = True) -> None:
         
     | 
| 
       168 
165 
     | 
    
         
             
                    """Update counts, and propagate upwards in the tree."""
         
     | 
| 
       169 
166 
     | 
    
         
             
                    self._count = self._item.mainCount + sum(c._count for c in self._children)  # noqa: SLF001
         
     | 
| 
       170 
167 
     | 
    
         
             
                    self._cache[C_COUNT_TEXT] = f"{self._count:n}"
         
     | 
| 
       171 
168 
     | 
    
         
             
                    if propagate and (parent := self._parent):
         
     | 
| 
       172 
169 
     | 
    
         
             
                        parent.updateCount()
         
     | 
| 
       173 
     | 
    
         
            -
                    return
         
     | 
| 
       174 
170 
     | 
    
         | 
| 
       175 
171 
     | 
    
         
             
                ##
         
     | 
| 
       176 
172 
     | 
    
         
             
                #  Data Access
         
     | 
| 
         @@ -223,7 +219,6 @@ class ProjectNode: 
     | 
|
| 
       223 
219 
     | 
    
         
             
                        self._children.append(child)
         
     | 
| 
       224 
220 
     | 
    
         
             
                    self._refreshChildrenPos()
         
     | 
| 
       225 
221 
     | 
    
         
             
                    self._item.notifyNovelStructureChange()
         
     | 
| 
       226 
     | 
    
         
            -
                    return
         
     | 
| 
       227 
222 
     | 
    
         | 
| 
       228 
223 
     | 
    
         
             
                def takeChild(self, pos: int) -> ProjectNode | None:
         
     | 
| 
       229 
224 
     | 
    
         
             
                    """Remove a child item and return it."""
         
     | 
| 
         @@ -243,7 +238,6 @@ class ProjectNode: 
     | 
|
| 
       243 
238 
     | 
    
         
             
                        self._children.insert(target, node)
         
     | 
| 
       244 
239 
     | 
    
         
             
                        self._refreshChildrenPos()
         
     | 
| 
       245 
240 
     | 
    
         
             
                        self._item.notifyNovelStructureChange()
         
     | 
| 
       246 
     | 
    
         
            -
                    return
         
     | 
| 
       247 
241 
     | 
    
         | 
| 
       248 
242 
     | 
    
         
             
                def setExpanded(self, state: bool) -> None:
         
     | 
| 
       249 
243 
     | 
    
         
             
                    """Set the node's expanded state."""
         
     | 
| 
         @@ -251,7 +245,6 @@ class ProjectNode: 
     | 
|
| 
       251 
245 
     | 
    
         
             
                        self._item.setExpanded(True)
         
     | 
| 
       252 
246 
     | 
    
         
             
                    else:
         
     | 
| 
       253 
247 
     | 
    
         
             
                        self._item.setExpanded(False)
         
     | 
| 
       254 
     | 
    
         
            -
                    return
         
     | 
| 
       255 
248 
     | 
    
         | 
| 
       256 
249 
     | 
    
         
             
                ##
         
     | 
| 
       257 
250 
     | 
    
         
             
                #  Internal Functions
         
     | 
| 
         @@ -262,14 +255,12 @@ class ProjectNode: 
     | 
|
| 
       262 
255 
     | 
    
         
             
                    for node in self._children:
         
     | 
| 
       263 
256 
     | 
    
         
             
                        children.append(node)
         
     | 
| 
       264 
257 
     | 
    
         
             
                        node._recursiveAppendChildren(children)  # noqa: SLF001
         
     | 
| 
       265 
     | 
    
         
            -
                    return
         
     | 
| 
       266 
258 
     | 
    
         | 
| 
       267 
259 
     | 
    
         
             
                def _refreshChildrenPos(self) -> None:
         
     | 
| 
       268 
260 
     | 
    
         
             
                    """Update the row value on all children."""
         
     | 
| 
       269 
261 
     | 
    
         
             
                    for n, child in enumerate(self._children):
         
     | 
| 
       270 
262 
     | 
    
         
             
                        child._row = n  # noqa: SLF001
         
     | 
| 
       271 
263 
     | 
    
         
             
                        child.item.setOrder(n)
         
     | 
| 
       272 
     | 
    
         
            -
                    return
         
     | 
| 
       273 
264 
     | 
    
         | 
| 
       274 
265 
     | 
    
         
             
                def _updateRelationships(self, child: ProjectNode) -> None:
         
     | 
| 
       275 
266 
     | 
    
         
             
                    """Update a child item's relationships."""
         
     | 
| 
         @@ -282,11 +273,10 @@ class ProjectNode: 
     | 
|
| 
       282 
273 
     | 
    
         
             
                        child.item.setParent(None)
         
     | 
| 
       283 
274 
     | 
    
         
             
                        child.item.setRoot(child.item.itemHandle)
         
     | 
| 
       284 
275 
     | 
    
         
             
                        child.item.setClassDefaults(child.item.itemClass)
         
     | 
| 
       285 
     | 
    
         
            -
                    return
         
     | 
| 
       286 
276 
     | 
    
         | 
| 
       287 
277 
     | 
    
         | 
| 
       288 
278 
     | 
    
         
             
            class ProjectModel(QAbstractItemModel):
         
     | 
| 
       289 
     | 
    
         
            -
                """Core: Project Model Class
         
     | 
| 
      
 279 
     | 
    
         
            +
                """Core: Project Model Class.
         
     | 
| 
       290 
280 
     | 
    
         | 
| 
       291 
281 
     | 
    
         
             
                This class provides the interface for the tree widget used on the
         
     | 
| 
       292 
282 
     | 
    
         
             
                GUI. It implements the QModelIndex based interface required, adds
         
     | 
| 
         @@ -302,11 +292,9 @@ class ProjectModel(QAbstractItemModel): 
     | 
|
| 
       302 
292 
     | 
    
         
             
                    self._root = ProjectNode(NWItem(tree.project, INV_ROOT))
         
     | 
| 
       303 
293 
     | 
    
         
             
                    self._root.item.setName("Invisible Root")
         
     | 
| 
       304 
294 
     | 
    
         
             
                    logger.debug("Ready: ProjectModel")
         
     | 
| 
       305 
     | 
    
         
            -
                    return
         
     | 
| 
       306 
295 
     | 
    
         | 
| 
       307 
296 
     | 
    
         
             
                def __del__(self) -> None:  # pragma: no cover
         
     | 
| 
       308 
297 
     | 
    
         
             
                    logger.debug("Delete: ProjectModel")
         
     | 
| 
       309 
     | 
    
         
            -
                    return
         
     | 
| 
       310 
298 
     | 
    
         | 
| 
       311 
299 
     | 
    
         
             
                ##
         
     | 
| 
       312 
300 
     | 
    
         
             
                #  Properties
         
     | 
| 
         @@ -363,7 +351,7 @@ class ProjectModel(QAbstractItemModel): 
     | 
|
| 
       363 
351 
     | 
    
         
             
                ##
         
     | 
| 
       364 
352 
     | 
    
         | 
| 
       365 
353 
     | 
    
         
             
                def supportedDropActions(self) -> Qt.DropAction:
         
     | 
| 
       366 
     | 
    
         
            -
                    """Return supported drop actions"""
         
     | 
| 
      
 354 
     | 
    
         
            +
                    """Return supported drop actions."""
         
     | 
| 
       367 
355 
     | 
    
         
             
                    return Qt.DropAction.MoveAction
         
     | 
| 
       368 
356 
     | 
    
         | 
| 
       369 
357 
     | 
    
         
             
                def mimeTypes(self) -> list[str]:
         
     | 
| 
         @@ -445,7 +433,6 @@ class ProjectModel(QAbstractItemModel): 
     | 
|
| 
       445 
433 
     | 
    
         
             
                    self.beginInsertRows(parent, row, row)
         
     | 
| 
       446 
434 
     | 
    
         
             
                    node.addChild(child, row)
         
     | 
| 
       447 
435 
     | 
    
         
             
                    self.endInsertRows()
         
     | 
| 
       448 
     | 
    
         
            -
                    return
         
     | 
| 
       449 
436 
     | 
    
         | 
| 
       450 
437 
     | 
    
         
             
                def removeChild(self, parent: QModelIndex, pos: int) -> ProjectNode | None:
         
     | 
| 
       451 
438 
     | 
    
         
             
                    """Remove a node from the model and return it."""
         
     | 
| 
         @@ -469,7 +456,6 @@ class ProjectModel(QAbstractItemModel): 
     | 
|
| 
       469 
456 
     | 
    
         
             
                                self.beginMoveRows(index.parent(), pos, pos, index.parent(), end)
         
     | 
| 
       470 
457 
     | 
    
         
             
                                parent.moveChild(pos, new)
         
     | 
| 
       471 
458 
     | 
    
         
             
                                self.endMoveRows()
         
     | 
| 
       472 
     | 
    
         
            -
                    return
         
     | 
| 
       473 
459 
     | 
    
         | 
| 
       474 
460 
     | 
    
         
             
                def multiMove(self, indices: list[QModelIndex], target: QModelIndex, pos: int = -1) -> None:
         
     | 
| 
       475 
461 
     | 
    
         
             
                    """Move multiple items to a new location."""
         
     | 
| 
         @@ -497,7 +483,6 @@ class ProjectModel(QAbstractItemModel): 
     | 
|
| 
       497 
483 
     | 
    
         
             
                                        node._updateRelationships(child)  # noqa: SLF001
         
     | 
| 
       498 
484 
     | 
    
         
             
                                        child.item.notifyToRefresh()
         
     | 
| 
       499 
485 
     | 
    
         
             
                                    node.item.notifyToRefresh()
         
     | 
| 
       500 
     | 
    
         
            -
                    return
         
     | 
| 
       501 
486 
     | 
    
         | 
| 
       502 
487 
     | 
    
         
             
                ##
         
     | 
| 
       503 
488 
     | 
    
         
             
                #  Other Methods
         
     | 
| 
         @@ -506,7 +491,6 @@ class ProjectModel(QAbstractItemModel): 
     | 
|
| 
       506 
491 
     | 
    
         
             
                def clear(self) -> None:
         
     | 
| 
       507 
492 
     | 
    
         
             
                    """Clear the project model."""
         
     | 
| 
       508 
493 
     | 
    
         
             
                    self._root.children.clear()
         
     | 
| 
       509 
     | 
    
         
            -
                    return
         
     | 
| 
       510 
494 
     | 
    
         | 
| 
       511 
495 
     | 
    
         
             
                def allExpanded(self) -> list[QModelIndex]:
         
     | 
| 
       512 
496 
     | 
    
         
             
                    """Return a list of all expanded items."""
         
     | 
    
        novelwriter/core/novelmodel.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
         
     | 
| 
         @@ -54,21 +54,20 @@ T_NodeData = str | QIcon | QPixmap | Qt.AlignmentFlag | None 
     | 
|
| 
       54 
54 
     | 
    
         | 
| 
       55 
55 
     | 
    
         | 
| 
       56 
56 
     | 
    
         
             
            class NovelModel(QAbstractTableModel):
         
     | 
| 
      
 57 
     | 
    
         
            +
                """Core: Novel Model CLass."""
         
     | 
| 
       57 
58 
     | 
    
         | 
| 
       58 
59 
     | 
    
         
             
                __slots__ = ("_columns", "_extraKey", "_extraLabel", "_more", "_rows")
         
     | 
| 
       59 
60 
     | 
    
         | 
| 
       60 
61 
     | 
    
         
             
                def __init__(self) -> None:
         
     | 
| 
       61 
62 
     | 
    
         
             
                    super().__init__()
         
     | 
| 
       62 
63 
     | 
    
         
             
                    self._rows: list[dict[int, T_NodeData]] = []
         
     | 
| 
       63 
     | 
    
         
            -
                    self._more = SHARED.theme.getIcon("more_arrow")
         
     | 
| 
      
 64 
     | 
    
         
            +
                    self._more = SHARED.theme.getIcon("more_arrow", "tool")
         
     | 
| 
       64 
65 
     | 
    
         
             
                    self._columns = 3
         
     | 
| 
       65 
66 
     | 
    
         
             
                    self._extraKey = ""
         
     | 
| 
       66 
67 
     | 
    
         
             
                    self._extraLabel = ""
         
     | 
| 
       67 
     | 
    
         
            -
                    return
         
     | 
| 
       68 
68 
     | 
    
         | 
| 
       69 
69 
     | 
    
         
             
                def __del__(self) -> None:  # pragma: no cover
         
     | 
| 
       70 
70 
     | 
    
         
             
                    logger.debug("Delete: NovelModel")
         
     | 
| 
       71 
     | 
    
         
            -
                    return
         
     | 
| 
       72 
71 
     | 
    
         | 
| 
       73 
72 
     | 
    
         
             
                ##
         
     | 
| 
       74 
73 
     | 
    
         
             
                #  Properties
         
     | 
| 
         @@ -102,7 +101,6 @@ class NovelModel(QAbstractTableModel): 
     | 
|
| 
       102 
101 
     | 
    
         
             
                            self._columns = 4
         
     | 
| 
       103 
102 
     | 
    
         
             
                            self._extraKey = nwKeyWords.PLOT_KEY
         
     | 
| 
       104 
103 
     | 
    
         
             
                            self._extraLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])
         
     | 
| 
       105 
     | 
    
         
            -
                    return
         
     | 
| 
       106 
104 
     | 
    
         | 
| 
       107 
105 
     | 
    
         
             
                ##
         
     | 
| 
       108 
106 
     | 
    
         
             
                #  Model Interface
         
     | 
| 
         @@ -147,7 +145,6 @@ class NovelModel(QAbstractTableModel): 
     | 
|
| 
       147 
145 
     | 
    
         
             
                def clear(self) -> None:
         
     | 
| 
       148 
146 
     | 
    
         
             
                    """Clear the model."""
         
     | 
| 
       149 
147 
     | 
    
         
             
                    self._rows.clear()
         
     | 
| 
       150 
     | 
    
         
            -
                    return
         
     | 
| 
       151 
148 
     | 
    
         | 
| 
       152 
149 
     | 
    
         
             
                def append(self, node: IndexNode) -> None:
         
     | 
| 
       153 
150 
     | 
    
         
             
                    """Append a node to the model."""
         
     | 
| 
         @@ -155,7 +152,6 @@ class NovelModel(QAbstractTableModel): 
     | 
|
| 
       155 
152 
     | 
    
         
             
                    for key, head in node.items():
         
     | 
| 
       156 
153 
     | 
    
         
             
                        if key != "T0000":
         
     | 
| 
       157 
154 
     | 
    
         
             
                            self._rows.append(self._generateEntry(handle, key, head))
         
     | 
| 
       158 
     | 
    
         
            -
                    return
         
     | 
| 
       159 
155 
     | 
    
         | 
| 
       160 
156 
     | 
    
         
             
                def refresh(self, node: IndexNode) -> bool:
         
     | 
| 
       161 
157 
     | 
    
         
             
                    """Refresh an index node."""
         
     | 
    
        novelwriter/core/options.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
         
     | 
| 
         @@ -56,7 +56,7 @@ VALID_MAP: dict[str, set[str]] = { 
     | 
|
| 
       56 
56 
     | 
    
         
             
                "GuiWordList": {"winWidth", "winHeight"},
         
     | 
| 
       57 
57 
     | 
    
         
             
                "GuiNovelView": {"lastCol", "lastColSize"},
         
     | 
| 
       58 
58 
     | 
    
         
             
                "GuiBuildSettings": {
         
     | 
| 
       59 
     | 
    
         
            -
                    "winWidth", "winHeight", "treeWidth", "filterWidth",
         
     | 
| 
      
 59 
     | 
    
         
            +
                    "winWidth", "winHeight", "treeWidth", "filterWidth", "autoPreview",
         
     | 
| 
       60 
60 
     | 
    
         
             
                },
         
     | 
| 
       61 
61 
     | 
    
         
             
                "GuiManuscript": {
         
     | 
| 
       62 
62 
     | 
    
         
             
                    "winWidth", "winHeight", "optsWidth", "viewWidth", "listHeight",
         
     | 
| 
         @@ -80,7 +80,7 @@ VALID_MAP: dict[str, set[str]] = { 
     | 
|
| 
       80 
80 
     | 
    
         | 
| 
       81 
81 
     | 
    
         | 
| 
       82 
82 
     | 
    
         
             
            class OptionState:
         
     | 
| 
       83 
     | 
    
         
            -
                """Core: GUI Options Storage
         
     | 
| 
      
 83 
     | 
    
         
            +
                """Core: GUI Options Storage.
         
     | 
| 
       84 
84 
     | 
    
         | 
| 
       85 
85 
     | 
    
         
             
                A class for storing the state of the GUI. The data is stored per
         
     | 
| 
       86 
86 
     | 
    
         
             
                project. Settings that should be project-independent are stored in
         
     | 
| 
         @@ -90,7 +90,6 @@ class OptionState: 
     | 
|
| 
       90 
90 
     | 
    
         
             
                def __init__(self, project: NWProject) -> None:
         
     | 
| 
       91 
91 
     | 
    
         
             
                    self._project = project
         
     | 
| 
       92 
92 
     | 
    
         
             
                    self._state = {}
         
     | 
| 
       93 
     | 
    
         
            -
                    return
         
     | 
| 
       94 
93 
     | 
    
         | 
| 
       95 
94 
     | 
    
         
             
                ##
         
     | 
| 
       96 
95 
     | 
    
         
             
                #  Load and Save Cache
         
     | 
    
        novelwriter/core/project.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
         
     | 
| 
         @@ -57,6 +57,7 @@ logger = logging.getLogger(__name__) 
     | 
|
| 
       57 
57 
     | 
    
         | 
| 
       58 
58 
     | 
    
         | 
| 
       59 
59 
     | 
    
         
             
            class NWProjectState(Enum):
         
     | 
| 
      
 60 
     | 
    
         
            +
                """The state of the loaded project."""
         
     | 
| 
       60 
61 
     | 
    
         | 
| 
       61 
62 
     | 
    
         
             
                UNKNOWN  = 0
         
     | 
| 
       62 
63 
     | 
    
         
             
                LOCKED   = 1
         
     | 
| 
         @@ -65,6 +66,11 @@ class NWProjectState(Enum): 
     | 
|
| 
       65 
66 
     | 
    
         | 
| 
       66 
67 
     | 
    
         | 
| 
       67 
68 
     | 
    
         
             
            class NWProject:
         
     | 
| 
      
 69 
     | 
    
         
            +
                """Core: novelWriter Project Class.
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                This class is the parent class of the project, and holds instances
         
     | 
| 
      
 72 
     | 
    
         
            +
                of project data, the project tree, and the project index.
         
     | 
| 
      
 73 
     | 
    
         
            +
                """
         
     | 
| 
       68 
74 
     | 
    
         | 
| 
       69 
75 
     | 
    
         
             
                __slots__ = (
         
     | 
| 
       70 
76 
     | 
    
         
             
                    "_changed", "_data", "_index", "_langData", "_options", "_session",
         
     | 
| 
         @@ -92,17 +98,13 @@ class NWProject: 
     | 
|
| 
       92 
98 
     | 
    
         | 
| 
       93 
99 
     | 
    
         
             
                    logger.debug("Ready: NWProject")
         
     | 
| 
       94 
100 
     | 
    
         | 
| 
       95 
     | 
    
         
            -
                    return
         
     | 
| 
       96 
     | 
    
         
            -
             
     | 
| 
       97 
101 
     | 
    
         
             
                def __del__(self) -> None:  # pragma: no cover
         
     | 
| 
       98 
102 
     | 
    
         
             
                    logger.debug("Delete: NWProject")
         
     | 
| 
       99 
     | 
    
         
            -
                    return
         
     | 
| 
       100 
103 
     | 
    
         | 
| 
       101 
104 
     | 
    
         
             
                def clear(self) -> None:
         
     | 
| 
       102 
105 
     | 
    
         
             
                    """Clear the project."""
         
     | 
| 
       103 
106 
     | 
    
         
             
                    self._tree.clear()
         
     | 
| 
       104 
107 
     | 
    
         
             
                    self._index.clear()
         
     | 
| 
       105 
     | 
    
         
            -
                    return
         
     | 
| 
       106 
108 
     | 
    
         | 
| 
       107 
109 
     | 
    
         
             
                ##
         
     | 
| 
       108 
110 
     | 
    
         
             
                #  Properties
         
     | 
| 
         @@ -224,7 +226,7 @@ class NWProject: 
     | 
|
| 
       224 
226 
     | 
    
         | 
| 
       225 
227 
     | 
    
         
             
                    return True
         
     | 
| 
       226 
228 
     | 
    
         | 
| 
       227 
     | 
    
         
            -
                def copyFileContent(self, tHandle: str, sHandle: str) -> bool:
         
     | 
| 
      
 229 
     | 
    
         
            +
                def copyFileContent(self, tHandle: str, sHandle: str, newTitle: str | None = None) -> bool:
         
     | 
| 
       228 
230 
     | 
    
         
             
                    """Copy content to a new document after it is created. This
         
     | 
| 
       229 
231 
     | 
    
         
             
                    will not run if the file exists and is not empty.
         
     | 
| 
       230 
232 
     | 
    
         
             
                    """
         
     | 
| 
         @@ -239,6 +241,14 @@ class NWProject: 
     | 
|
| 
       239 
241 
     | 
    
         | 
| 
       240 
242 
     | 
    
         
             
                    logger.debug("Populating '%s' with text from '%s'", tHandle, sHandle)
         
     | 
| 
       241 
243 
     | 
    
         
             
                    text = self._storage.getDocumentText(sHandle)
         
     | 
| 
      
 244 
     | 
    
         
            +
                    if (
         
     | 
| 
      
 245 
     | 
    
         
            +
                        newTitle and (lines := text.split("\n")) and lines
         
     | 
| 
      
 246 
     | 
    
         
            +
                        and lines[0].startswith(("# ", "## ", "### ", "#### ", "#! ", "##! ", "###! "))
         
     | 
| 
      
 247 
     | 
    
         
            +
                    ):
         
     | 
| 
      
 248 
     | 
    
         
            +
                        prefix, _, _ = lines[0].partition(" ")
         
     | 
| 
      
 249 
     | 
    
         
            +
                        lines[0] = f"{prefix} {newTitle}"
         
     | 
| 
      
 250 
     | 
    
         
            +
                        text = "\n".join(lines)
         
     | 
| 
      
 251 
     | 
    
         
            +
             
     | 
| 
       242 
252 
     | 
    
         
             
                    self._storage.getDocument(tHandle).writeDocument(text)
         
     | 
| 
       243 
253 
     | 
    
         
             
                    sItem.setLayout(tItem.itemLayout)
         
     | 
| 
       244 
254 
     | 
    
         
             
                    self._index.reIndexHandle(tHandle)
         
     | 
| 
         @@ -255,7 +265,6 @@ class NWProject: 
     | 
|
| 
       255 
265 
     | 
    
         
             
                        if rHandle and (tHandle := SHARED.project.newFile(tag.title(), rHandle)):
         
     | 
| 
       256 
266 
     | 
    
         
             
                            self.writeNewFile(tHandle, 1, False, f"@tag: {tag}\n\n")
         
     | 
| 
       257 
267 
     | 
    
         
             
                            self._tree.refreshItems([tHandle])
         
     | 
| 
       258 
     | 
    
         
            -
                    return
         
     | 
| 
       259 
268 
     | 
    
         | 
| 
       260 
269 
     | 
    
         
             
                ##
         
     | 
| 
       261 
270 
     | 
    
         
             
                #  Project Methods
         
     | 
| 
         @@ -433,7 +442,6 @@ class NWProject: 
     | 
|
| 
       433 
442 
     | 
    
         
             
                    self._tree.writeToCFile()
         
     | 
| 
       434 
443 
     | 
    
         
             
                    self._session.appendSession(idleTime)
         
     | 
| 
       435 
444 
     | 
    
         
             
                    self._storage.closeSession()
         
     | 
| 
       436 
     | 
    
         
            -
                    return
         
     | 
| 
       437 
445 
     | 
    
         | 
| 
       438 
446 
     | 
    
         
             
                def backupProject(self, doNotify: bool) -> bool:
         
     | 
| 
       439 
447 
     | 
    
         
             
                    """Create a zip file of the entire project."""
         
     | 
| 
         @@ -483,15 +491,14 @@ class NWProject: 
     | 
|
| 
       483 
491 
     | 
    
         | 
| 
       484 
492 
     | 
    
         
             
                def setDefaultStatusImport(self) -> None:
         
     | 
| 
       485 
493 
     | 
    
         
             
                    """Set the default status and importance values."""
         
     | 
| 
       486 
     | 
    
         
            -
                    self._data.itemStatus.add(None, self.tr("New"), 
     | 
| 
       487 
     | 
    
         
            -
                    self._data.itemStatus.add(None, self.tr("Note"), 
     | 
| 
       488 
     | 
    
         
            -
                    self._data.itemStatus.add(None, self.tr("Draft"), 
     | 
| 
       489 
     | 
    
         
            -
                    self._data.itemStatus.add(None, self.tr("Finished"),  
     | 
| 
       490 
     | 
    
         
            -
                    self._data.itemImport.add(None, self.tr("New"), 
     | 
| 
       491 
     | 
    
         
            -
                    self._data.itemImport.add(None, self.tr("Minor"), 
     | 
| 
       492 
     | 
    
         
            -
                    self._data.itemImport.add(None, self.tr("Major"), 
     | 
| 
       493 
     | 
    
         
            -
                    self._data.itemImport.add(None, self.tr("Main"), 
     | 
| 
       494 
     | 
    
         
            -
                    return
         
     | 
| 
      
 494 
     | 
    
         
            +
                    self._data.itemStatus.add(None, self.tr("New"), "faded", "STAR", 0)
         
     | 
| 
      
 495 
     | 
    
         
            +
                    self._data.itemStatus.add(None, self.tr("Note"), "red", "TRIANGLE", 0)
         
     | 
| 
      
 496 
     | 
    
         
            +
                    self._data.itemStatus.add(None, self.tr("Draft"), "yellow", "CIRCLE_T", 0)
         
     | 
| 
      
 497 
     | 
    
         
            +
                    self._data.itemStatus.add(None, self.tr("Finished"), "green", "STAR", 0)
         
     | 
| 
      
 498 
     | 
    
         
            +
                    self._data.itemImport.add(None, self.tr("New"), "purple", "SQUARE", 0)
         
     | 
| 
      
 499 
     | 
    
         
            +
                    self._data.itemImport.add(None, self.tr("Minor"), "purple", "BLOCK_2", 0)
         
     | 
| 
      
 500 
     | 
    
         
            +
                    self._data.itemImport.add(None, self.tr("Major"), "purple", "BLOCK_3", 0)
         
     | 
| 
      
 501 
     | 
    
         
            +
                    self._data.itemImport.add(None, self.tr("Main"), "purple", "BLOCK_4", 0)
         
     | 
| 
       495 
502 
     | 
    
         | 
| 
       496 
503 
     | 
    
         
             
                def setProjectLang(self, language: str | None) -> None:
         
     | 
| 
       497 
504 
     | 
    
         
             
                    """Set the project-specific language."""
         
     | 
| 
         @@ -500,7 +507,6 @@ class NWProject: 
     | 
|
| 
       500 
507 
     | 
    
         
             
                        self._data.setLanguage(language)
         
     | 
| 
       501 
508 
     | 
    
         
             
                        self._loadProjectLocalisation()
         
     | 
| 
       502 
509 
     | 
    
         
             
                        self.setProjectChanged(True)
         
     | 
| 
       503 
     | 
    
         
            -
                    return
         
     | 
| 
       504 
510 
     | 
    
         | 
| 
       505 
511 
     | 
    
         
             
                def setProjectChanged(self, status: bool) -> bool:
         
     | 
| 
       506 
512 
     | 
    
         
             
                    """Toggle the project changed flag, and propagate the
         
     | 
| 
         @@ -519,7 +525,6 @@ class NWProject: 
     | 
|
| 
       519 
525 
     | 
    
         
             
                    """Update the total word and character count values."""
         
     | 
| 
       520 
526 
     | 
    
         
             
                    wNovel, wNotes, cNovel, cNotes = self._tree.sumCounts()
         
     | 
| 
       521 
527 
     | 
    
         
             
                    self._data.setCurrCounts(wNovel=wNovel, wNotes=wNotes, cNovel=cNovel, cNotes=cNotes)
         
     | 
| 
       522 
     | 
    
         
            -
                    return
         
     | 
| 
       523 
528 
     | 
    
         | 
| 
       524 
529 
     | 
    
         
             
                def countStatus(self) -> None:
         
     | 
| 
       525 
530 
     | 
    
         
             
                    """Count how many times the various status flags are used in the
         
     | 
| 
         @@ -533,7 +538,6 @@ class NWProject: 
     | 
|
| 
       533 
538 
     | 
    
         
             
                            self._data.itemStatus.increment(nwItem.itemStatus)
         
     | 
| 
       534 
539 
     | 
    
         
             
                        else:
         
     | 
| 
       535 
540 
     | 
    
         
             
                            self._data.itemImport.increment(nwItem.itemImport)
         
     | 
| 
       536 
     | 
    
         
            -
                    return
         
     | 
| 
       537 
541 
     | 
    
         | 
| 
       538 
542 
     | 
    
         
             
                def updateStatus(self, kind: T_StatusKind, update: T_UpdateEntry) -> None:
         
     | 
| 
       539 
543 
     | 
    
         
             
                    """Update status or import entries."""
         
     | 
| 
         @@ -545,7 +549,13 @@ class NWProject: 
     | 
|
| 
       545 
549 
     | 
    
         
             
                        self._data.itemImport.update(update)
         
     | 
| 
       546 
550 
     | 
    
         
             
                        SHARED.emitStatusLabelsChanged(self, kind)
         
     | 
| 
       547 
551 
     | 
    
         
             
                        self._tree.refreshAllItems()
         
     | 
| 
       548 
     | 
    
         
            -
             
     | 
| 
      
 552 
     | 
    
         
            +
             
     | 
| 
      
 553 
     | 
    
         
            +
                def updateTheme(self) -> None:
         
     | 
| 
      
 554 
     | 
    
         
            +
                    """Update theme elements."""
         
     | 
| 
      
 555 
     | 
    
         
            +
                    logger.debug("Theme Update: NWProject")
         
     | 
| 
      
 556 
     | 
    
         
            +
             
     | 
| 
      
 557 
     | 
    
         
            +
                    self._data.itemStatus.refreshIcons()
         
     | 
| 
      
 558 
     | 
    
         
            +
                    self._data.itemImport.refreshIcons()
         
     | 
| 
       549 
559 
     | 
    
         | 
| 
       550 
560 
     | 
    
         
             
                def localLookup(self, word: str | int) -> str:
         
     | 
| 
       551 
561 
     | 
    
         
             
                    """Look up a word or number in the translation map for the
         
     |