novelWriter 2.3.1__py3-none-any.whl → 2.4b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/METADATA +1 -1
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/RECORD +81 -70
- novelwriter/__init__.py +5 -5
- novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_dark/typ_arrow-down.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_arrow-right.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_light/icons.conf +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_light/typ_arrow-down.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_arrow-right.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +25 -23
- novelwriter/common.py +1 -1
- novelwriter/config.py +35 -12
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +60 -40
- novelwriter/core/coretools.py +98 -13
- novelwriter/core/docbuild.py +74 -7
- novelwriter/core/document.py +24 -3
- novelwriter/core/index.py +31 -112
- novelwriter/core/project.py +10 -15
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/status.py +4 -4
- novelwriter/core/storage.py +8 -2
- novelwriter/core/tohtml.py +22 -25
- novelwriter/core/tokenizer.py +416 -232
- novelwriter/core/tomd.py +17 -8
- novelwriter/core/toodt.py +65 -7
- novelwriter/core/tree.py +8 -8
- novelwriter/dialogs/docsplit.py +7 -8
- novelwriter/dialogs/preferences.py +3 -6
- novelwriter/enum.py +17 -14
- novelwriter/extensions/modified.py +20 -2
- novelwriter/extensions/versioninfo.py +1 -1
- novelwriter/gui/doceditor.py +257 -279
- novelwriter/gui/dochighlight.py +29 -25
- novelwriter/gui/docviewer.py +139 -148
- novelwriter/gui/docviewerpanel.py +4 -24
- novelwriter/gui/editordocument.py +12 -1
- novelwriter/gui/itemdetails.py +6 -6
- novelwriter/gui/mainmenu.py +37 -16
- novelwriter/gui/noveltree.py +11 -19
- novelwriter/gui/outline.py +43 -20
- novelwriter/gui/projtree.py +35 -43
- novelwriter/gui/search.py +316 -0
- novelwriter/gui/sidebar.py +25 -30
- novelwriter/gui/theme.py +59 -6
- novelwriter/guimain.py +176 -173
- novelwriter/shared.py +26 -1
- novelwriter/text/__init__.py +3 -0
- novelwriter/text/counting.py +137 -0
- novelwriter/tools/manuscript.py +344 -55
- novelwriter/tools/manussettings.py +213 -71
- novelwriter/tools/welcome.py +1 -1
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/WHEEL +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/top_level.txt +0 -0
    
        novelwriter/core/tomd.py
    CHANGED
    
    | @@ -27,7 +27,7 @@ import logging | |
| 27 27 |  | 
| 28 28 | 
             
            from pathlib import Path
         | 
| 29 29 |  | 
| 30 | 
            -
            from novelwriter.constants import nwHeadFmt, nwLabels
         | 
| 30 | 
            +
            from novelwriter.constants import nwHeadFmt, nwLabels, nwUnicode
         | 
| 31 31 | 
             
            from novelwriter.core.project import NWProject
         | 
| 32 32 | 
             
            from novelwriter.core.tokenizer import Tokenizer
         | 
| 33 33 |  | 
| @@ -49,6 +49,7 @@ class ToMarkdown(Tokenizer): | |
| 49 49 | 
             
                    super().__init__(project)
         | 
| 50 50 | 
             
                    self._genMode = self.M_STD
         | 
| 51 51 | 
             
                    self._fullMD: list[str] = []
         | 
| 52 | 
            +
                    self._preserveBreaks = True
         | 
| 52 53 | 
             
                    return
         | 
| 53 54 |  | 
| 54 55 | 
             
                ##
         | 
| @@ -74,6 +75,11 @@ class ToMarkdown(Tokenizer): | |
| 74 75 | 
             
                    self._genMode = self.M_EXT
         | 
| 75 76 | 
             
                    return
         | 
| 76 77 |  | 
| 78 | 
            +
                def setPreserveBreaks(self, state: bool) -> None:
         | 
| 79 | 
            +
                    """Preserve line breaks in paragraphs."""
         | 
| 80 | 
            +
                    self._preserveBreaks = state
         | 
| 81 | 
            +
                    return
         | 
| 82 | 
            +
             | 
| 77 83 | 
             
                ##
         | 
| 78 84 | 
             
                #  Class Methods
         | 
| 79 85 | 
             
                ##
         | 
| @@ -95,11 +101,14 @@ class ToMarkdown(Tokenizer): | |
| 95 101 | 
             
                            self.FMT_D_E: "",
         | 
| 96 102 | 
             
                            self.FMT_U_B: "",
         | 
| 97 103 | 
             
                            self.FMT_U_E: "",
         | 
| 104 | 
            +
                            self.FMT_M_B: "",
         | 
| 105 | 
            +
                            self.FMT_M_E: "",
         | 
| 98 106 | 
             
                            self.FMT_SUP_B: "",
         | 
| 99 107 | 
             
                            self.FMT_SUP_E: "",
         | 
| 100 108 | 
             
                            self.FMT_SUB_B: "",
         | 
| 101 109 | 
             
                            self.FMT_SUB_E: "",
         | 
| 102 110 | 
             
                        }
         | 
| 111 | 
            +
                        cSkip = ""
         | 
| 103 112 | 
             
                    else:
         | 
| 104 113 | 
             
                        # Extended Markdown
         | 
| 105 114 | 
             
                        mdTags = {
         | 
| @@ -111,22 +120,26 @@ class ToMarkdown(Tokenizer): | |
| 111 120 | 
             
                            self.FMT_D_E: "~~",
         | 
| 112 121 | 
             
                            self.FMT_U_B: "",
         | 
| 113 122 | 
             
                            self.FMT_U_E: "",
         | 
| 123 | 
            +
                            self.FMT_M_B: "==",
         | 
| 124 | 
            +
                            self.FMT_M_E: "==",
         | 
| 114 125 | 
             
                            self.FMT_SUP_B: "^",
         | 
| 115 126 | 
             
                            self.FMT_SUP_E: "^",
         | 
| 116 127 | 
             
                            self.FMT_SUB_B: "~",
         | 
| 117 128 | 
             
                            self.FMT_SUB_E: "~",
         | 
| 118 129 | 
             
                        }
         | 
| 130 | 
            +
                        cSkip = nwUnicode.U_MMSP
         | 
| 119 131 |  | 
| 120 132 | 
             
                    self._result = ""
         | 
| 121 133 |  | 
| 122 134 | 
             
                    para = []
         | 
| 123 135 | 
             
                    lines = []
         | 
| 136 | 
            +
                    lineSep = "  \n" if self._preserveBreaks else " "
         | 
| 124 137 |  | 
| 125 138 | 
             
                    for tType, _, tText, tFormat, tStyle in self._tokens:
         | 
| 126 139 |  | 
| 127 140 | 
             
                        if tType == self.T_EMPTY:
         | 
| 128 | 
            -
                            if  | 
| 129 | 
            -
                                tTemp = ( | 
| 141 | 
            +
                            if para:
         | 
| 142 | 
            +
                                tTemp = (lineSep.join(para)).rstrip(" ")
         | 
| 130 143 | 
             
                                lines.append(f"{tTemp}\n\n")
         | 
| 131 144 | 
             
                            para = []
         | 
| 132 145 |  | 
| @@ -134,10 +147,6 @@ class ToMarkdown(Tokenizer): | |
| 134 147 | 
             
                            tHead = tText.replace(nwHeadFmt.BR, "\n")
         | 
| 135 148 | 
             
                            lines.append(f"# {tHead}\n\n")
         | 
| 136 149 |  | 
| 137 | 
            -
                        elif tType == self.T_UNNUM:
         | 
| 138 | 
            -
                            tHead = tText.replace(nwHeadFmt.BR, "\n")
         | 
| 139 | 
            -
                            lines.append(f"## {tHead}\n\n")
         | 
| 140 | 
            -
             | 
| 141 150 | 
             
                        elif tType == self.T_HEAD1:
         | 
| 142 151 | 
             
                            tHead = tText.replace(nwHeadFmt.BR, "\n")
         | 
| 143 152 | 
             
                            lines.append(f"# {tHead}\n\n")
         | 
| @@ -158,7 +167,7 @@ class ToMarkdown(Tokenizer): | |
| 158 167 | 
             
                            lines.append(f"{tText}\n\n")
         | 
| 159 168 |  | 
| 160 169 | 
             
                        elif tType == self.T_SKIP:
         | 
| 161 | 
            -
                            lines.append("\n\n | 
| 170 | 
            +
                            lines.append(f"{cSkip}\n\n")
         | 
| 162 171 |  | 
| 163 172 | 
             
                        elif tType == self.T_TEXT:
         | 
| 164 173 | 
             
                            tTemp = tText
         | 
    
        novelwriter/core/toodt.py
    CHANGED
    
    | @@ -84,14 +84,16 @@ X_BLD = 0x01  # Bold format | |
| 84 84 | 
             
            X_ITA = 0x02  # Italic format
         | 
| 85 85 | 
             
            X_DEL = 0x04  # Strikethrough format
         | 
| 86 86 | 
             
            X_UND = 0x08  # Underline format
         | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 87 | 
            +
            X_MRK = 0x10  # Marked format
         | 
| 88 | 
            +
            X_SUP = 0x20  # Superscript
         | 
| 89 | 
            +
            X_SUB = 0x40  # Subscript
         | 
| 89 90 |  | 
| 90 91 | 
             
            # Formatting Masks
         | 
| 91 92 | 
             
            M_BLD = ~X_BLD
         | 
| 92 93 | 
             
            M_ITA = ~X_ITA
         | 
| 93 94 | 
             
            M_DEL = ~X_DEL
         | 
| 94 95 | 
             
            M_UND = ~X_UND
         | 
| 96 | 
            +
            M_MRK = ~X_MRK
         | 
| 95 97 | 
             
            M_SUP = ~X_SUP
         | 
| 96 98 | 
             
            M_SUB = ~X_SUB
         | 
| 97 99 |  | 
| @@ -136,6 +138,7 @@ class ToOdt(Tokenizer): | |
| 136 138 | 
             
                    self._textSize     = 12
         | 
| 137 139 | 
             
                    self._textFixed    = False
         | 
| 138 140 | 
             
                    self._colourHead   = False
         | 
| 141 | 
            +
                    self._firstIndent  = False
         | 
| 139 142 | 
             
                    self._headerFormat = ""
         | 
| 140 143 | 
             
                    self._pageOffset   = 0
         | 
| 141 144 |  | 
| @@ -151,6 +154,7 @@ class ToOdt(Tokenizer): | |
| 151 154 | 
             
                    self._fSizeText    = "12pt"
         | 
| 152 155 | 
             
                    self._fLineHeight  = "115%"
         | 
| 153 156 | 
             
                    self._fBlockIndent = "1.693cm"
         | 
| 157 | 
            +
                    self._fTextIndent  = "0.499cm"
         | 
| 154 158 | 
             
                    self._textAlign    = "left"
         | 
| 155 159 | 
             
                    self._dLanguage    = "en"
         | 
| 156 160 | 
             
                    self._dCountry     = "GB"
         | 
| @@ -189,6 +193,7 @@ class ToOdt(Tokenizer): | |
| 189 193 | 
             
                    self._opaHead34 = None
         | 
| 190 194 | 
             
                    self._colMetaTx = None
         | 
| 191 195 | 
             
                    self._opaMetaTx = None
         | 
| 196 | 
            +
                    self._markText  = "#ffffa6"
         | 
| 192 197 |  | 
| 193 198 | 
             
                    return
         | 
| 194 199 |  | 
| @@ -229,6 +234,11 @@ class ToOdt(Tokenizer): | |
| 229 234 | 
             
                    self._pageOffset = offset
         | 
| 230 235 | 
             
                    return
         | 
| 231 236 |  | 
| 237 | 
            +
                def setFirstLineIndent(self, state: bool) -> None:
         | 
| 238 | 
            +
                    """Enable or disable first line indent."""
         | 
| 239 | 
            +
                    self._firstIndent = state
         | 
| 240 | 
            +
                    return
         | 
| 241 | 
            +
             | 
| 232 242 | 
             
                ##
         | 
| 233 243 | 
             
                #  Class Methods
         | 
| 234 244 | 
             
                ##
         | 
| @@ -394,6 +404,7 @@ class ToOdt(Tokenizer): | |
| 394 404 | 
             
                    pFmt = []
         | 
| 395 405 | 
             
                    pText = []
         | 
| 396 406 | 
             
                    pStyle = None
         | 
| 407 | 
            +
                    pIndent = True
         | 
| 397 408 | 
             
                    for tType, _, tText, tFormat, tStyle in self._tokens:
         | 
| 398 409 |  | 
| 399 410 | 
             
                        # Styles
         | 
| @@ -424,6 +435,9 @@ class ToOdt(Tokenizer): | |
| 424 435 | 
             
                            if tStyle & self.A_IND_R:
         | 
| 425 436 | 
             
                                oStyle.setMarginRight(self._fBlockIndent)
         | 
| 426 437 |  | 
| 438 | 
            +
                        if tType not in (self.T_EMPTY, self.T_TEXT):
         | 
| 439 | 
            +
                            pIndent = False
         | 
| 440 | 
            +
             | 
| 427 441 | 
             
                        # Process Text Types
         | 
| 428 442 | 
             
                        if tType == self.T_EMPTY:
         | 
| 429 443 | 
             
                            if len(pText) > 1 and pStyle is not None:
         | 
| @@ -437,7 +451,14 @@ class ToOdt(Tokenizer): | |
| 437 451 | 
             
                                    tLen = len(tTxt)
         | 
| 438 452 | 
             
                                    tTxt += f"{nText}\n"
         | 
| 439 453 | 
             
                                    tFmt.extend((p+tLen, fmt) for p, fmt in nFmt)
         | 
| 440 | 
            -
             | 
| 454 | 
            +
             | 
| 455 | 
            +
                                # Don't indent a paragraph if it has alignment set
         | 
| 456 | 
            +
                                tIndent = self._firstIndent and pIndent and pStyle.isUnaligned()
         | 
| 457 | 
            +
                                self._addTextPar(
         | 
| 458 | 
            +
                                    "First_20_line_20_indent" if tIndent else "Text_20_body",
         | 
| 459 | 
            +
                                    pStyle, tTxt.rstrip(), tFmt=tFmt
         | 
| 460 | 
            +
                                )
         | 
| 461 | 
            +
                                pIndent = True
         | 
| 441 462 |  | 
| 442 463 | 
             
                            pFmt = []
         | 
| 443 464 | 
             
                            pText = []
         | 
| @@ -447,10 +468,6 @@ class ToOdt(Tokenizer): | |
| 447 468 | 
             
                            tHead = tText.replace(nwHeadFmt.BR, "\n")
         | 
| 448 469 | 
             
                            self._addTextPar("Title", oStyle, tHead, isHead=False)  # Title must be text:p
         | 
| 449 470 |  | 
| 450 | 
            -
                        elif tType == self.T_UNNUM:
         | 
| 451 | 
            -
                            tHead = tText.replace(nwHeadFmt.BR, "\n")
         | 
| 452 | 
            -
                            self._addTextPar("Heading_20_2", oStyle, tHead, isHead=True, oLevel="2")
         | 
| 453 | 
            -
             | 
| 454 471 | 
             
                        elif tType == self.T_HEAD1:
         | 
| 455 472 | 
             
                            tHead = tText.replace(nwHeadFmt.BR, "\n")
         | 
| 456 473 | 
             
                            self._addTextPar("Heading_20_1", oStyle, tHead, isHead=True, oLevel="1")
         | 
| @@ -643,6 +660,10 @@ class ToOdt(Tokenizer): | |
| 643 660 | 
             
                            xFmt |= X_UND
         | 
| 644 661 | 
             
                        elif fFmt == self.FMT_U_E:
         | 
| 645 662 | 
             
                            xFmt &= M_UND
         | 
| 663 | 
            +
                        elif fFmt == self.FMT_M_B:
         | 
| 664 | 
            +
                            xFmt |= X_MRK
         | 
| 665 | 
            +
                        elif fFmt == self.FMT_M_E:
         | 
| 666 | 
            +
                            xFmt &= M_MRK
         | 
| 646 667 | 
             
                        elif fFmt == self.FMT_SUP_B:
         | 
| 647 668 | 
             
                            xFmt |= X_SUP
         | 
| 648 669 | 
             
                        elif fFmt == self.FMT_SUP_E:
         | 
| @@ -710,6 +731,8 @@ class ToOdt(Tokenizer): | |
| 710 731 | 
             
                        newStyle.setUnderlineStyle("solid")
         | 
| 711 732 | 
             
                        newStyle.setUnderlineWidth("auto")
         | 
| 712 733 | 
             
                        newStyle.setUnderlineColour("font-color")
         | 
| 734 | 
            +
                    if hFmt & X_MRK:
         | 
| 735 | 
            +
                        newStyle.setBackgroundColor(self._markText)
         | 
| 713 736 | 
             
                    if hFmt & X_SUP:
         | 
| 714 737 | 
             
                        newStyle.setTextPosition("super")
         | 
| 715 738 | 
             
                    if hFmt & X_SUB:
         | 
| @@ -850,6 +873,18 @@ class ToOdt(Tokenizer): | |
| 850 873 |  | 
| 851 874 | 
             
                    self._mainPara["Text_20_body"] = oStyle
         | 
| 852 875 |  | 
| 876 | 
            +
                    # Add First Line Indent Style
         | 
| 877 | 
            +
                    # ===========================
         | 
| 878 | 
            +
             | 
| 879 | 
            +
                    oStyle = ODTParagraphStyle()
         | 
| 880 | 
            +
                    oStyle.setDisplayName("First line indent")
         | 
| 881 | 
            +
                    oStyle.setParentStyleName("Text_20_body")
         | 
| 882 | 
            +
                    oStyle.setClass("text")
         | 
| 883 | 
            +
                    oStyle.setTextIndent(self._fTextIndent)
         | 
| 884 | 
            +
                    oStyle.packXML(self._xStyl, "First_20_line_20_indent")
         | 
| 885 | 
            +
             | 
| 886 | 
            +
                    self._mainPara["First_20_line_20_indent"] = oStyle
         | 
| 887 | 
            +
             | 
| 853 888 | 
             
                    # Add Text Meta Style
         | 
| 854 889 | 
             
                    # ===================
         | 
| 855 890 |  | 
| @@ -1077,6 +1112,7 @@ class ODTParagraphStyle: | |
| 1077 1112 | 
             
                        "margin-bottom": ["fo", None],
         | 
| 1078 1113 | 
             
                        "margin-left":   ["fo", None],
         | 
| 1079 1114 | 
             
                        "margin-right":  ["fo", None],
         | 
| 1115 | 
            +
                        "text-indent":   ["fo", None],
         | 
| 1080 1116 | 
             
                        "line-height":   ["fo", None],
         | 
| 1081 1117 | 
             
                        "text-align":    ["fo", None],
         | 
| 1082 1118 | 
             
                        "break-before":  ["fo", None],
         | 
| @@ -1095,6 +1131,16 @@ class ODTParagraphStyle: | |
| 1095 1131 |  | 
| 1096 1132 | 
             
                    return
         | 
| 1097 1133 |  | 
| 1134 | 
            +
                ##
         | 
| 1135 | 
            +
                #  Checkers
         | 
| 1136 | 
            +
                ##
         | 
| 1137 | 
            +
             | 
| 1138 | 
            +
                def isUnaligned(self) -> bool:
         | 
| 1139 | 
            +
                    """Check if paragraph has any sort of alignment or margins."""
         | 
| 1140 | 
            +
                    return all(
         | 
| 1141 | 
            +
                        self._pAttr[n][1] is None for n in ["text-align", "margin-left", "margin-right"]
         | 
| 1142 | 
            +
                    )
         | 
| 1143 | 
            +
             | 
| 1098 1144 | 
             
                ##
         | 
| 1099 1145 | 
             
                #  Attribute Setters
         | 
| 1100 1146 | 
             
                ##
         | 
| @@ -1145,6 +1191,10 @@ class ODTParagraphStyle: | |
| 1145 1191 | 
             
                    self._pAttr["margin-right"][1] = value
         | 
| 1146 1192 | 
             
                    return
         | 
| 1147 1193 |  | 
| 1194 | 
            +
                def setTextIndent(self, value: str | None) -> None:
         | 
| 1195 | 
            +
                    self._pAttr["text-indent"][1] = value
         | 
| 1196 | 
            +
                    return
         | 
| 1197 | 
            +
             | 
| 1148 1198 | 
             
                def setLineHeight(self, value: str | None) -> None:
         | 
| 1149 1199 | 
             
                    self._pAttr["line-height"][1] = value
         | 
| 1150 1200 | 
             
                    return
         | 
| @@ -1279,6 +1329,7 @@ class ODTTextStyle: | |
| 1279 1329 | 
             
                    self._tAttr = {
         | 
| 1280 1330 | 
             
                        "font-weight":             ["fo",    None],
         | 
| 1281 1331 | 
             
                        "font-style":              ["fo",    None],
         | 
| 1332 | 
            +
                        "background-color":        ["fo",    None],
         | 
| 1282 1333 | 
             
                        "text-position":           ["style", None],
         | 
| 1283 1334 | 
             
                        "text-line-through-style": ["style", None],
         | 
| 1284 1335 | 
             
                        "text-line-through-type":  ["style", None],
         | 
| @@ -1306,6 +1357,13 @@ class ODTTextStyle: | |
| 1306 1357 | 
             
                        self._tAttr["font-style"][1] = None
         | 
| 1307 1358 | 
             
                    return
         | 
| 1308 1359 |  | 
| 1360 | 
            +
                def setBackgroundColor(self, value: str | None) -> None:
         | 
| 1361 | 
            +
                    if value and len(value) == 7 and value[0] == "#":
         | 
| 1362 | 
            +
                        self._tAttr["background-color"][1] = value
         | 
| 1363 | 
            +
                    else:
         | 
| 1364 | 
            +
                        self._tAttr["background-color"][1] = None
         | 
| 1365 | 
            +
                    return
         | 
| 1366 | 
            +
             | 
| 1309 1367 | 
             
                def setTextPosition(self, value: str | None) -> None:
         | 
| 1310 1368 | 
             
                    if value in self.VALID_POS:
         | 
| 1311 1369 | 
             
                        self._tAttr["text-position"][1] = f"{value} 58%"
         | 
    
        novelwriter/core/tree.py
    CHANGED
    
    | @@ -28,7 +28,7 @@ import logging | |
| 28 28 |  | 
| 29 29 | 
             
            from typing import TYPE_CHECKING, Literal, overload
         | 
| 30 30 | 
             
            from pathlib import Path
         | 
| 31 | 
            -
            from collections.abc import Iterator
         | 
| 31 | 
            +
            from collections.abc import Iterable, Iterator
         | 
| 32 32 |  | 
| 33 33 | 
             
            from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
         | 
| 34 34 | 
             
            from novelwriter.error import logException
         | 
| @@ -351,16 +351,16 @@ class NWTree: | |
| 351 351 | 
             
                        return False
         | 
| 352 352 | 
             
                    return tItem.itemType == itemType
         | 
| 353 353 |  | 
| 354 | 
            -
                def getItemPath(self, tHandle: str) -> list[str]:
         | 
| 354 | 
            +
                def getItemPath(self, tHandle: str, asName: bool = False) -> list[str]:
         | 
| 355 355 | 
             
                    """Iterate upwards in the tree until we find the item with
         | 
| 356 | 
            -
                    parent None, the root item, and return the list of handles | 
| 357 | 
            -
                    We do this with a for loop with a | 
| 358 | 
            -
                    infinite loops impossible.
         | 
| 356 | 
            +
                    parent None, the root item, and return the list of handles, or
         | 
| 357 | 
            +
                    alternatively item names. We do this with a for loop with a
         | 
| 358 | 
            +
                    maximum depth to make infinite loops impossible.
         | 
| 359 359 | 
             
                    """
         | 
| 360 360 | 
             
                    tTree = []
         | 
| 361 361 | 
             
                    tItem = self.__getitem__(tHandle)
         | 
| 362 362 | 
             
                    if tItem is not None:
         | 
| 363 | 
            -
                        tTree.append(tHandle)
         | 
| 363 | 
            +
                        tTree.append(tItem.itemName if asName else tHandle)
         | 
| 364 364 | 
             
                        for _ in range(MAX_DEPTH):
         | 
| 365 365 | 
             
                            if tItem.itemParent is None:
         | 
| 366 366 | 
             
                                return tTree
         | 
| @@ -370,7 +370,7 @@ class NWTree: | |
| 370 370 | 
             
                                if tItem is None:
         | 
| 371 371 | 
             
                                    return tTree
         | 
| 372 372 | 
             
                                else:
         | 
| 373 | 
            -
                                    tTree.append(tHandle)
         | 
| 373 | 
            +
                                    tTree.append(tItem.itemName if asName else tHandle)
         | 
| 374 374 | 
             
                        else:
         | 
| 375 375 | 
             
                            raise RecursionError("Critical internal error")
         | 
| 376 376 |  | 
| @@ -387,7 +387,7 @@ class NWTree: | |
| 387 387 | 
             
                        rootClasses.add(nwItem.itemClass)
         | 
| 388 388 | 
             
                    return rootClasses
         | 
| 389 389 |  | 
| 390 | 
            -
                def iterRoots(self, itemClass: nwItemClass | None) ->  | 
| 390 | 
            +
                def iterRoots(self, itemClass: nwItemClass | None) -> Iterable[tuple[str, NWItem]]:
         | 
| 391 391 | 
             
                    """Iterate over all root items of a given class in order."""
         | 
| 392 392 | 
             
                    for tHandle in self._order:
         | 
| 393 393 | 
             
                        nwItem = self.__getitem__(tHandle)
         | 
    
        novelwriter/dialogs/docsplit.py
    CHANGED
    
    | @@ -57,7 +57,7 @@ class GuiDocSplit(QDialog): | |
| 57 57 |  | 
| 58 58 | 
             
                    self.setWindowTitle(self.tr("Split Document"))
         | 
| 59 59 |  | 
| 60 | 
            -
                    self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Document  | 
| 60 | 
            +
                    self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Document Headings")))
         | 
| 61 61 | 
             
                    self.helpLabel = NColourLabel(
         | 
| 62 62 | 
             
                        self.tr("Select the maximum level to split into files."),
         | 
| 63 63 | 
             
                        SHARED.theme.helpText, parent=self, wrap=True
         | 
| @@ -74,17 +74,17 @@ class GuiDocSplit(QDialog): | |
| 74 74 | 
             
                    intoFolder = pOptions.getBool("GuiDocSplit", "intoFolder", True)
         | 
| 75 75 | 
             
                    docHierarchy = pOptions.getBool("GuiDocSplit", "docHierarchy", True)
         | 
| 76 76 |  | 
| 77 | 
            -
                    #  | 
| 77 | 
            +
                    # Heading Selection
         | 
| 78 78 | 
             
                    self.listBox = QListWidget()
         | 
| 79 79 | 
             
                    self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
         | 
| 80 80 | 
             
                    self.listBox.setMinimumWidth(CONFIG.pxInt(400))
         | 
| 81 81 | 
             
                    self.listBox.setMinimumHeight(CONFIG.pxInt(180))
         | 
| 82 82 |  | 
| 83 83 | 
             
                    self.splitLevel = QComboBox(self)
         | 
| 84 | 
            -
                    self.splitLevel.addItem(self.tr("Split on  | 
| 85 | 
            -
                    self.splitLevel.addItem(self.tr("Split up to  | 
| 86 | 
            -
                    self.splitLevel.addItem(self.tr("Split up to  | 
| 87 | 
            -
                    self.splitLevel.addItem(self.tr("Split up to  | 
| 84 | 
            +
                    self.splitLevel.addItem(self.tr("Split on Heading Level 1 (Partition)"),      1)
         | 
| 85 | 
            +
                    self.splitLevel.addItem(self.tr("Split up to Heading Level 2 (Chapter)"), 2)
         | 
| 86 | 
            +
                    self.splitLevel.addItem(self.tr("Split up to Heading Level 3 (Scene)"),   3)
         | 
| 87 | 
            +
                    self.splitLevel.addItem(self.tr("Split up to Heading Level 4 (Section)"), 4)
         | 
| 88 88 | 
             
                    spIndex = self.splitLevel.findData(spLevel)
         | 
| 89 89 | 
             
                    if spIndex != -1:
         | 
| 90 90 | 
             
                        self.splitLevel.setCurrentIndex(spIndex)
         | 
| @@ -214,8 +214,7 @@ class GuiDocSplit(QDialog): | |
| 214 214 |  | 
| 215 215 | 
             
                    spLevel = self.splitLevel.currentData()
         | 
| 216 216 | 
             
                    if not self._text:
         | 
| 217 | 
            -
                         | 
| 218 | 
            -
                        self._text = (inDoc.readDocument() or "").splitlines()
         | 
| 217 | 
            +
                        self._text = SHARED.project.storage.getDocumentText(sHandle).splitlines()
         | 
| 219 218 |  | 
| 220 219 | 
             
                    for lineNo, aLine in enumerate(self._text):
         | 
| 221 220 |  | 
| @@ -151,8 +151,7 @@ class GuiPreferences(QDialog): | |
| 151 151 | 
             
                    self.guiLocale.setMinimumWidth(minWidth)
         | 
| 152 152 | 
             
                    for lang, name in CONFIG.listLanguages(CONFIG.LANG_NW):
         | 
| 153 153 | 
             
                        self.guiLocale.addItem(name, lang)
         | 
| 154 | 
            -
                     | 
| 155 | 
            -
                        self.guiLocale.setCurrentIndex(idx)
         | 
| 154 | 
            +
                    self.guiLocale.setCurrentData(CONFIG.guiLocale, "en_GB")
         | 
| 156 155 |  | 
| 157 156 | 
             
                    self.mainForm.addRow(
         | 
| 158 157 | 
             
                        self.tr("Display language"), self.guiLocale,
         | 
| @@ -164,8 +163,7 @@ class GuiPreferences(QDialog): | |
| 164 163 | 
             
                    self.guiTheme.setMinimumWidth(minWidth)
         | 
| 165 164 | 
             
                    for theme, name in SHARED.theme.listThemes():
         | 
| 166 165 | 
             
                        self.guiTheme.addItem(name, theme)
         | 
| 167 | 
            -
                     | 
| 168 | 
            -
                        self.guiTheme.setCurrentIndex(idx)
         | 
| 166 | 
            +
                    self.guiTheme.setCurrentData(CONFIG.guiTheme, "default")
         | 
| 169 167 |  | 
| 170 168 | 
             
                    self.mainForm.addRow(
         | 
| 171 169 | 
             
                        self.tr("Colour theme"), self.guiTheme,
         | 
| @@ -226,8 +224,7 @@ class GuiPreferences(QDialog): | |
| 226 224 | 
             
                    self.guiSyntax.setMinimumWidth(CONFIG.pxInt(200))
         | 
| 227 225 | 
             
                    for syntax, name in SHARED.theme.listSyntax():
         | 
| 228 226 | 
             
                        self.guiSyntax.addItem(name, syntax)
         | 
| 229 | 
            -
                     | 
| 230 | 
            -
                        self.guiSyntax.setCurrentIndex(idx)
         | 
| 227 | 
            +
                    self.guiSyntax.setCurrentData(CONFIG.guiSyntax, "default_light")
         | 
| 231 228 |  | 
| 232 229 | 
             
                    self.mainForm.addRow(
         | 
| 233 230 | 
             
                        self.tr("Document colour theme"), self.guiSyntax,
         | 
    
        novelwriter/enum.py
    CHANGED
    
    | @@ -112,20 +112,22 @@ class nwDocAction(Enum): | |
| 112 112 | 
             
                BLOCK_TXT = 19
         | 
| 113 113 | 
             
                BLOCK_TTL = 20
         | 
| 114 114 | 
             
                BLOCK_UNN = 21
         | 
| 115 | 
            -
                 | 
| 116 | 
            -
                 | 
| 117 | 
            -
                 | 
| 118 | 
            -
                 | 
| 119 | 
            -
                 | 
| 120 | 
            -
                 | 
| 121 | 
            -
                 | 
| 122 | 
            -
                 | 
| 123 | 
            -
                 | 
| 124 | 
            -
                 | 
| 125 | 
            -
                 | 
| 126 | 
            -
                 | 
| 127 | 
            -
                 | 
| 128 | 
            -
                 | 
| 115 | 
            +
                BLOCK_HSC = 22
         | 
| 116 | 
            +
                REPL_SNG  = 23
         | 
| 117 | 
            +
                REPL_DBL  = 24
         | 
| 118 | 
            +
                RM_BREAKS = 25
         | 
| 119 | 
            +
                ALIGN_L   = 26
         | 
| 120 | 
            +
                ALIGN_C   = 27
         | 
| 121 | 
            +
                ALIGN_R   = 28
         | 
| 122 | 
            +
                INDENT_L  = 29
         | 
| 123 | 
            +
                INDENT_R  = 30
         | 
| 124 | 
            +
                SC_ITALIC = 31
         | 
| 125 | 
            +
                SC_BOLD   = 32
         | 
| 126 | 
            +
                SC_STRIKE = 33
         | 
| 127 | 
            +
                SC_ULINE  = 34
         | 
| 128 | 
            +
                SC_MARK   = 35
         | 
| 129 | 
            +
                SC_SUP    = 36
         | 
| 130 | 
            +
                SC_SUB    = 37
         | 
| 129 131 |  | 
| 130 132 | 
             
            # END Enum nwDocAction
         | 
| 131 133 |  | 
| @@ -153,6 +155,7 @@ class nwView(Enum): | |
| 153 155 | 
             
                PROJECT = 1
         | 
| 154 156 | 
             
                NOVEL   = 2
         | 
| 155 157 | 
             
                OUTLINE = 3
         | 
| 158 | 
            +
                SEARCH  = 4
         | 
| 156 159 |  | 
| 157 160 | 
             
            # END Enum nwView
         | 
| 158 161 |  | 
| @@ -25,9 +25,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. | |
| 25 25 | 
             
            """
         | 
| 26 26 | 
             
            from __future__ import annotations
         | 
| 27 27 |  | 
| 28 | 
            -
            from PyQt5.QtCore import Qt
         | 
| 28 | 
            +
            from PyQt5.QtCore import QSize, Qt
         | 
| 29 29 | 
             
            from PyQt5.QtGui import QWheelEvent
         | 
| 30 | 
            -
            from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QWidget
         | 
| 30 | 
            +
            from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QToolButton, QWidget
         | 
| 31 31 |  | 
| 32 32 |  | 
| 33 33 | 
             
            class NComboBox(QComboBox):
         | 
| @@ -44,6 +44,12 @@ class NComboBox(QComboBox): | |
| 44 44 | 
             
                        event.ignore()
         | 
| 45 45 | 
             
                    return
         | 
| 46 46 |  | 
| 47 | 
            +
                def setCurrentData(self, data: str, default: str) -> None:
         | 
| 48 | 
            +
                    """Set the current index from data, with a fallback."""
         | 
| 49 | 
            +
                    idx = self.findData(data)
         | 
| 50 | 
            +
                    self.setCurrentIndex(self.findData(default) if idx < 0 else idx)
         | 
| 51 | 
            +
                    return
         | 
| 52 | 
            +
             | 
| 47 53 | 
             
            # END Class NComboBox
         | 
| 48 54 |  | 
| 49 55 |  | 
| @@ -79,3 +85,15 @@ class NDoubleSpinBox(QDoubleSpinBox): | |
| 79 85 | 
             
                    return
         | 
| 80 86 |  | 
| 81 87 | 
             
            # END Class NDoubleSpinBox
         | 
| 88 | 
            +
             | 
| 89 | 
            +
             | 
| 90 | 
            +
            class NIconToolButton(QToolButton):
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                def __init__(self, parent: QWidget, iconSize: int) -> None:
         | 
| 93 | 
            +
                    super().__init__(parent=parent)
         | 
| 94 | 
            +
                    self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
         | 
| 95 | 
            +
                    self.setIconSize(QSize(iconSize, iconSize))
         | 
| 96 | 
            +
                    self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
         | 
| 97 | 
            +
                    return
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            # END Class NIconToolButton
         | 
| @@ -57,7 +57,7 @@ class VersionInfoWidget(QWidget): | |
| 57 57 | 
             
                    # Labels
         | 
| 58 58 | 
             
                    self._lblInfo = QLabel("{0} {1} \u2013 {2} {3} \u2013 {4}".format(
         | 
| 59 59 | 
             
                        self.tr("Version"), formatVersion(__version__),
         | 
| 60 | 
            -
                        self.tr("Released on"), datetime.strptime(__date__, "%Y-%m-%d") | 
| 60 | 
            +
                        self.tr("Released on"), CONFIG.localDate(datetime.strptime(__date__, "%Y-%m-%d")),
         | 
| 61 61 | 
             
                        "<a href='#notes'>{0}</a>".format(self.tr("Release Notes")),
         | 
| 62 62 | 
             
                    ), self)
         | 
| 63 63 | 
             
                    self._lblInfo.linkActivated.connect(self._processLink)
         |