diff --git a/CREDITS.md b/CREDITS.md index c058105e..913530c3 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -58,9 +58,11 @@ Some of the assets bundled with novelWriter were adapted from the following sour ## Fonts -The font used for the main novelWriter logo, mimetype and text banners is: +The font used for the main novelWriter logo, mimetype and text banners is Pridi. Other fonts are +used on buttons and icons. * Pridi by Cadson Demak (Open Font License, Version 1.1) +* Source Sans Pro by Paul D. Hunt (SIL Open Font License) ## Special Mentions diff --git a/docs/source/int_customise.rst b/docs/source/int_customise.rst index 7124b3a9..a44e8b75 100644 --- a/docs/source/int_customise.rst +++ b/docs/source/int_customise.rst @@ -179,15 +179,20 @@ A syntax theme ``.conf`` file consists of the following settings: errorline = 0, 0, 0 replacetag = 0, 0, 0 modifier = 0, 0, 0 + texthighlight = 255, 255, 255, 128 In the Main section, you must define at least the ``name`` setting. The Syntax colour values are -RGB numbers of the format ``r, g, b`` where each is an integer from to 255. Omitted values default -to black, except ``background`` which defaults to white, +RGB(A) numbers of the format ``r, g, b, a`` where each is an integer from to 255. The fourth value +is the alpha channel, which can be omitted. + +Omitted syntax colours default to black, except ``background`` which defaults to white, and +``texthighlight`` which defaults to grey. .. versionadded:: 2.2 - The `shortcode` syntax colour entry was added, so you need to update your custom themes if you - made any before version 2.2. + The `shortcode` syntax colour entry was added. .. versionadded:: 2.3 - The `optional` syntax colour entry was added, so you need to update your custom themes if you - made any before version 2.3. + The `optional` syntax colour entry was added. + +.. versionadded:: 2.4 + The `texthighlight` syntax colour entry was added. diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf index 250bac57..d5725121 100644 --- a/novelwriter/assets/icons/typicons_dark/icons.conf +++ b/novelwriter/assets/icons/typicons_dark/icons.conf @@ -53,6 +53,7 @@ fmt_bold = nw_tb-bold.svg fmt_bold-md = nw_tb-bold-md.svg fmt_italic = nw_tb-italic.svg fmt_italic-md = nw_tb-italic-md.svg +fmt_mark = nw_tb-mark.svg fmt_strike = nw_tb-strike.svg fmt_strike-md = nw_tb-strike-md.svg fmt_subscript = nw_tb-subscript.svg diff --git a/novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg b/novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg new file mode 100644 index 00000000..5539581a --- /dev/null +++ b/novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf index 65419898..5720ba58 100644 --- a/novelwriter/assets/icons/typicons_light/icons.conf +++ b/novelwriter/assets/icons/typicons_light/icons.conf @@ -53,6 +53,7 @@ fmt_bold = nw_tb-bold.svg fmt_bold-md = nw_tb-bold-md.svg fmt_italic = nw_tb-italic.svg fmt_italic-md = nw_tb-italic-md.svg +fmt_mark = nw_tb-mark.svg fmt_strike = nw_tb-strike.svg fmt_strike-md = nw_tb-strike-md.svg fmt_subscript = nw_tb-subscript.svg diff --git a/novelwriter/assets/icons/typicons_light/nw_tb-mark.svg b/novelwriter/assets/icons/typicons_light/nw_tb-mark.svg new file mode 100644 index 00000000..dcf138de --- /dev/null +++ b/novelwriter/assets/icons/typicons_light/nw_tb-mark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/novelwriter/assets/syntax/default_dark.conf b/novelwriter/assets/syntax/default_dark.conf index 2743b91a..1e4939d9 100644 --- a/novelwriter/assets/syntax/default_dark.conf +++ b/novelwriter/assets/syntax/default_dark.conf @@ -25,3 +25,4 @@ spellcheckline = 200, 46, 0 errorline = 46, 200, 0 replacetag = 0, 184, 46 modifier = 200, 120, 0 +texthighlight = 136, 200, 0, 96 diff --git a/novelwriter/assets/syntax/default_light.conf b/novelwriter/assets/syntax/default_light.conf index eff5c4a8..94619291 100644 --- a/novelwriter/assets/syntax/default_light.conf +++ b/novelwriter/assets/syntax/default_light.conf @@ -25,3 +25,4 @@ spellcheckline = 200, 0, 0 errorline = 0, 150, 0 replacetag = 0, 150, 0 modifier = 150, 110, 30 +texthighlight = 150, 150, 0, 96 diff --git a/novelwriter/assets/syntax/grey_dark.conf b/novelwriter/assets/syntax/grey_dark.conf index b1e2081c..8a71c293 100644 --- a/novelwriter/assets/syntax/grey_dark.conf +++ b/novelwriter/assets/syntax/grey_dark.conf @@ -25,3 +25,4 @@ spellcheckline = 200, 46, 0 errorline = 46, 200, 0 replacetag = 225, 225, 225 modifier = 225, 225, 225 +texthighlight = 255, 255, 255, 64 diff --git a/novelwriter/assets/syntax/grey_light.conf b/novelwriter/assets/syntax/grey_light.conf index 06f43522..879f8f53 100644 --- a/novelwriter/assets/syntax/grey_light.conf +++ b/novelwriter/assets/syntax/grey_light.conf @@ -25,3 +25,4 @@ spellcheckline = 200, 0, 0 errorline = 0, 150, 0 replacetag = 0, 0, 0 modifier = 0, 0, 0 +texthighlight = 0, 0, 0, 64 diff --git a/novelwriter/assets/syntax/light_owl.conf b/novelwriter/assets/syntax/light_owl.conf index 63723094..2704586f 100644 --- a/novelwriter/assets/syntax/light_owl.conf +++ b/novelwriter/assets/syntax/light_owl.conf @@ -45,3 +45,4 @@ spellcheckline = 222, 61, 58 errorline = 8, 145, 106 replacetag = 42, 162, 152 modifier = 224, 175, 5 +texthighlight = 218, 170, 1, 96 diff --git a/novelwriter/assets/syntax/night_owl.conf b/novelwriter/assets/syntax/night_owl.conf index 6d817ef5..b45a2db2 100644 --- a/novelwriter/assets/syntax/night_owl.conf +++ b/novelwriter/assets/syntax/night_owl.conf @@ -45,3 +45,4 @@ spellcheckline = 247, 140, 108 errorline = 173, 219, 103 replacetag = 127, 219, 202 modifier = 236, 196, 141 +texthighlight = 255, 235, 149, 96 diff --git a/novelwriter/assets/syntax/solarized_dark.conf b/novelwriter/assets/syntax/solarized_dark.conf index 9b473923..1108acd2 100644 --- a/novelwriter/assets/syntax/solarized_dark.conf +++ b/novelwriter/assets/syntax/solarized_dark.conf @@ -25,3 +25,4 @@ spellcheckline = 203, 75, 22 errorline = 220, 50, 47 replacetag = 133, 153, 0 modifier = 181, 137, 0 +texthighlight = 181, 137, 0, 96 diff --git a/novelwriter/assets/syntax/solarized_light.conf b/novelwriter/assets/syntax/solarized_light.conf index c94a30fc..daf6cc65 100644 --- a/novelwriter/assets/syntax/solarized_light.conf +++ b/novelwriter/assets/syntax/solarized_light.conf @@ -25,3 +25,4 @@ spellcheckline = 203, 75, 22 errorline = 220, 50, 47 replacetag = 133, 153, 0 modifier = 181, 137, 0 +texthighlight = 181, 137, 0, 96 diff --git a/novelwriter/assets/syntax/tomorrow.conf b/novelwriter/assets/syntax/tomorrow.conf index aa16138a..39826a26 100644 --- a/novelwriter/assets/syntax/tomorrow.conf +++ b/novelwriter/assets/syntax/tomorrow.conf @@ -45,3 +45,4 @@ spellcheckline = 240, 40, 41 errorline = 113, 140, 0 replacetag = 62, 153, 159 modifier = 245, 135, 31 +texthighlight = 234, 183, 0, 96 diff --git a/novelwriter/assets/syntax/tomorrow_night.conf b/novelwriter/assets/syntax/tomorrow_night.conf index af951937..6b5673ed 100644 --- a/novelwriter/assets/syntax/tomorrow_night.conf +++ b/novelwriter/assets/syntax/tomorrow_night.conf @@ -45,3 +45,4 @@ spellcheckline = 204, 102, 102 errorline = 181, 189, 104 replacetag = 138, 190, 183 modifier = 222, 147, 95 +texthighlight = 240, 198, 116, 96 diff --git a/novelwriter/assets/syntax/tomorrow_night_blue.conf b/novelwriter/assets/syntax/tomorrow_night_blue.conf index 939dc146..4b0e19fc 100644 --- a/novelwriter/assets/syntax/tomorrow_night_blue.conf +++ b/novelwriter/assets/syntax/tomorrow_night_blue.conf @@ -45,3 +45,4 @@ spellcheckline = 255, 157, 164 errorline = 209, 241, 169 replacetag = 153, 255, 255 modifier = 255, 197, 143 +texthighlight = 255, 238, 173, 96 diff --git a/novelwriter/assets/syntax/tomorrow_night_bright.conf b/novelwriter/assets/syntax/tomorrow_night_bright.conf index d3005072..30f22216 100644 --- a/novelwriter/assets/syntax/tomorrow_night_bright.conf +++ b/novelwriter/assets/syntax/tomorrow_night_bright.conf @@ -45,3 +45,4 @@ spellcheckline = 213, 78, 83 errorline = 185, 202, 74 replacetag = 112, 192, 177 modifier = 231, 140, 69 +texthighlight = 231, 197, 71, 128 diff --git a/novelwriter/assets/syntax/tomorrow_night_eighties.conf b/novelwriter/assets/syntax/tomorrow_night_eighties.conf index 7773a9df..0b96bf20 100644 --- a/novelwriter/assets/syntax/tomorrow_night_eighties.conf +++ b/novelwriter/assets/syntax/tomorrow_night_eighties.conf @@ -45,3 +45,4 @@ spellcheckline = 242, 119, 122 errorline = 153, 204, 153 replacetag = 102, 204, 204 modifier = 249, 145, 57 +texthighlight = 255, 204, 102, 96 diff --git a/novelwriter/assets/text/credits_en.htm b/novelwriter/assets/text/credits_en.htm index 3733090e..35129dcf 100644 --- a/novelwriter/assets/text/credits_en.htm +++ b/novelwriter/assets/text/credits_en.htm @@ -8,9 +8,9 @@

Contributors

- Concept: Marian Lückhof (@Number042)
- Internationalisation: Bruno Meneguello (@bkmeneguello)
- Setup and Packaging: Rachel Powers (@Ryex) + • Concept: Marian Lückhof (@Number042)
+ • Internationalisation: Bruno Meneguello (@bkmeneguello)
+ • Setup and Packaging: Rachel Powers (@Ryex)

For other contributions, see the project's Contributors page.

@@ -24,20 +24,20 @@

The default language is English (UK) with English (US) as an option. These are the original translators for the languages currently available:

- Dutch: Martijn van der Kleijn (@mvdkleijn)
- French: Jan Lüdke (@jyhelle)
- German: Myian (@heymyian)
- Italian: Riccardo Mangili
- Japanese: hebekeg (@hebekeg)
- Latin American Spanish: Tommy Marplatt (@tmarplatt)
- Norwegian: Veronica Berglyd Olsen (@vkbo)
- Portuguese: Bruno Meneguello (@bkmeneguello)
- Simplified Chinese: Qianzhi Long (@longqzh) + • Dutch: Martijn van der Kleijn (@mvdkleijn)
+ • French: Jan Lüdke (@jyhelle)
+ • German: Myian (@heymyian)
+ • Italian: Riccardo Mangili
+ • Japanese: hebekeg (@hebekeg)
+ • Latin American Spanish: Tommy Marplatt (@tmarplatt)
+ • Norwegian: Veronica Berglyd Olsen (@vkbo)
+ • Portuguese: Bruno Meneguello (@bkmeneguello)
+ • Simplified Chinese: Qianzhi Long (@longqzh)

Additional larger translation contributions:

- French: Albert Aribaud (@aaribaud) + • French: Albert Aribaud (@aaribaud)

Translations are managed on Crowdin, and @@ -47,27 +47,29 @@ more contributions are listed on the project's Qt5 by Qt Company
- PyQt5 by Riverbank Computing
- Enchant by Dom Lachowicz
- PyEnchant by Dimitri Merejkowsky + • Qt5 by Qt Company
+ • PyQt5 by Riverbank Computing
+ • Enchant by Dom Lachowicz
+ • PyEnchant by Dimitri Merejkowsky

Assets

Some of the assets bundled with novelWriter were adapted from the following sources:

- Typicons icons by Stephen Hutchings (CC BY-SA 4.0)
- Tomorrow syntax themes by Chris Kempson (MIT License)
- Owl syntax themes by Sarah Drasner (MIT License)
- Solarized themes by Ethan Schoonover (MIT License) + • Typicons icons by Stephen Hutchings (CC BY-SA 4.0)
+ • Tomorrow syntax themes by Chris Kempson (MIT License)
+ • Owl syntax themes by Sarah Drasner (MIT License)
+ • Solarized themes by Ethan Schoonover (MIT License)

Fonts

-

The font used for the main novelWriter logo, mimetype and text banners is:

+

The font used for the main novelWriter logo, mimetype and text banners is Pridi. Other fonts are +used on buttons and icons.

- Pridi by Cadson Demak (Open Font License, Version 1.1) + • Pridi by Cadson Demak (Open Font License, Version 1.1)
+ • Source Sans Pro by Paul D. Hunt (SIL Open Font License)

Special Mentions

diff --git a/novelwriter/constants.py b/novelwriter/constants.py index 39fd7a69..0ba85a57 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -67,7 +67,7 @@ class nwRegEx: FMT_EI = r"(?", self.FMT_U_B: "", self.FMT_U_E: "", + self.FMT_M_B: "", + self.FMT_M_E: "", } else: htmlTags = { # HTML5 (for export) @@ -144,6 +146,8 @@ class ToHtml(Tokenizer): self.FMT_D_E: "", self.FMT_U_B: "", self.FMT_U_E: "", + self.FMT_M_B: "", + self.FMT_M_E: "", } htmlTags[self.FMT_SUP_B] = "" diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index ecd33414..dd9ff0a6 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -74,10 +74,12 @@ class Tokenizer(ABC): FMT_D_E = 6 # End strikeout FMT_U_B = 7 # Begin underline FMT_U_E = 8 # End underline - FMT_SUP_B = 9 # Begin superscript - FMT_SUP_E = 10 # End superscript - FMT_SUB_B = 11 # Begin subscript - FMT_SUB_E = 12 # End subscript + FMT_M_B = 9 # Begin mark + FMT_M_E = 10 # End mark + FMT_SUP_B = 11 # Begin superscript + FMT_SUP_E = 12 # End superscript + FMT_SUB_B = 13 # Begin subscript + FMT_SUB_E = 14 # End subscript # Block Type T_EMPTY = 1 # Empty line (new paragraph) @@ -187,6 +189,7 @@ class Tokenizer(ABC): nwShortcode.BOLD_O: self.FMT_B_B, nwShortcode.BOLD_C: self.FMT_B_E, nwShortcode.STRIKE_O: self.FMT_D_B, nwShortcode.STRIKE_C: self.FMT_D_E, nwShortcode.ULINE_O: self.FMT_U_B, nwShortcode.ULINE_C: self.FMT_U_E, + nwShortcode.MARK_O: self.FMT_M_B, nwShortcode.MARK_C: self.FMT_M_E, nwShortcode.SUP_O: self.FMT_SUP_B, nwShortcode.SUP_C: self.FMT_SUP_E, nwShortcode.SUB_O: self.FMT_SUB_B, nwShortcode.SUB_C: self.FMT_SUB_E, } diff --git a/novelwriter/core/tomd.py b/novelwriter/core/tomd.py index ad51e3b7..fbc2e463 100644 --- a/novelwriter/core/tomd.py +++ b/novelwriter/core/tomd.py @@ -95,6 +95,8 @@ class ToMarkdown(Tokenizer): self.FMT_D_E: "", self.FMT_U_B: "", self.FMT_U_E: "", + self.FMT_M_B: "", + self.FMT_M_E: "", self.FMT_SUP_B: "", self.FMT_SUP_E: "", self.FMT_SUB_B: "", @@ -111,6 +113,8 @@ class ToMarkdown(Tokenizer): self.FMT_D_E: "~~", self.FMT_U_B: "", self.FMT_U_E: "", + self.FMT_M_B: "==", + self.FMT_M_E: "==", self.FMT_SUP_B: "^", self.FMT_SUP_E: "^", self.FMT_SUB_B: "~", diff --git a/novelwriter/core/toodt.py b/novelwriter/core/toodt.py index e4bf57c5..494b12c4 100644 --- a/novelwriter/core/toodt.py +++ b/novelwriter/core/toodt.py @@ -84,14 +84,16 @@ X_BLD = 0x01 # Bold format X_ITA = 0x02 # Italic format X_DEL = 0x04 # Strikethrough format X_UND = 0x08 # Underline format -X_SUP = 0x10 # Superscript -X_SUB = 0x20 # Subscript +X_MRK = 0x10 # Marked format +X_SUP = 0x20 # Superscript +X_SUB = 0x40 # Subscript # Formatting Masks M_BLD = ~X_BLD M_ITA = ~X_ITA M_DEL = ~X_DEL M_UND = ~X_UND +M_MRK = ~X_MRK M_SUP = ~X_SUP M_SUB = ~X_SUB @@ -189,6 +191,7 @@ class ToOdt(Tokenizer): self._opaHead34 = None self._colMetaTx = None self._opaMetaTx = None + self._markText = "#ffffa6" return @@ -643,6 +646,10 @@ class ToOdt(Tokenizer): xFmt |= X_UND elif fFmt == self.FMT_U_E: xFmt &= M_UND + elif fFmt == self.FMT_M_B: + xFmt |= X_MRK + elif fFmt == self.FMT_M_E: + xFmt &= M_MRK elif fFmt == self.FMT_SUP_B: xFmt |= X_SUP elif fFmt == self.FMT_SUP_E: @@ -710,6 +717,8 @@ class ToOdt(Tokenizer): newStyle.setUnderlineStyle("solid") newStyle.setUnderlineWidth("auto") newStyle.setUnderlineColour("font-color") + if hFmt & X_MRK: + newStyle.setBackgroundColor(self._markText) if hFmt & X_SUP: newStyle.setTextPosition("super") if hFmt & X_SUB: @@ -1279,6 +1288,7 @@ class ODTTextStyle: self._tAttr = { "font-weight": ["fo", None], "font-style": ["fo", None], + "background-color": ["fo", None], "text-position": ["style", None], "text-line-through-style": ["style", None], "text-line-through-type": ["style", None], @@ -1306,6 +1316,13 @@ class ODTTextStyle: self._tAttr["font-style"][1] = None return + def setBackgroundColor(self, value: str | None) -> None: + if value and len(value) == 7 and value[0] == "#": + self._tAttr["background-color"][1] = value + else: + self._tAttr["background-color"][1] = None + return + def setTextPosition(self, value: str | None) -> None: if value in self.VALID_POS: self._tAttr["text-position"][1] = f"{value} 58%" diff --git a/novelwriter/enum.py b/novelwriter/enum.py index aa9106e3..85724c95 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -124,8 +124,9 @@ class nwDocAction(Enum): SC_BOLD = 31 SC_STRIKE = 32 SC_ULINE = 33 - SC_SUP = 34 - SC_SUB = 35 + SC_MARK = 34 + SC_SUP = 35 + SC_SUB = 36 # END Enum nwDocAction diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 192b00ef..33f6672b 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -769,6 +769,8 @@ class GuiDocEditor(QPlainTextEdit): self._wrapSelection(nwShortcode.STRIKE_O, nwShortcode.STRIKE_C) elif action == nwDocAction.SC_ULINE: self._wrapSelection(nwShortcode.ULINE_O, nwShortcode.ULINE_C) + elif action == nwDocAction.SC_MARK: + self._wrapSelection(nwShortcode.MARK_O, nwShortcode.MARK_C) elif action == nwDocAction.SC_SUP: self._wrapSelection(nwShortcode.SUP_O, nwShortcode.SUP_C) elif action == nwDocAction.SC_SUB: @@ -2313,6 +2315,13 @@ class GuiDocToolBar(QWidget): lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE) ) + self.tbMark = QToolButton(self) + self.tbMark.setIconSize(iconSize) + self.tbMark.setToolTip(self.tr("Shortcode Highlight")) + self.tbMark.clicked.connect( + lambda: self.requestDocAction.emit(nwDocAction.SC_MARK) + ) + self.tbSuperscript = QToolButton(self) self.tbSuperscript.setIconSize(iconSize) self.tbSuperscript.setToolTip(self.tr("Shortcode Superscript")) @@ -2339,6 +2348,7 @@ class GuiDocToolBar(QWidget): self.outerBox.addWidget(self.tbItalic) self.outerBox.addWidget(self.tbStrike) self.outerBox.addWidget(self.tbUnderline) + self.outerBox.addWidget(self.tbMark) self.outerBox.addWidget(self.tbSuperscript) self.outerBox.addWidget(self.tbSubscript) self.outerBox.setContentsMargins(cM, cM, cM, cM) @@ -2369,6 +2379,7 @@ class GuiDocToolBar(QWidget): self.tbItalic.setIcon(SHARED.theme.getIcon("fmt_italic")) self.tbStrike.setIcon(SHARED.theme.getIcon("fmt_strike")) self.tbUnderline.setIcon(SHARED.theme.getIcon("fmt_underline")) + self.tbMark.setIcon(SHARED.theme.getIcon("fmt_mark")) self.tbSuperscript.setIcon(SHARED.theme.getIcon("fmt_superscript")) self.tbSubscript.setIcon(SHARED.theme.getIcon("fmt_subscript")) diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index b6ca8480..52a07f12 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -466,48 +466,30 @@ class GuiDocViewer(QTextBrowser): colText = SHARED.theme.colText colHead = SHARED.theme.colHead colVals = SHARED.theme.colVal - colEmph = SHARED.theme.colEmph + colMark = SHARED.theme.colMark colKeys = SHARED.theme.colKey colHide = SHARED.theme.colHidden colMods = SHARED.theme.colMod colOpts = SHARED.theme.colOpt styleSheet = ( - "body {{" - " color: rgb({tColR}, {tColG}, {tColB});" - "}}\n" - "h1, h2, h3, h4 {{" - " color: rgb({hColR}, {hColG}, {hColB});" - "}}\n" - "a {{" - " color: rgb({aColR}, {aColG}, {aColB});" - "}}\n" - "mark {{" - " color: rgb({eColR}, {eColG}, {eColB});" - "}}\n" - ".tags {{" - " color: rgb({kColR}, {kColG}, {kColB});" - "}}\n" - ".optional {{" - " color: rgb({oColR}, {oColG}, {oColB});" - "}}\n" - ".comment {{" - " color: rgb({cColR}, {cColG}, {cColB});" - "}}\n" - ".synopsis {{" - " color: rgb({mColR}, {mColG}, {mColB});" - "}}\n" - ".title {{" - " text-align: center;" - "}}\n" + "body {{color: rgb({rT}, {gT}, {bT});}}\n" + "h1, h2, h3, h4 {{color: rgb({rH}, {gH}, {bH});}}\n" + "a {{color: rgb({rA}, {gA}, {bA});}}\n" + "mark {{background-color: rgba({rE}, {gE}, {bE}, {aE});}}\n" + ".tags {{color: rgb({rK}, {gK}, {bK});}}\n" + ".optional {{color: rgb({rO}, {gO}, {bO});}}\n" + ".comment {{color: rgb({rC}, {gC}, {bC});}}\n" + ".synopsis {{color: rgb({rM}, {gM}, {bM});}}\n" + ".title {{text-align: center;}}\n" ).format( - tColR=colText.red(), tColG=colText.green(), tColB=colText.blue(), - hColR=colHead.red(), hColG=colHead.green(), hColB=colHead.blue(), - aColR=colVals.red(), aColG=colVals.green(), aColB=colVals.blue(), - eColR=colEmph.red(), eColG=colEmph.green(), eColB=colEmph.blue(), - kColR=colKeys.red(), kColG=colKeys.green(), kColB=colKeys.blue(), - cColR=colHide.red(), cColG=colHide.green(), cColB=colHide.blue(), - mColR=colMods.red(), mColG=colMods.green(), mColB=colMods.blue(), - oColR=colOpts.red(), oColG=colOpts.green(), oColB=colOpts.blue(), + rT=colText.red(), gT=colText.green(), bT=colText.blue(), + rH=colHead.red(), gH=colHead.green(), bH=colHead.blue(), + rA=colVals.red(), gA=colVals.green(), bA=colVals.blue(), + rE=colMark.red(), gE=colMark.green(), bE=colMark.blue(), aE=colMark.alpha(), + rK=colKeys.red(), gK=colKeys.green(), bK=colKeys.blue(), + rC=colHide.red(), gC=colHide.green(), bC=colHide.blue(), + rM=colMods.red(), gM=colMods.green(), bM=colMods.blue(), + rO=colOpts.red(), gO=colOpts.green(), bO=colOpts.blue(), ) self.document().setDefaultStyleSheet(styleSheet) diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index ae99f6c2..c8cc263b 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -672,6 +672,12 @@ class GuiMainMenu(QMenuBar): lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE) ) + # Shortcode Mark + self.aScMark = self.mShortcodes.addAction(self.tr("Highlight")) + self.aScMark.triggered.connect( + lambda: self.requestDocAction.emit(nwDocAction.SC_MARK) + ) + # Shortcode Superscript self.aScSuper = self.mShortcodes.addAction(self.tr("Superscript")) self.aScSuper.triggered.connect( diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 412a5363..fb8ca63c 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -106,6 +106,7 @@ class GuiTheme: self.colError = QColor(0, 0, 0) self.colRepTag = QColor(0, 0, 0) self.colMod = QColor(0, 0, 0) + self.colMark = QColor(255, 255, 255, 128) # Class Setup # =========== @@ -329,6 +330,7 @@ class GuiTheme: self.colError = self._parseColour(confParser, cnfSec, "errorline") self.colRepTag = self._parseColour(confParser, cnfSec, "replacetag") self.colMod = self._parseColour(confParser, cnfSec, "modifier") + self.colMark = self._parseColour(confParser, cnfSec, "texthighlight") return True @@ -448,8 +450,8 @@ class GuiIcons: "search_regex", "search_word", # Format Icons - "fmt_bold", "fmt_bold-md", "fmt_italic", "fmt_italic-md", "fmt_strike", "fmt_strike-md", - "fmt_subscript", "fmt_superscript", "fmt_underline", + "fmt_bold", "fmt_bold-md", "fmt_italic", "fmt_italic-md", "fmt_mark", "fmt_strike", + "fmt_strike-md", "fmt_subscript", "fmt_superscript", "fmt_underline", # General Button Icons "add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross", diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index aaf09141..8d95da6a 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -768,6 +768,8 @@ class _PreviewWidget(QTextBrowser): html = html.replace("\t", "!!tab!!") html = html.replace("", "") html = html.replace("", "") + html = html.replace("", "") + html = html.replace("", "") self.setHtml(html) qApp.processEvents() while self.find("!!tab!!"): diff --git a/sample/content/636b6aa9b697b.nwd b/sample/content/636b6aa9b697b.nwd index 78c6d8b9..ed5bacb6 100644 --- a/sample/content/636b6aa9b697b.nwd +++ b/sample/content/636b6aa9b697b.nwd @@ -1,8 +1,8 @@ %%~name: Making a Scene %%~path: 6a2d6d5f4f401/636b6aa9b697b %%~kind: NOVEL/DOCUMENT -%%~hash: d7920fe95fb48140278ae160fe7c3c1ce530072c -%%~date: Unknown/2023-10-20 22:57:56 +%%~hash: e1c58699b05b512306a534da70c2952aafc60e8b +%%~date: Unknown/2024-02-25 16:33:40 ### Making a Scene @pov: Jane @@ -13,7 +13,7 @@ A scene is defined by a level three heading, like the one at the top of this pag Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and **_bold italic_**. You can also ~~strike through~~ text. There is **some support for _nested_ emphasis**, but there are some known limitations. If the syntax highlighter doesn’t show it correctly, the export tool will not either. -For special formatting aside from standard emphasis, you can use “shortcodes”. Here we have super[sup]script[/sup] and sub[sub]script[/sub], and [b]part[/b]ial bold too, which ends in the middle of a word. The shortcodes also support [u]underline[/u]. +For special formatting aside from standard emphasis, you can use “shortcodes”. Here we have super[sup]script[/sup] and sub[sub]script[/sub], and [b]part[/b]ial bold too, which ends in the middle of a word. The shortcodes also support [u]underline[/u], and you can [m]highlight text as well[/m]. In addition, the editor supports automatic formatting of “quotes”, both double and ‘single’. Depending on the syntax highlighter settings and colour theme, these can be in different colours. “You can of course use **bold** and _italic_ text inside of quotes too.” diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 9f504c5a..82a35ce8 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Sample Project Jane Smith @@ -35,7 +35,7 @@ Main - + Novel @@ -57,7 +57,7 @@ Chapter One - + Making a Scene diff --git a/tests/test_core/test_core_tohtml.py b/tests/test_core/test_core_tohtml.py index 8b3b589c..a274f2a6 100644 --- a/tests/test_core/test_core_tohtml.py +++ b/tests/test_core/test_core_tohtml.py @@ -29,8 +29,8 @@ from novelwriter.core.project import NWProject @pytest.mark.core -def testCoreToHtml_ConvertFormat(mockGUI): - """Test the tokenizer and converter chain using the ToHtml class.""" +def testCoreToHtml_ConvertHeaders(mockGUI): + """Test header formats in the ToHtml class.""" project = NWProject() html = ToHtml(project) @@ -129,6 +129,19 @@ def testCoreToHtml_ConvertFormat(mockGUI): html.doConvert() assert html.result == "

Heading Two

\n" +# END Test testCoreToHtml_ConvertHeaders + + +@pytest.mark.core +def testCoreToHtml_ConvertParagraphs(mockGUI): + """Test paragraph formats in the ToHtml class.""" + project = NWProject() + html = ToHtml(project) + + html._isNovel = True + html._isNote = False + html._isFirst = True + # Paragraphs # ========== @@ -141,6 +154,19 @@ def testCoreToHtml_ConvertFormat(mockGUI): "strikethrough text here

\n" ) + # Shortcodes + html._text = ( + "Some [b]bold[/b], [i]italic[/i], [s]strike[/s], [u]underline[/u], [m]mark[/m], " + "super[sup]script[/sup], sub[sub]script[/sub] here\n" + ) + html.tokenizeText() + html.doConvert() + assert html.result == ( + "

Some bold, italic, strike, " + "underline, mark, " + "superscript, subscript here

\n" + ) + # Text w/Hard Break html._text = "Line one \nLine two \nLine three\n" html.tokenizeText() @@ -206,17 +232,13 @@ def testCoreToHtml_ConvertFormat(mockGUI): html.tokenizeText() html.doConvert() assert html.result == ( - "

" - "Chapter

\n" - "

" - "Point of View: Bod" - "

\n" - "

" - "Plot: Main" - "

\n" - "

" - "Locations: Europe" - "

\n" + "

Chapter

\n" + "

Point of View: " + "Bod

\n" + "

Plot: " + "Main

\n" + "

Locations: " + "Europe

\n" ) # Preview Mode @@ -234,7 +256,7 @@ def testCoreToHtml_ConvertFormat(mockGUI): "text here

\n" ) -# END Test testCoreToHtml_ConvertFormat +# END Test testCoreToHtml_ConvertParagraphs @pytest.mark.core diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py index a74e567c..5c492a7f 100644 --- a/tests/test_core/test_core_tokenizer.py +++ b/tests/test_core/test_core_tokenizer.py @@ -638,6 +638,11 @@ def testCoreToken_ExtractFormats(mockGUI): assert text == "Text with underline in it." assert fmt == [(10, tokens.FMT_U_B), (19, tokens.FMT_U_E)] + # Plain mark + text, fmt = tokens._extractFormats("Text with [m]highlight[/m] in it.") + assert text == "Text with highlight in it." + assert fmt == [(10, tokens.FMT_M_B), (19, tokens.FMT_M_E)] + # Plain superscript text, fmt = tokens._extractFormats("Text with super[sup]script[/sup] in it.") assert text == "Text with superscript in it." diff --git a/tests/test_core/test_core_tomd.py b/tests/test_core/test_core_tomd.py index fc2503ac..b368ad68 100644 --- a/tests/test_core/test_core_tomd.py +++ b/tests/test_core/test_core_tomd.py @@ -29,16 +29,11 @@ from novelwriter.core.project import NWProject @pytest.mark.core -def testCoreToMarkdown_ConvertFormat(mockGUI): - """Test the tokenizer and converter chain using the ToMarkdown - class. - """ +def testCoreToMarkdown_ConvertHeaders(mockGUI): + """Test header formats in the ToMarkdown class.""" project = NWProject() toMD = ToMarkdown(project) - # Headers - # ======= - toMD._isNovel = True toMD._isNote = False toMD._isFirst = True @@ -79,8 +74,18 @@ def testCoreToMarkdown_ConvertFormat(mockGUI): toMD.doConvert() assert toMD.result == "## Prologue\n\n" - # Paragraphs - # ========== +# END Test testCoreToMarkdown_ConvertHeaders + + +@pytest.mark.core +def testCoreToMarkdown_ConvertParagraphs(mockGUI): + """Test paragraph formats in the ToMarkdown class.""" + project = NWProject() + toMD = ToMarkdown(project) + + toMD._isNovel = True + toMD._isNote = False + toMD._isFirst = True # Text for Extended Markdown toMD.setExtendedMarkdown() @@ -100,6 +105,31 @@ def testCoreToMarkdown_ConvertFormat(mockGUI): "Some **nested bold and _italic_ and strikethrough text** here\n\n" ) + # Shortcodes for Extended Markdown + toMD.setExtendedMarkdown() + toMD._text = ( + "Some [b]bold[/b], [i]italic[/i], [s]strike[/s], [u]underline[/u], [m]mark[/m], " + "super[sup]script[/sup], sub[sub]script[/sub] here\n" + ) + toMD.tokenizeText() + toMD.doConvert() + assert toMD.result == ( + "Some **bold**, _italic_, ~~strike~~, underline, ==mark==, " + "super^script^, sub~script~ here\n\n" + ) + + # Shortcodes for Standard Markdown + toMD.setStandardMarkdown() + toMD._text = ( + "Some [b]bold[/b], [i]italic[/i], [s]strike[/s], [u]underline[/u], [m]mark[/m], " + "super[sup]script[/sup], sub[sub]script[/sub] here\n" + ) + toMD.tokenizeText() + toMD.doConvert() + assert toMD.result == ( + "Some **bold**, _italic_, strike, underline, mark, superscript, subscript here\n\n" + ) + # Text w/Hard Break toMD._text = "Line one \nLine two \nLine three\n" toMD.tokenizeText() @@ -160,7 +190,7 @@ def testCoreToMarkdown_ConvertFormat(mockGUI): "**Locations:** Europe\n\n" ) -# END Test testCoreToMarkdown_ConvertFormat +# END Test testCoreToMarkdown_ConvertParagraphs @pytest.mark.core diff --git a/tests/test_core/test_core_toodt.py b/tests/test_core/test_core_toodt.py index c9039236..23afa0ac 100644 --- a/tests/test_core/test_core_toodt.py +++ b/tests/test_core/test_core_toodt.py @@ -237,22 +237,12 @@ def testCoreToOdt_TextFormatting(mockGUI): @pytest.mark.core -def testCoreToOdt_Convert(mockGUI): +def testCoreToOdt_ConvertHeaders(mockGUI): """Test the converter of the ToOdt class.""" project = NWProject() odt = ToOdt(project, isFlat=True) - odt._isNovel = True - def getStyle(styleName): - for aSet in odt._autoPara.values(): - if aSet[0] == styleName: - return aSet[1] - return None - - # Headers - # ======= - # Header 1 odt._text = "# Title\n" odt.tokenizeText() @@ -331,8 +321,21 @@ def testCoreToOdt_Convert(mockGUI): '' ) - # Paragraphs - # ========== +# END Test testCoreToOdt_ConvertHeaders + + +@pytest.mark.core +def testCoreToOdt_ConvertParagraphs(mockGUI): + """Test the converter of the ToOdt class.""" + project = NWProject() + odt = ToOdt(project, isFlat=True) + odt._isNovel = True + + def getStyle(styleName): + for aSet in odt._autoPara.values(): + if aSet[0] == styleName: + return aSet[1] + return None # Nested Markdown Text odt._text = "Some ~~nested **bold** and _italics_ text~~ text." @@ -372,7 +375,7 @@ def testCoreToOdt_Convert(mockGUI): '' ) - # Nested Shortcode Text, Super/Subscript + # Shortcode Text, Super/Subscript odt._text = "Some super[sup]script[/sup] and sub[sub]script[/sub] text." odt.tokenizeText() odt.initDocument() @@ -387,6 +390,21 @@ def testCoreToOdt_Convert(mockGUI): '' ) + # Shortcode Text, Underline/Highlight + odt._text = "Some [u]underlined and [m]highlighted[/m][/u] text." + odt.tokenizeText() + odt.initDocument() + odt.doConvert() + odt.closeDocument() + assert odt.errData == [] + assert xmlToText(odt._xText) == ( + '' + 'Some ' + 'underlined and ' + 'highlighted text.' + '' + ) + # Hard Break odt._text = "Some text.\nNext line\n" odt.tokenizeText() @@ -422,7 +440,7 @@ def testCoreToOdt_Convert(mockGUI): assert odt.errData == [] assert xmlToText(odt._xText) == ( '' - 'Some ' + 'Some ' 'boldtext' '' ) @@ -467,13 +485,13 @@ def testCoreToOdt_Convert(mockGUI): assert xmlToText(odt._xText) == ( '' 'Scene' - '' + '' 'Point of View: Jane' - '' + '' 'Synopsis: So it begins' - '' + '' 'Short Description: Then what' - '' + '' 'Comment: A plain comment' '' ) @@ -535,26 +553,26 @@ def testCoreToOdt_Convert(mockGUI): assert xmlToText(odt._xText) == ( '' 'Scene' - '' + '' 'Point of View: Jane' - '' + '' 'Characters: John' - '' + '' 'Plot: Main' - 'Right align' + 'Right align' 'Left Align' - 'Centered' - 'Left indent' - 'Right indent' + 'Centered' + 'Left indent' + 'Right indent' '' ) - assert getStyle("P3")._pAttr["margin-bottom"] == ["fo", "0.000cm"] # type: ignore - assert getStyle("P4")._pAttr["margin-bottom"] == ["fo", "0.000cm"] # type: ignore - assert getStyle("P4")._pAttr["margin-top"] == ["fo", "0.000cm"] # type: ignore - assert getStyle("P5")._pAttr["text-align"] == ["fo", "right"] # type: ignore - assert getStyle("P6")._pAttr["text-align"] == ["fo", "center"] # type: ignore - assert getStyle("P7")._pAttr["margin-left"] == ["fo", "1.693cm"] # type: ignore - assert getStyle("P8")._pAttr["margin-right"] == ["fo", "1.693cm"] # type: ignore + assert getStyle("P1")._pAttr["margin-bottom"] == ["fo", "0.000cm"] # type: ignore + assert getStyle("P2")._pAttr["margin-bottom"] == ["fo", "0.000cm"] # type: ignore + assert getStyle("P2")._pAttr["margin-top"] == ["fo", "0.000cm"] # type: ignore + assert getStyle("P3")._pAttr["text-align"] == ["fo", "right"] # type: ignore + assert getStyle("P4")._pAttr["text-align"] == ["fo", "center"] # type: ignore + assert getStyle("P5")._pAttr["margin-left"] == ["fo", "1.693cm"] # type: ignore + assert getStyle("P6")._pAttr["margin-right"] == ["fo", "1.693cm"] # type: ignore # Justified odt._text = ( @@ -573,11 +591,11 @@ def testCoreToOdt_Convert(mockGUI): '' 'Scene' 'Regular paragraph' - 'withbreak' - 'Left Align' + 'withbreak' + 'Left Align' '' ) - assert getStyle("P9")._pAttr["text-align"] == ["fo", "left"] # type: ignore + assert getStyle("P7")._pAttr["text-align"] == ["fo", "left"] # type: ignore # Page Breaks odt._text = ( @@ -593,9 +611,9 @@ def testCoreToOdt_Convert(mockGUI): assert odt.errData == [] assert xmlToText(odt._xText) == ( '' - 'Chapter One' + 'Chapter One' 'Text' - 'Chapter Two' + 'Chapter Two' 'Text' '' ) @@ -612,12 +630,12 @@ def testCoreToOdt_Convert(mockGUI): assert odt.errData == [] assert xmlToText(odt._xText) == ( '' - 'Test text **' + 'Test text **' 'bold** and more.' '' ) -# END Test testCoreToOdt_Convert +# END Test testCoreToOdt_ConvertParagraphs @pytest.mark.core @@ -1081,6 +1099,17 @@ def testCoreToOdt_ODTTextStyle(): txtStyle.setFontStyle("stuff") assert txtStyle._tAttr["font-style"] == ["fo", None] + # Background Color + assert txtStyle._tAttr["background-color"] == ["fo", None] + txtStyle.setBackgroundColor("stuff") + assert txtStyle._tAttr["background-color"] == ["fo", None] + txtStyle.setBackgroundColor("012345") + assert txtStyle._tAttr["background-color"] == ["fo", None] + txtStyle.setBackgroundColor("#012345") + assert txtStyle._tAttr["background-color"] == ["fo", "#012345"] + txtStyle.setBackgroundColor("stuff") + assert txtStyle._tAttr["background-color"] == ["fo", None] + # Text Position assert txtStyle._tAttr["text-position"] == ["style", None] txtStyle.setTextPosition("stuff") diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 93b8bcc8..8ce95710 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -491,6 +491,13 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd): assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True assert nwGUI.docEditor.getText() == text + # Mark + nwGUI.docEditor.setCursorPosition(46) + assert nwGUI.docEditor.docAction(nwDocAction.SC_MARK) is True + assert nwGUI.docEditor.getText() == text.replace("consectetur", "[m]consectetur[/m]") + assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True + assert nwGUI.docEditor.getText() == text + # Superscript nwGUI.docEditor.setCursorPosition(46) assert nwGUI.docEditor.docAction(nwDocAction.SC_SUP) is True