diff --git a/CREDITS.md b/CREDITS.md
index 9ac3b34a..d036a4a4 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -55,19 +55,60 @@ The following libraries are dependencies of novelWriter:
* **Enchant** by Dom Lachowicz
* **PyEnchant** by Dimitri Merejkowsky
-## Assets
+## Icon Themes
-Some of the assets bundled with novelWriter were adapted from the following sources:
+The default icon theme is a subset of icons from:
-* **Material Symbols** icons by Google Inc (Apache 2.0)
-* **Remix** icons by RemixIcon (Apache 2.0)
-* **Font Awesome** icons by Fonticons Inc (CC BY 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)
-* **Cyberpunk Night** theme by Anders Lemvigh (CC BY-SA 4.0)
-* **Dracula** theme by Zeno Rocha (MIT License)
-* **Snazzy Light** theme by Florian Reuschel (MIT License)
+* **Material Symbols** by Google Inc (Apache 2.0)
+
+Additional icon themes may or may not be bundled with novelWriter, depending on license
+constraints. These themes include a subset of icons from:
+
+* **Font Awesome** by Fonticons Inc (CC BY 4.0)
+* **Remix** by RemixIcon (Apache 2.0 w/non-free clause)
+
+## Colour Themes
+
+Colour themes included with novelWriter were adapted from the following sources:
+
+* **Aura** themes by Dalton Menezes
+* **B2T** themes by Bram de Haan
+* **B4T** themes by Bram de Haan
+* **Dracula** theme by Zeno Rocha
+* **Espresso** theme by Sainnhe Park and Antoine Cotten
+* **Everforest** themes by Sainnhe Park
+* **Horizon** themes by Jonathan Olaleye
+* **New Moon** theme by Tania Rascia
+* **Noctis** themes by Liviu Schera and contributors
+* **Nord** theme by Sven Greb
+* **Owl** themes by Sarah Drasner
+* **Primer** themes by GitHub Inc. and Myian
+* **Snazzy Light** theme by Florian Reuschel
+* **Solarized** themes by Ethan Schoonover
+* **Tango** themes by the Tango Desktop Project
+* **Tomorrow** themes by Chris Kempson
+
+Other themes were created for novelWriter:
+
+* **Aura Bright** theme by Myian
+* **Blue Streak** themes by Veronica Berglyd Olsen
+* **Castle** themes by Myian
+* **Chalky Soil** theme by Myian
+* **Chernozem** theme by Myian
+* **Cyberpunk Night** theme by Anders Lemvigh
+* **Floral** themes by Myian
+* **Full Moon** theme by Myian
+* **LCARS** theme by Veronica Berglyd Olsen
+* **Nordlicht** theme by Myian
+* **Otium** theme by Myian
+* **Paragon** theme by Myian
+* **Ruby** themes by Myian
+* **Selenium** themes by Myian
+* **Sepia** themes by Myian
+* **Sultana** themes by Myian
+* **Vivid** themes by Myian
+* **Warpgate** theme by Myian
+* **Waterlily** themes by Myian
## Fonts
diff --git a/docs/source/conf.py b/docs/source/conf.py
index b3d3d41f..bfedb52a 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,7 +1,7 @@
"""
Configuration file for the Sphinx documentation builder.
Documentation: http://www.sphinx-doc.org/en/master/config
-"""
+""" # noqa
# -- Imports -----------------------------------------------------------------
diff --git a/docs/source/features/shortcuts.rst b/docs/source/features/shortcuts.rst
index 0a29927b..e5811c85 100644
--- a/docs/source/features/shortcuts.rst
+++ b/docs/source/features/shortcuts.rst
@@ -101,6 +101,7 @@ Text Formatting Shortcuts
":kbd:`Ctrl+B`", "Format selected text, or word under cursor, with bold"
":kbd:`Ctrl+D`", "Format selected text, or word under cursor, with strike through"
":kbd:`Ctrl+I`", "Format selected text, or word under cursor, with italic"
+ ":kbd:`Ctrl+M`", "Highlight selected text, or word under cursor"
":kbd:`Ctrl+Shift+/`", "Remove format for block or selected text"
":kbd:`Ctrl+Shift+D`", "Toggle ignored text format for block or selected text"
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 8e863ff8..793d1d38 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -75,6 +75,7 @@ storage for robustness.
more/counting
more/typography
+ more/dictionaries
more/customise
more/handling_errors
more/project_format
diff --git a/docs/source/more/customise.rst b/docs/source/more/customise.rst
index 80aa1201..89a74ba0 100644
--- a/docs/source/more/customise.rst
+++ b/docs/source/more/customise.rst
@@ -1,94 +1,35 @@
.. _docs_more_custom:
-**************
-Customisations
-**************
+*************
+Custom Themes
+*************
-.. _Enchant: https://rrthomas.github.io/enchant/
-.. _Free Desktop: https://cgit.freedesktop.org/libreoffice/dictionaries/tree/
-
-There are a few ways you can customise novelWriter yourself. Currently, you can add new GUI themes,
-your own syntax themes, and install additional dictionaries.
-
-
-.. _docs_more_custom_dict:
-
-Spell Check Dictionaries
-========================
-
-novelWriter uses Enchant_ as the spell checking tool. Depending on your operating system, it may or
-may not load all installed spell check dictionaries automatically.
-
-
-Linux and MacOS
----------------
-
-On Linux and MacOS, you generally only have to install hunspell, aspell or myspell dictionaries on
-your system like you do for other applications. See your distro or OS documentation for how to do
-this. These dictionaries should show up as available spell check languages in novelWriter.
-
-
-Windows
--------
-
-For Windows, English is included with the installation. For other languages you have to download
-and add dictionaries yourself.
-
-**Install Tool**
-
-A small tool to assist with this can be found under **Tools > Add Dictionaries**. It will import
-spell checking dictionaries from Free Office or Libre Office extensions. The dictionaries are then
-installed in the install location for the Enchant library and should thus work for any application
-that uses Enchant for spell checking.
-
-**Manual Install**
-
-If you prefer to do this manually or want to use a different source than the ones mentioned above,
-You need to get compatible dictionary files for your language. You need two files files ending with
-``.aff`` and ``.dic``. These files must then be copied to the following location:
-
-``C:\Users\\AppData\Local\enchant\hunspell``
-
-This assumes your user profile is stored at ``C:\Users\``. The last one or two folders may
-not exist, so you may need to create them.
-
-You can find the various dictionaries on the `Free Desktop`_ website.
-
-.. note::
- The Free Desktop link points to a repository, and what may look like file links inside the
- dictionary folder are actually links to web pages. If you right-click and download those, you
- get HTML files, not dictionaries!
-
- In order to download the actual dictionary files, right-click the "plain" label at the end of
- each line and download that.
+There are a few ways you can customise novelWriter yourself. Currently, you can relatively easily
+add new GUI themes. You can also add new icon themes, although this is not as straightforward.
.. _docs_more_custom_theme:
-Syntax and GUI Themes
-=====================
+Colour Themes
+=============
-Adding your own GUI and syntax themes is relatively easy, although it requires that you manually
-edit config files with colour values. The themes are defined by simple plain text config files with
-meta data and colour settings.
+Adding your own colour themes is relatively easy, although it requires that you manually edit
+config files with colour values. The themes are defined by simple plain text config files with meta
+data and colour settings.
In order to make your own versions, first copy one of the existing files to your local computer and
modify it as you like.
-* The existing syntax themes are stored in
- `novelwriter/assets/syntax `_.
-* The existing GUI themes are stored in
- `novelwriter/assets/themes `_.
-* The existing icon themes are stored in
- `novelwriter/assets/icons `_.
+The existing colour themes are stored in
+`novelwriter/assets/themes `_.
Remember to also change the name of your theme by modifying the ``name`` setting at the top of the
file, otherwise you may not be able to distinguish them in **Preferences**.
For novelWriter to be able to locate the custom theme files, you must copy them to the
-:ref:`docs_technical_locations_data` location in your home or user area. There should be a folder there named
-``syntax`` for syntax themes, just ``themes`` for GUI themes, and ``icons`` for icon themes. These
-folders are created the first time you start novelWriter.
+:ref:`docs_technical_locations_data` location in your home or user area. There should be a folder
+there named ``themes`` for colour themes. These folders are created the first time you start
+novelWriter.
Once the files are copied there, they should show up in **Preferences** with the label you
set as ``name`` inside the file.
@@ -99,71 +40,129 @@ set as ``name`` inside the file.
files up to date.
-Custom GUI and Icons Theme
---------------------------
+The Theme File Format
+---------------------
-A GUI theme ``.conf`` file consists of the following settings:
+A colour theme ``.conf`` file consists of the following settings:
.. code-block:: cfg
+ :caption: The theme file for the "Default Light Theme"
[Main]
- name = My Custom Theme
- description = A description of my custom theme
- author = Jane Doe
- credit = John Doe
- url = https://example.com
- license = CC BY-SA 4.0
- licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
+ name = Default Light Theme
+ mode = light
+ author = Veronica Berglyd Olsen
+ credit = Veronica Berglyd Olsen
+ url = https://github.com/vkbo/novelWriter
- [Icons]
- default = 100, 100, 100
- faded = 100, 100, 100
- red = 255, 0, 0
- orange = 255, 128, 0
- yellow = 255, 255, 0
- green = 0, 255, 0
- aqua = 0, 255, 255
- blue = 0, 0, 255
- purple = 255, 0, 255
+ [Base]
+ base = #fcfcfc
+ default = #303030
+ faded = #6c6c6c
+ red = #a62a2d
+ orange = #b36829
+ yellow = #a39c34
+ green = #296629
+ cyan = #269999
+ blue = #3a70a6
+ purple = #b35ab3
[Project]
- root = 0, 255, 255
- folder = 255, 255, 0
- file = 100, 100, 100
- title = 0, 255, 0
- chapter = 255, 0, 0
- scene = 0, 0, 255
- note = 255, 255, 0
+ root = blue
+ folder = yellow
+ file = default
+ title = green
+ chapter = red
+ scene = blue
+ note = yellow
+ active = green
+ inactive = red
+ disabled = faded
[Palette]
- window = 100, 100, 100
- windowtext = 100, 100, 100
- base = 100, 100, 100
- alternatebase = 100, 100, 100
- text = 100, 100, 100
- tooltipbase = 100, 100, 100
- tooltiptext = 100, 100, 100
- button = 100, 100, 100
- buttontext = 100, 100, 100
- brighttext = 100, 100, 100
- highlight = 100, 100, 100
- highlightedtext = 100, 100, 100
- link = 100, 100, 100
- linkvisited = 100, 100, 100
+ window = base:D105
+ windowtext = default
+ base = base
+ alternatebase = #e0e0e0
+ text = default
+ tooltipbase = #ffffc0
+ tooltiptext = #15150d
+ button = #efefef
+ buttontext = default
+ brighttext = base
+ highlight = #3087c6
+ highlightedtext = base
+ link = blue
+ linkvisited = blue
+ accent = #3087c6
[GUI]
- helptext = 0, 0, 0
- fadedtext = 128, 128, 128
- errortext = 255, 0, 0
+ helptext = #5c5c5c
+ fadedtext = #6c6c6c
+ errortext = red
-In the Main section you must at least define the ``name`` settings.
+ [Syntax]
+ background = base
+ text = default
+ line = default:32
+ link = blue
+ headertext = green
+ headertag = green:L135
+ emphasis = orange
+ dialog = blue
+ altdialog = red
+ note = yellow:D125
+ hidden = faded
+ shortcode = blue
+ keyword = red
+ tag = green
+ value = green
+ optional = blue
+ spellcheckline = red
+ errorline = green
+ replacetag = green
+ modifier = blue
+ texthighlight = yellow:72
-The Palette values correspond to the Qt enum values for ``QPalette::ColorRole``, see the
-`Qt documentation `_ for more details. The
-colour values are RGB numbers on the format ``r, g, b`` where each is an integer from ``0`` to
-``255``. Omitted values are not loaded and will use default values. If the ``helptext`` colour is
-not defined, it is computed as a colour between the ``window`` and ``windowtext`` colour.
-Additional shades of some of the colours are also computed. These are mainly used for 3D effects.
+
+Theme Sections
+--------------
+
+.. _ColorRole: https://doc.qt.io/qt-6/qpalette.html#ColorRole-enum
+
+The theme file is made up of different sections depending on what part of novelWriter the theme
+affects.
+
+.. csv-table:: Theme Sections Overview
+ :header: "Section", "Description"
+ :class: "tight-table"
+
+ "``[Main]``", "Meta data about the theme, You must at least set ``name``, ``mode`` and ``author``, and ``mode`` must be either ``light`` or ``dark``."
+ "``[Base]``", "The base colours of the theme. These are also selectable colours in various places inside the app, like for icon colours in **Preferences**."
+ "``[Project]``", "The colours used for icons and markers for the different project item types."
+ "``[Palette]``", "The colours used for styling the user interface. The values correspond to the ColorRole_ values in the Qt library."
+ "``[GUI]``", "The colours used for styling additional elements of the user interface."
+ "``[Syntax]``", "The colours used for syntax highlighting in documents."
+
+
+Colour Value Formats
+--------------------
+
+There are several ways to enter colour values:
+
+.. csv-table:: Colour Formats
+ :header: "Syntax", "Description"
+ :widths: 15, 85
+ :class: "tight-table"
+
+ "``#RRGGBB``", "A CSS style hexadecimal values, like ``#ff0000`` for red."
+ "``#RRGGBBAA``", "A CSS style hexadecimal values with transparency, like ``#ff00007f`` for half-transparent red."
+ "``name``", "A name referring to one of the colours already specified under the ``[Base]`` section, like ``red``. Note that you should not use named colours in the ``[Base]`` section itself as that may have unintended results."
+ "``name:255``", "A name referring to one of the colours already specified under the ``[Base]`` section, with a transparency value added. The value must be in the range ``0`` to ``255``, like ``red:127`` for half-transparent red."
+ "``name:L100``", "A name referring to one of the colours already specified under the ``[Base]`` section, where the L-number is a percentage value that makes it lighter. The value must be greater than ``0``. ``L100`` means no change."
+ "``name:D100``", "A name referring to one of the colours already specified under the ``[Base]`` section, where the D-number is a percentage value that makes it darker. The value must be greater than ``0``. ``D100`` means no change."
+ "``r, g, b``", "A set of red, green and blue numbers in the range ``0`` to ``255``, like ``255, 0, 0`` for red."
+ "``r, g, b, a``", "A set of red, green, blue and alpha numbers in the range ``0`` to ``255``, like ``255, 0, 0, 127`` for half-transparent red."
.. versionadded:: 2.5
The ``fadedtext`` and ``errortext`` theme colour entries were added.
@@ -172,60 +171,86 @@ Additional shades of some of the colours are also computed. These are mainly use
The ``icontheme`` setting was dropped as the icon theme is now its own setting.
The ``[Icons]`` and ``[Project]`` sections were added, and the ``status*`` settings removed.
+.. versionadded:: 2.8
+ The ``[Syntax]`` section was moved into the main theme file. Previously, these settings were in
+ their own file. The ``[Icons]`` section was renamed to ``[Base]``. Added the ``line`` setting.
+ Dropped the ``license``, ``licenseurl``, and ``description`` settings. The ``author`` field
+ is now required if the theme is included in the app, but not for user themes.
-Custom Syntax Theme
--------------------
-A syntax theme ``.conf`` file consists of the following settings:
+Icon Themes
+===========
+
+Icon themes are *not* straightforward to add, but if you want to make the effort, this section
+describes how to do it.
+
+The existing icon themes are stored in
+`novelwriter/assets/icons `_.
+
+As with colour themes, remember to change the name of your theme by modifying the ``name`` setting
+at the top of the file, otherwise you may not be able to distinguish them in **Preferences**.
+
+For novelWriter to be able to locate the custom theme files, you must copy them to the
+:ref:`docs_technical_locations_data` location in your home or user area. There should be a folder
+there ``icons`` for icon themes. These folders are created the first time you start novelWriter.
+
+
+The Icons File Format
+---------------------
+
+Icon themes are kept in files with the ``.icons`` file extension. The file format is a custom
+format with entries on the form ``section:key = value``.
.. code-block:: cfg
+ :caption: The icons file for "Material Symbols - Rounded Medium" (truncated)
- [Main]
- name = My Syntax Theme
- author = Jane Doe
- credit = John Doe
- url = https://example.com
- license = CC BY-SA 4.0
- licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
+ # Meta
+ meta:name = Material Symbols - Rounded Medium
+ meta:author = Google Inc
+ meta:license = Apache 2.0
- [Syntax]
- background = 255, 255, 255
- text = 0, 0, 0
- link = 0, 0, 0
- headertext = 0, 0, 0
- headertag = 0, 0, 0
- emphasis = 0, 0, 0
- dialog = 0, 0, 0
- altdialog = 0, 0, 0
- note = 0, 0, 0
- hidden = 0, 0, 0
- shortcode = 0, 0, 0
- keyword = 0, 0, 0
- tag = 0, 0, 0
- value = 0, 0, 0
- optional = 0, 0, 0
- spellcheckline = 0, 0, 0
- errorline = 0, 0, 0
- replacetag = 0, 0, 0
- modifier = 0, 0, 0
- texthighlight = 255, 255, 255, 128
+ # Icons
+ icon:alert_error =
PyEnchant by Dimitri Merejkowsky
-
Assets
+
Icon Themes
-
Some of the assets bundled with novelWriter were adapted from the following sources:
+
The default icon theme is a subset of icons from:
-
Material Symbols icons by Google Inc (Apache 2.0)
-
Remix icons by RemixIcon (Apache 2.0)
-
Font Awesome icons by Fonticons Inc (CC BY 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)
-
Cyberpunk Night theme by Anders Lemvigh (CC BY-SA 4.0)
-
Dracula theme by Zeno Rocha (MIT License)
-
Snazzy Light theme by Florian Reuschel (MIT License)
+
Material Symbols by Google Inc (Apache 2.0)
+
+
+
Additional icon themes may or may not be bundled with novelWriter, depending on license
+constraints. These themes include a subset of icons from:
+
+
+
Font Awesome by Fonticons Inc (CC BY 4.0)
+
Remix by RemixIcon (Apache 2.0 w/non-free clause)
+
+
+
Colour Themes
+
+
Colour themes included with novelWriter were adapted from the following sources:
+
+
+
Aura themes by Dalton Menezes
+
B2T themes by Bram de Haan
+
B4T themes by Bram de Haan
+
Dracula theme by Zeno Rocha
+
Espresso theme by Sainnhe Park and Antoine Cotten
+
Everforest themes by Sainnhe Park
+
Horizon themes by Jonathan Olaleye
+
New Moon theme by Tania Rascia
+
Noctis themes by Liviu Schera and contributors
+
Nord theme by Sven Greb
+
Owl themes by Sarah Drasner
+
Primer themes by GitHub Inc. and Myian
+
Snazzy Light theme by Florian Reuschel
+
Solarized themes by Ethan Schoonover
+
Tango themes by the Tango Desktop Project
+
Tomorrow themes by Chris Kempson
+
+
+
Other themes were created for novelWriter:
+
+
+
Aura Bright theme by Myian
+
Blue Streak themes by Veronica Berglyd Olsen
+
Castle themes by Myian
+
Chalky Soil theme by Myian
+
Chernozem theme by Myian
+
Cyberpunk Night theme by Anders Lemvigh
+
Floral themes by Myian
+
Full Moon theme by Myian
+
LCARS theme by Veronica Berglyd Olsen
+
Nordlicht theme by Myian
+
Otium themes by Myian
+
Paragon theme by Myian
+
Ruby themes by Myian
+
Selenium themes by Myian
+
Sepia themes by Myian
+
Sultana themes by Myian
+
Vivid themes by Myian
+
Warpgate theme by Myian
+
Waterlily themes by Myian
Fonts
diff --git a/novelwriter/assets/themes/aura.conf b/novelwriter/assets/themes/aura.conf
new file mode 100644
index 00000000..a31ac7ba
--- /dev/null
+++ b/novelwriter/assets/themes/aura.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Aura
+mode = dark
+author = Myian
+credit = Dalton Menezes
+url = https://github.com/daltonmenezes/aura-theme
+
+[Base]
+base = #15141b
+default = #edecee
+faded = #888888
+red = #ff6767
+orange = #ff9341
+yellow = #ffca85
+green = #8ae986
+cyan = #61ffca
+blue = #82e2ff
+purple = #a277ff
+
+[Project]
+root = default
+folder = red
+file = default
+title = cyan
+chapter = purple
+scene = #f694ff
+note = yellow
+active = cyan
+inactive = red
+disabled = faded
+
+[Palette]
+window = #0c0c11
+windowtext = default
+base = base
+alternatebase = #353249
+text = default
+tooltipbase = yellow
+tooltiptext = base
+button = base:L125
+buttontext = default
+brighttext = base
+highlight = #603bb1
+highlightedtext = default
+link = blue
+linkvisited = blue
+accent = #603bb1
+
+[GUI]
+helptext = faded:L125
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #29263c
+link = blue
+headertext = cyan
+headertag = cyan:128
+emphasis = purple:L105
+dialog = cyan
+altdialog = blue
+note = yellow
+hidden = faded
+shortcode = yellow
+keyword = #f694ff
+tag = yellow
+value = faded
+optional = faded
+spellcheckline = red
+errorline = blue
+replacetag = yellow
+modifier = red
+texthighlight = #3c3958
diff --git a/novelwriter/assets/themes/aura_bright.conf b/novelwriter/assets/themes/aura_bright.conf
new file mode 100644
index 00000000..651e452c
--- /dev/null
+++ b/novelwriter/assets/themes/aura_bright.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Aura Bright
+mode = light
+author = Myian
+
+[Base]
+base = #f0e9f0
+default = #45425a
+faded = #777777
+red = #ce4e4e
+orange = #bd6b23
+yellow = #a97a20
+green = #378c1d
+cyan = #098d82
+blue = #3371c2
+purple = #7752ce
+
+[Project]
+root = default
+folder = red
+file = #7b788a
+title = cyan
+chapter = purple
+scene = #c540cc
+note = #c49134
+active = cyan
+inactive = red
+disabled = faded
+
+[Palette]
+window = #e1dae2
+windowtext = default
+base = base
+alternatebase = #cec9d3
+text = default
+tooltipbase = #ffecad
+tooltiptext = default
+button = base
+buttontext = default
+brighttext = base
+highlight = purple
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = purple
+
+[GUI]
+helptext = faded:D125
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #e1dae2
+link = blue
+headertext = cyan
+headertag = cyan:128
+emphasis = purple
+dialog = cyan:D105
+altdialog = blue
+note = yellow
+hidden = faded
+shortcode = yellow
+keyword = #c540cc
+tag = yellow
+value = faded
+optional = faded
+spellcheckline = red
+errorline = blue
+replacetag = yellow
+modifier = red
+texthighlight = #c5bfca
diff --git a/novelwriter/assets/themes/aura_soft.conf b/novelwriter/assets/themes/aura_soft.conf
new file mode 100644
index 00000000..f8d58f70
--- /dev/null
+++ b/novelwriter/assets/themes/aura_soft.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Aura Soft
+mode = dark
+author = Myian
+credit = Dalton Menezes
+url = https://github.com/daltonmenezes/aura-theme
+
+[Base]
+base = #21202e
+default = #bdbdbd
+faded = #6d6d6d
+red = #c55858
+orange = #e0925d
+yellow = #d8af79
+green = #6fb16d
+cyan = #54c59f
+blue = #6cb2c7
+purple = #8464c6
+
+[Project]
+root = default
+folder = red
+file = default
+title = cyan
+chapter = purple
+scene = #f694ff
+note = yellow
+active = cyan
+inactive = red
+disabled = faded
+
+[Palette]
+window = #191924
+windowtext = default
+base = base
+alternatebase = #353249
+text = default
+tooltipbase = yellow
+tooltiptext = base
+button = base:L125
+buttontext = default
+brighttext = base
+highlight = #6a4da3
+highlightedtext = #edecee
+link = blue
+linkvisited = blue
+accent = #6a4da3
+
+[GUI]
+helptext = faded:L125
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #29263c
+link = blue
+headertext = cyan
+headertag = cyan:128
+emphasis = #a581ec
+dialog = cyan
+altdialog = blue
+note = yellow
+hidden = faded
+shortcode = yellow
+keyword = #c17ac8
+tag = yellow
+value = faded
+optional = faded
+spellcheckline = #ff6767
+errorline = #82e2ff
+replacetag = yellow
+modifier = red
+texthighlight = #3c3958
diff --git a/novelwriter/assets/themes/b2t_garden_dark.conf b/novelwriter/assets/themes/b2t_garden_dark.conf
new file mode 100644
index 00000000..f5ecceba
--- /dev/null
+++ b/novelwriter/assets/themes/b2t_garden_dark.conf
@@ -0,0 +1,75 @@
+[Main]
+name = B2T Garden Dark
+mode = dark
+author = Myian
+credit = Bram de Haan
+url = https://base2t.one/demo/garden/
+
+[Base]
+base = #2b2c2a
+default = #ece5df
+faded = #828782
+red = #df6547
+orange = #db9257
+yellow = #e9e98e
+green = #90d98c
+cyan = #81c5de
+blue = #9596ea
+purple = #da72b2
+
+[Project]
+root = default
+folder = orange
+file = default
+title = #1c8217
+chapter = #4cb946
+scene = #90d98c
+note = faded
+active = green
+inactive = orange
+disabled = #696d69
+
+[Palette]
+window = #1e1f1e
+windowtext = default
+base = base
+alternatebase = #505350
+text = default
+tooltipbase = #e0cab8
+tooltiptext = base
+button = base
+buttontext = default
+brighttext = base
+highlight = #1c8217
+highlightedtext = default
+link = green
+linkvisited = green
+accent = #bd5d0f
+
+[GUI]
+helptext = #aab1aa
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #3e403d
+link = blue
+headertext = #fbfaf8
+headertag = #828782
+emphasis = #3fac39
+dialog = #90d98c
+altdialog = #4cb946
+note = #dd843c
+hidden = faded
+shortcode = #dd843c
+keyword = #dd843c
+tag = #dba070
+value = #dba070
+optional = faded
+spellcheckline = #ff6f65
+errorline = green
+replacetag = #dd843c
+modifier = faded
+texthighlight = green:80
diff --git a/novelwriter/assets/themes/b2t_garden_light.conf b/novelwriter/assets/themes/b2t_garden_light.conf
new file mode 100644
index 00000000..ac2287b7
--- /dev/null
+++ b/novelwriter/assets/themes/b2t_garden_light.conf
@@ -0,0 +1,75 @@
+[Main]
+name = B2T Garden Light
+mode = light
+author = Myian
+credit = Bram de Haan
+url = https://base2t.one/demo/garden/
+
+[Base]
+base = #fbfaf8
+default = #505350
+faded = #969c96
+red = #df6547
+orange = #db9257
+yellow = #cccc58
+green = #3fac39
+cyan = #5dacc9
+blue = #7678d4
+purple = #d362a8
+
+[Project]
+root = default
+folder = orange
+file = default
+title = #6bcc66
+chapter = #3fac39
+scene = #1c8217
+note = faded
+active = green
+inactive = orange
+disabled = #aab1aa
+
+[Palette]
+window = #ece5df
+windowtext = default
+base = base
+alternatebase = #dbd0c7
+text = default
+tooltipbase = #dcf0db
+tooltiptext = default
+button = base
+buttontext = default
+brighttext = base
+highlight = #4cb946
+highlightedtext = base
+link = #1c8217
+linkvisited = #1c8217
+accent = #bd5d0f
+
+[GUI]
+helptext = #696d69
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #f0ebe6
+link = blue
+headertext = #2b2c2a
+headertag = #828782
+emphasis = #4cb946
+dialog = #1c8217
+altdialog = #3fac39
+note = #d97726
+hidden = faded
+shortcode = #d97726
+keyword = #d97726
+tag = #bd5d0f
+value = #bd5d0f
+optional = faded
+spellcheckline = red
+errorline = green
+replacetag = #d97726
+modifier = faded
+texthighlight = green:80
diff --git a/novelwriter/assets/themes/b2t_suburb_dark.conf b/novelwriter/assets/themes/b2t_suburb_dark.conf
new file mode 100644
index 00000000..854808ca
--- /dev/null
+++ b/novelwriter/assets/themes/b2t_suburb_dark.conf
@@ -0,0 +1,75 @@
+[Main]
+name = B2T Suburb Dark
+mode = dark
+author = Myian
+credit = Bram de Haan
+url = https://base2t.one/demo/suburb/
+
+[Base]
+base = #25293d
+default = #d7dbf3
+faded = #888eaf
+red = #fb6fa9
+orange = #ff9b7d
+yellow = #ffdf8f
+green = #8bd59d
+cyan = #76cde3
+blue = #8696fd
+purple = #de95ee
+
+[Project]
+root = default
+folder = faded
+file = #b0a6aa
+title = #d14781
+chapter = #f764a1
+scene = #ff99c3
+note = #8696fd
+active = red
+inactive = faded
+disabled = #575c79
+
+[Palette]
+window = #1e212f
+windowtext = default
+base = base
+alternatebase = #444864
+text = default
+tooltipbase = #d2d8fe
+tooltiptext = base
+button = base
+buttontext = default
+brighttext = base
+highlight = red
+highlightedtext = #1e202f
+link = blue
+linkvisited = blue
+accent = red
+
+[GUI]
+helptext = #9b9fb6
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #b1b7d9
+line = #373a51
+link = blue
+headertext = red
+headertag = red:128
+emphasis = #ffffff
+dialog = #fe81b5
+altdialog = #ffb3d2
+note = #a0acfe
+hidden = blue
+shortcode = #6375ee
+keyword = faded
+tag = #a0acfe
+value = default
+optional = faded
+spellcheckline = #ff3b3b
+errorline = #7083ff
+replacetag = blue
+modifier = faded
+texthighlight = red:72
diff --git a/novelwriter/assets/themes/b2t_suburb_light.conf b/novelwriter/assets/themes/b2t_suburb_light.conf
new file mode 100644
index 00000000..f31c8c29
--- /dev/null
+++ b/novelwriter/assets/themes/b2t_suburb_light.conf
@@ -0,0 +1,75 @@
+[Main]
+name = B2T Suburb Light
+mode = light
+author = Myian
+credit = Bram de Haan
+url = https://base2t.one/demo/suburb/
+
+[Base]
+base = #fbf9fa
+default = #444864
+faded = #878ba6
+red = #d14781
+orange = #d38152
+yellow = #cac63a
+green = #378d60
+cyan = #308ab1
+blue = #5165e6
+purple = #9958be
+
+[Project]
+root = default
+folder = faded
+file = #b0a5a9
+title = red
+chapter = #f764a1
+scene = #ff99c3
+note = #8696fd
+active = red
+inactive = faded
+disabled = #b6bad1
+
+[Palette]
+window = #e9e5e7
+windowtext = default
+base = base
+alternatebase = #d7cbd0
+text = default
+tooltipbase = #d2d8fe
+tooltiptext = default
+button = #fbf9fa
+buttontext = default
+brighttext = base
+highlight = #f764a1
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = #fe81b5
+
+[GUI]
+helptext = #65697c
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #6e7396
+line = #eae1e5
+link = blue
+headertext = red
+headertag = red:128
+emphasis = #1e202f
+dialog = red
+altdialog = #fb6fa9
+note = blue
+hidden = blue
+shortcode = #8696fd
+keyword = faded
+tag = blue
+value = default
+optional = faded
+spellcheckline = #ff0000
+errorline = cyan
+replacetag = blue
+modifier = faded
+texthighlight = #f25a9944
diff --git a/novelwriter/assets/themes/b4t_classic_o_dark.conf b/novelwriter/assets/themes/b4t_classic_o_dark.conf
new file mode 100644
index 00000000..ce775630
--- /dev/null
+++ b/novelwriter/assets/themes/b4t_classic_o_dark.conf
@@ -0,0 +1,75 @@
+[Main]
+name = B4T Classic-O Dark
+mode = dark
+author = Myian
+credit = Bram de Haan
+url = https://atelierbram.github.io/Base4Tone-prism/b4t_classic-o/dark/
+
+[Base]
+base = #22272f
+default = #d0d5dd
+faded = #707F98
+red = #f17eaa
+orange = #eca277
+yellow = #f3cd91
+green = #7bd892
+cyan = #5ee3d1
+blue = #87b4fc
+purple = #d19af4
+
+[Project]
+root = default
+folder = #73a7fb
+file = default
+title = #039582
+chapter = #1BC3AD
+scene = #93ebdf
+note = #d19af4
+active = default
+inactive = faded
+disabled = #454f5f
+
+[Palette]
+window = #191d23
+windowtext = default
+base = base
+alternatebase = #333b47
+text = default
+tooltipbase = yellow
+tooltiptext = base
+button = #22272f
+buttontext = default
+brighttext = base
+highlight = #1bbba5
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = #1bbba5
+
+[GUI]
+helptext = default:192
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #333b47
+link = blue
+headertext = cyan
+headertag = cyan:128
+emphasis = #5fe2d1
+dialog = #87B4FC
+altdialog = #5A96F6
+note = #d19af4
+hidden = faded
+shortcode = #ba6cea
+keyword = #877af0
+tag = #a095f3
+value = faded
+optional = faded
+spellcheckline = #ff688d
+errorline = #5fe2d1
+replacetag = #877af0
+modifier = #ba6cea
+texthighlight = cyan:56
diff --git a/novelwriter/assets/themes/b4t_classic_o_light.conf b/novelwriter/assets/themes/b4t_classic_o_light.conf
new file mode 100644
index 00000000..0f9c94a1
--- /dev/null
+++ b/novelwriter/assets/themes/b4t_classic_o_light.conf
@@ -0,0 +1,75 @@
+[Main]
+name = B4T Classic-O Light
+mode = light
+author = Myian
+credit = Bram de Haan
+url = https://atelierbram.github.io/Base4Tone-prism/b4t_classic-o/light/
+
+[Base]
+base = #f6f7f9
+default = #333b47
+faded = #718098
+red = #c03b6e
+orange = #c46d26
+yellow = #bb8e46
+green = #3d9e29
+cyan = #039582
+blue = #4187f5
+purple = #aa52e0
+
+[Project]
+root = default
+folder = blue
+file = faded
+title = #007061
+chapter = #039582
+scene = #1abba6
+note = #c17eed
+active = default
+inactive = faded
+disabled = #acb5c3
+
+[Palette]
+window = #e7eaee
+windowtext = default
+base = base
+alternatebase = #d0d5dd
+text = default
+tooltipbase = #ffe8c4
+tooltiptext = default
+button = base
+buttontext = default
+brighttext = base
+highlight = #0c9c89
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = #0c9c89
+
+[GUI]
+helptext = default:192
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #dcdfe4
+link = blue
+headertext = cyan
+headertag = cyan:128
+emphasis = #008976
+dialog = #1a68e5
+altdialog = #1249a0
+note = #c17eed
+hidden = faded
+shortcode = purple
+keyword = #6555e0
+tag = #9488f2
+value = faded
+optional = faded
+spellcheckline = #ff2259
+errorline = blue
+replacetag = #7e6fe5
+modifier = purple
+texthighlight = cyan:64
diff --git a/novelwriter/assets/themes/b4t_modern_c_dark.conf b/novelwriter/assets/themes/b4t_modern_c_dark.conf
new file mode 100644
index 00000000..e348fbef
--- /dev/null
+++ b/novelwriter/assets/themes/b4t_modern_c_dark.conf
@@ -0,0 +1,75 @@
+[Main]
+name = B4T Modern-C Dark
+mode = dark
+author = Myian
+credit = Bram de Haan
+url = https://atelierbram.github.io/Base4Tone-prism/b4t_modern-c/dark/
+
+[Base]
+base = #25262c
+default = #e7e7ed
+faded = #9b9daa
+red = #f17eaa
+orange = #ffa978
+yellow = #f3cd91
+green = #46db6c
+cyan = #5fe2d1
+blue = #8393f6
+purple = #d292f7
+
+[Project]
+root = default
+folder = blue
+file = default
+title = #e64d87
+chapter = #f17ea9
+scene = #F6B1CB
+note = #18ae9a
+active = default
+inactive = faded
+disabled = #5d5f6f
+
+[Palette]
+window = #1b1c20
+windowtext = default
+base = base
+alternatebase = #40424e
+text = default
+tooltipbase = yellow
+tooltiptext = base
+button = #2e2f37
+buttontext = default
+brighttext = base
+highlight = yellow
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = yellow
+
+[GUI]
+helptext = default:192
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #373943
+link = blue
+headertext = red
+headertag = red:128
+emphasis = #f391b6
+dialog = yellow
+altdialog = #e86296
+note = #929ff7
+hidden = faded
+shortcode = #6f81f0
+keyword = #18ae9a
+tag = #93ece0
+value = #c5cdfb
+optional = faded
+spellcheckline = #e64d87
+errorline = blue
+replacetag = cyan
+modifier = #6f81f0
+texthighlight = blue:72
diff --git a/novelwriter/assets/themes/b4t_modern_c_light.conf b/novelwriter/assets/themes/b4t_modern_c_light.conf
new file mode 100644
index 00000000..b881167f
--- /dev/null
+++ b/novelwriter/assets/themes/b4t_modern_c_light.conf
@@ -0,0 +1,75 @@
+[Main]
+name = B4T Modern-C Light
+mode = light
+author = Myian
+credit = Bram de Haan
+url = https://atelierbram.github.io/Base4Tone-prism/b4t_modern-c/light/
+
+[Base]
+base = #f6f6f9
+default = #4a4c59
+faded = #848699
+red = #d22d6c
+orange = #d55c0b
+yellow = #d5880b
+green = #269b43
+cyan = #17a592
+blue = #5b6feb
+purple = #8e4fb3
+
+[Project]
+root = default
+folder = blue
+file = faded
+title = #931f4c
+chapter = #dd3f7c
+scene = #eb75a2
+note = #18ae9a
+active = default
+inactive = faded
+disabled = #BBBDC9
+
+[Palette]
+window = #e7e7ed
+windowtext = default
+base = base
+alternatebase = #d1d2dc
+text = default
+tooltipbase = #f9e6c8
+tooltiptext = default
+button = #ffffff
+buttontext = default
+brighttext = base
+highlight = yellow
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = yellow
+
+[GUI]
+helptext = default:192
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #e7e7ed
+link = blue
+headertext = red
+headertag = red:128
+emphasis = #d53874
+dialog = #9f6303
+altdialog = #e86296
+note = blue
+hidden = faded
+shortcode = #354ddd
+keyword = #007a6a
+tag = #18ae9a
+value = #8493F6
+optional = faded
+spellcheckline = red
+errorline = blue
+replacetag = cyan
+modifier = #354ddd
+texthighlight = blue:64
diff --git a/novelwriter/assets/themes/blue_streak_dark.conf b/novelwriter/assets/themes/blue_streak_dark.conf
new file mode 100644
index 00000000..49d19c42
--- /dev/null
+++ b/novelwriter/assets/themes/blue_streak_dark.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Blue Streak Dark
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Veronica Berglyd Olsen
+url = https://github.com/vkbo/novelWriter
+
+[Base]
+base = #2d2d2d
+default = #cccccc
+faded = #999999
+red = #ff6666
+orange = #ff9966
+yellow = #ffcc66
+green = #99cc99
+cyan = #66cccc
+blue = #6699cc
+purple = #cc99cc
+
+[Project]
+root = blue
+folder = default
+file = default
+title = blue:D150
+chapter = blue
+scene = blue:L150
+note = yellow
+active = blue:L150
+inactive = blue:D150
+disabled = faded
+
+[Palette]
+window = base:L125
+windowtext = default
+base = base
+alternatebase = base:L150
+text = default
+tooltipbase = yellow:L140
+tooltiptext = base:D150
+button = base
+buttontext = default
+brighttext = base
+highlight = blue
+highlightedtext = default:L120
+link = blue
+linkvisited = blue
+accent = blue
+
+[GUI]
+helptext = blue:L125
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = blue:48
+link = blue
+headertext = blue
+headertag = blue:D150
+emphasis = blue:L150
+dialog = blue
+altdialog = blue:L150
+note = blue:L150
+hidden = faded
+shortcode = blue:D125
+keyword = blue:D125
+tag = blue
+value = blue
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = blue:L150
+modifier = blue:D125
+texthighlight = blue:96
diff --git a/novelwriter/assets/themes/blue_streak_light.conf b/novelwriter/assets/themes/blue_streak_light.conf
new file mode 100644
index 00000000..9f52d1a6
--- /dev/null
+++ b/novelwriter/assets/themes/blue_streak_light.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Blue Streak Light
+mode = light
+author = Veronica Berglyd Olsen
+credit = Veronica Berglyd Olsen
+url = https://github.com/vkbo/novelWriter
+
+[Base]
+base = #fcfcfc
+default = #303030
+faded = #6c6c6c
+red = #cc3939
+orange = #cc7239
+yellow = #cca339
+green = #72a372
+cyan = #39a3a3
+blue = #3972a3
+purple = #a372a3
+
+[Project]
+root = blue
+folder = faded
+file = faded
+title = blue:D150
+chapter = blue
+scene = blue:L125
+note = yellow
+active = blue:L115
+inactive = blue:D150
+disabled = faded
+
+[Palette]
+window = base:D105
+windowtext = default
+base = base
+alternatebase = base:D110
+text = default
+tooltipbase = yellow:L150
+tooltiptext = default:D150
+button = base
+buttontext = default
+brighttext = base
+highlight = blue:L110
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = blue
+
+[GUI]
+helptext = blue
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = blue:32
+link = blue
+headertext = blue
+headertag = blue:D150
+emphasis = blue:L125
+dialog = blue
+altdialog = blue:L125
+note = blue:L125
+hidden = faded
+shortcode = blue:D125
+keyword = blue:D125
+tag = blue
+value = blue
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = blue:L125
+modifier = blue:D125
+texthighlight = blue:96
diff --git a/novelwriter/assets/themes/castle_day.conf b/novelwriter/assets/themes/castle_day.conf
new file mode 100644
index 00000000..ab57dcd8
--- /dev/null
+++ b/novelwriter/assets/themes/castle_day.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Castle by Day
+mode = light
+author = Myian
+
+[Base]
+base = #f3eeec
+default = #443d38
+faded = #8b827b
+red = #c95e5e
+orange = #d47643
+yellow = #b68730
+green = #4e8b30
+cyan = #539c9c
+blue = #687fca
+purple = #b261b4
+
+[Project]
+root = default
+folder = blue
+file = red
+title = green
+chapter = yellow
+scene = orange
+note = #9c9188
+active = default
+inactive = faded
+disabled = #b4aca5
+
+[Palette]
+window = base:D110
+windowtext = default
+base = base
+alternatebase = base:D118
+text = default
+tooltipbase = yellow:L230
+tooltiptext = default
+button = base
+buttontext = default
+brighttext = base
+highlight = #cf7c6d
+highlightedtext = base:L110
+link = #5068b6
+linkvisited = #5068b6
+accent = #cf7c6d
+
+[GUI]
+helptext = default:L180
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = base:D110
+link = blue
+headertext = yellow
+headertag = yellow:160
+emphasis = #aa791e
+dialog = green:D110
+altdialog = #378b8b
+note = faded
+hidden = faded
+shortcode = faded
+keyword = #ca4b4b
+tag = orange
+value = purple
+optional = #dd8c60
+spellcheckline = #e94444
+errorline = #378b8b
+replacetag = blue
+modifier = #963399
+texthighlight = red:80
diff --git a/novelwriter/assets/themes/castle_night.conf b/novelwriter/assets/themes/castle_night.conf
new file mode 100644
index 00000000..c4245236
--- /dev/null
+++ b/novelwriter/assets/themes/castle_night.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Castle by Night
+mode = dark
+author = Myian
+
+[Base]
+base = #1c1f28
+default = #d6d6d6
+faded = #878a91
+red = #fa8080
+orange = #ee9c70
+yellow = #fadd8d
+green = #afd49d
+cyan = #a4dad7
+blue = #9cb2ec
+purple = #d296d4
+
+[Project]
+root = default
+folder = blue
+file = red
+title = green
+chapter = yellow
+scene = orange
+note = #9b9ea5
+active = default
+inactive = faded
+disabled = #4b4f5c
+
+[Palette]
+window = base:D130
+windowtext = default
+base = base
+alternatebase = base:L160
+text = default
+tooltipbase = yellow:L120
+tooltiptext = base
+button = base:L110
+buttontext = default
+brighttext = base
+highlight = red:D150
+highlightedtext = default:L110
+link = blue
+linkvisited = blue
+accent = red:D150
+
+[GUI]
+helptext = faded
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = base:L150
+link = blue
+headertext = yellow
+headertag = yellow:160
+emphasis = yellow
+dialog = green
+altdialog = cyan
+note = faded
+hidden = faded
+shortcode = faded
+keyword = red
+tag = orange
+value = #e4b2e6
+optional = orange:L120
+spellcheckline = #ff5d5d
+errorline = cyan
+replacetag = blue
+modifier = purple
+texthighlight = red:72
diff --git a/novelwriter/assets/themes/chalky_soil.conf b/novelwriter/assets/themes/chalky_soil.conf
new file mode 100644
index 00000000..5e90b992
--- /dev/null
+++ b/novelwriter/assets/themes/chalky_soil.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Chalky Soil
+mode = light
+author = Myian
+
+[Base]
+base = #f0f0f0
+default = #494949
+faded = #8a8a8a
+red = #c45e6c
+orange = #c9700b
+yellow = #c09227
+green = #5f8f5a
+cyan = #3f9b96
+blue = #417db6
+purple = #975ea8
+
+[Project]
+root = default
+folder = green
+file = faded:D105
+title = red
+chapter = orange
+scene = yellow
+note = #6293c2
+active = green
+inactive = faded
+disabled = faded:L135
+
+[Palette]
+window = base:D108
+windowtext = default
+base = base
+alternatebase = base:D115
+text = default
+tooltipbase = #f1dcab
+tooltiptext = default
+button = base:D110
+buttontext = default
+brighttext = base
+highlight = green
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = green
+
+[GUI]
+helptext = default:L140
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = faded:48
+link = blue
+headertext = red
+headertag = red:128
+emphasis = #488843
+dialog = #b95a00
+altdialog = #b18010
+note = faded
+hidden = faded
+shortcode = blue
+keyword = blue
+tag = #276096
+value = #276096
+optional = faded
+spellcheckline = #f0344d
+errorline = cyan
+replacetag = purple
+modifier = blue
+texthighlight = green:80
diff --git a/novelwriter/assets/themes/chernozem.conf b/novelwriter/assets/themes/chernozem.conf
new file mode 100644
index 00000000..56f80f66
--- /dev/null
+++ b/novelwriter/assets/themes/chernozem.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Chernozem
+mode = dark
+author = Myian
+
+[Base]
+base = #2c2525
+default = #dad0ca
+faded = #857972
+red = #e06c6c
+orange = #ec935f
+yellow = #e5cc91
+green = #6e9e6a
+cyan = #72b69f
+blue = #7c9fbd
+purple = #c899d6
+
+[Project]
+root = default
+folder = green
+file = default
+title = red
+chapter = orange
+scene = yellow
+note = blue
+active = green
+inactive = faded
+disabled = #504742
+
+[Palette]
+window = #241d1d
+windowtext = default
+base = base
+alternatebase = faded:80
+text = default
+tooltipbase = yellow
+tooltiptext = base
+button = #362c2b
+buttontext = default
+brighttext = base
+highlight = #6ca167
+highlightedtext = #241d1d
+link = blue
+linkvisited = blue
+accent = #5a8d55
+
+[GUI]
+helptext = #a39891
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = faded:48
+link = blue
+headertext = red
+headertag = red:128
+emphasis = #85c47f
+dialog = orange
+altdialog = yellow
+note = faded
+hidden = faded
+shortcode = blue
+keyword = blue
+tag = #a1c4e4
+value = #a1c4e4
+optional = faded
+spellcheckline = #ff5555
+errorline = cyan
+replacetag = purple
+modifier = blue
+texthighlight = green:80
diff --git a/novelwriter/assets/themes/cyberpunk_night.conf b/novelwriter/assets/themes/cyberpunk_night.conf
index 125cab45..d433dabe 100644
--- a/novelwriter/assets/themes/cyberpunk_night.conf
+++ b/novelwriter/assets/themes/cyberpunk_night.conf
@@ -1,48 +1,74 @@
[Main]
-name = Cyberpunk Night
-description = A taste of the future 80s
-author = Anders Lemvigh
-url = https://github.com/alemvigh
-license = CC BY-SA 4.0
-licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
+name = Cyberpunk Night
+mode = dark
+author = Anders Lemvigh
+url = https://github.com/alemvigh
-[Icons]
-default = 136, 136, 136
-faded = 97, 97, 97
-red = 242, 72, 23
-orange = 255, 150, 10
-yellow = 255, 255, 0
-green = 0, 255, 0
-aqua = 0, 255, 255
-blue = 77, 77, 255
-purple = 50, 0, 180
+[Base]
+base = #000000
+default = #888888
+faded = #616161
+red = #f24817
+orange = #ff960a
+yellow = #ffff00
+green = #00ff00
+cyan = #00ffff
+blue = #4d4dff
+purple = #320064
[Project]
-root = 77, 77, 255
-folder = 255, 255, 0
-file = 136, 136, 136
-title = 0, 255, 0
-chapter = 242, 72, 23
-scene = 77, 77, 255
-note = 255, 255, 0
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
[Palette]
-window = 0, 0, 0
-windowtext = 150, 150, 150
-base = 0, 0, 0
-alternatebase = 40, 40, 40
-text = 150, 150, 150
-tooltipbase = 40, 20, 70
-tooltiptext = 255, 255, 255
-button = 5, 0, 10
-buttontext = 150, 150, 150
-brighttext = 255, 255, 255
-highlight = 50, 30, 80
-highlightedtext = 255, 255, 255
-link = 77, 77, 255
-linkvisited = 50, 0, 80
+window = base
+windowtext = #969696
+base = base
+alternatebase = #282828
+text = #969696
+tooltipbase = #281446
+tooltiptext = #ffffff
+button = #05000a
+buttontext = #969696
+brighttext = #ffffff
+highlight = #321e50
+highlightedtext = #ffffff
+link = blue
+linkvisited = #320050
+accent = #321e50
[GUI]
-helptext = 97, 97, 97
-fadedtext = 97, 97, 97
-errortext = 242, 72, 23
+helptext = faded
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #969696
+line = #282828
+link = blue
+headertext = #ffffff
+headertag = purple
+emphasis = cyan
+dialog = green
+altdialog = #008cff
+note = #969696
+hidden = #4d4d64
+shortcode = yellow
+keyword = #ff64ff
+tag = orange
+value = orange
+spellcheckline = red
+errorline = #bada04
+replacetag = #0000b4
+modifier = #908eb0
+optional = #b4b4b4
+texthighlight = #ffff8460
diff --git a/novelwriter/assets/themes/default.conf b/novelwriter/assets/themes/default.conf
deleted file mode 100644
index 9cd9cd5b..00000000
--- a/novelwriter/assets/themes/default.conf
+++ /dev/null
@@ -1,3 +0,0 @@
-[Main]
-name = Qt Default Theme
-description = Qt standard colours
diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf
index 76916604..1f7359bb 100644
--- a/novelwriter/assets/themes/default_dark.conf
+++ b/novelwriter/assets/themes/default_dark.conf
@@ -1,49 +1,75 @@
[Main]
-name = Default Dark Theme
-description = The novelWriter standard dark theme
-author = Veronica Berglyd Olsen
-credit = Veronica Berglyd Olsen
-url = https://github.com/vkbo/novelWriter
-license = CC BY-SA 4.0
-licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
+name = Default Dark Theme
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Veronica Berglyd Olsen
+url = https://github.com/vkbo/novelWriter
-[Icons]
-default = 184, 184, 184
-faded = 148, 148, 148
-red = 242, 119, 122
-orange = 249, 145, 57
-yellow = 255, 204, 102
-green = 153, 204, 153
-aqua = 102, 204, 204
-blue = 102, 153, 204
-purple = 204, 153, 204
+[Base]
+base = #373737
+default = #cccccc
+faded = #949494
+red = #ff6666
+orange = #ff9966
+yellow = #ffcc66
+green = #99cc99
+cyan = #66cccc
+blue = #6699cc
+purple = #cc99cc
[Project]
-root = 102, 153, 204
-folder = 255, 204, 102
-file = 184, 184, 184
-title = 153, 204, 153
-chapter = 242, 119, 122
-scene = 102, 153, 204
-note = 255, 204, 102
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
[Palette]
-window = 54, 54, 54
-windowtext = 204, 204, 204
-base = 62, 62, 62
-alternatebase = 78, 78, 78
-text = 204, 204, 204
-tooltipbase = 255, 255, 192
-tooltiptext = 21, 21, 13
-button = 62, 62, 62
-buttontext = 204, 204, 204
-brighttext = 62, 62, 62
-highlight = 44, 152, 247
-highlightedtext = 255, 255, 255
-link = 102, 153, 204
-linkvisited = 102, 153, 204
+window = base:L125
+windowtext = default
+base = base
+alternatebase = #4e4e4e
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = base
+buttontext = default
+brighttext = base
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = #2c98f7
[GUI]
-helptext = 164, 164, 164
-fadedtext = 148, 148, 148
-errortext = 255, 164, 164
+helptext = #a4a4a4
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = default:32
+link = blue
+headertext = green
+headertag = green:D150
+emphasis = orange
+dialog = blue
+altdialog = red
+note = yellow
+hidden = faded
+shortcode = blue
+keyword = red
+tag = green
+value = green
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = green
+modifier = blue
+texthighlight = yellow:72
diff --git a/novelwriter/assets/themes/default_light.conf b/novelwriter/assets/themes/default_light.conf
index 29b4b98a..da7784f5 100644
--- a/novelwriter/assets/themes/default_light.conf
+++ b/novelwriter/assets/themes/default_light.conf
@@ -1,49 +1,75 @@
[Main]
-name = Default Light Theme
-description = The novelWriter standard light theme
-author = Veronica Berglyd Olsen
-credit = Veronica Berglyd Olsen
-url = https://github.com/vkbo/novelWriter
-license = CC BY-SA 4.0
-licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
+name = Default Light Theme
+mode = light
+author = Veronica Berglyd Olsen
+credit = Veronica Berglyd Olsen
+url = https://github.com/vkbo/novelWriter
-[Icons]
-default = 72, 72, 72
-faded = 108, 108, 108
-red = 240, 40, 41
-orange = 245, 135, 31
-yellow = 234, 183, 0
-green = 113, 140, 0
-aqua = 62, 153, 159
-blue = 66, 113, 174
-purple = 137, 89, 168
+[Base]
+base = #fcfcfc
+default = #303030
+faded = #6c6c6c
+red = #a62a2d
+orange = #b36829
+yellow = #a39c34
+green = #296629
+cyan = #269999
+blue = #3a70a6
+purple = #b35ab3
[Project]
-root = 66, 113, 174
-folder = 234, 183, 0
-file = 72, 72, 72
-title = 113, 140, 0
-chapter = 240, 40, 41
-scene = 66, 113, 174
-note = 234, 183, 0
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
[Palette]
-window = 239, 239, 239
-windowtext = 0, 0, 0
-base = 255, 255, 255
-alternatebase = 224, 224, 224
-text = 0, 0, 0
-tooltipbase = 255, 255, 220
-tooltiptext = 0, 0, 0
-button = 239, 239, 239
-buttontext = 0, 0, 0
-brighttext = 255, 255, 255
-highlight = 48, 135, 198
-highlightedtext = 255, 255, 255
-link = 66, 113, 174
-linkvisited = 66, 113, 174
+window = base:D105
+windowtext = default
+base = base
+alternatebase = #e0e0e0
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #efefef
+buttontext = default
+brighttext = base
+highlight = #3087c6
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = #3087c6
[GUI]
-helptext = 92, 92, 92
-fadedtext = 108, 108, 108
-errortext = 255, 92, 92
+helptext = #5c5c5c
+fadedtext = #6c6c6c
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = default:32
+link = blue
+headertext = green
+headertag = green:L135
+emphasis = orange
+dialog = blue
+altdialog = red
+note = yellow:D125
+hidden = faded
+shortcode = blue
+keyword = red
+tag = green
+value = green
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = green
+modifier = blue
+texthighlight = yellow:72
diff --git a/novelwriter/assets/themes/dracula.conf b/novelwriter/assets/themes/dracula.conf
index 5805b84b..04fe9949 100644
--- a/novelwriter/assets/themes/dracula.conf
+++ b/novelwriter/assets/themes/dracula.conf
@@ -1,17 +1,16 @@
[Main]
-name = Dracula
-description = A dark theme with bright colours
-author = Veronica Berglyd Olsen (adaptation)
-credit = Zeno Rocha
-url = https://draculatheme.com
-license = MIT
-licenseurl = https://github.com/dracula/dracula-theme/blob/main/LICENSE
+name = Dracula
+mode = dark
+author = Veronica Berglyd Olsen (adaptation)
+credit = Zeno Rocha
+url = https://draculatheme.com
##
# Colours:
+# See: https://spec.draculatheme.com/
# Background = 282a36 : 40, 42, 54
# Foreground = f8f8f2 : 248, 248, 242
-# Current Line = 44475A : 68, 71, 90
+# Current Line = 44475a : 68, 71, 90
# Comment = 6272a4 : 98, 114, 164
# Cyan = 8be9fd : 139, 233, 253
# Green = 50fa7b : 80, 250, 123
@@ -22,43 +21,71 @@ licenseurl = https://github.com/dracula/dracula-theme/blob/main/LICENSE
# Yellow = f1fa8c : 241, 250, 140
##
-[Icons]
-default = 230, 230, 224
-faded = 98, 114, 164
-red = 255, 85, 85
-orange = 255, 184, 108
-yellow = 241, 250, 140
-green = 80, 250, 123
-aqua = 139, 233, 253
-blue = 147, 207, 249
-purple = 189, 147, 249
+[Base]
+base = #282a36
+default = #e6e6e0
+faded = #6272a4
+red = #ff5555
+orange = #ffb86c
+yellow = #f1fa8c
+green = #50fa7b
+cyan = #8be9fd
+blue = #93cff9
+purple = #bd93f9
[Project]
-root = 189, 147, 249
-folder = 241, 250, 140
-file = 230, 230, 224
-title = 80, 250, 123
-chapter = 255, 85, 85
-scene = 139, 233, 253
-note = 241, 250, 140
+root = purple
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = cyan
+note = yellow
+active = green
+inactive = red
+disabled = faded
[Palette]
-window = 68, 71, 90
-windowtext = 248, 248, 242
-base = 40, 42, 54
-alternatebase = 51, 54, 69
-text = 248, 248, 242
-tooltipbase = 241, 250, 140
-tooltiptext = 40, 42, 54
-button = 80, 83, 105
-buttontext = 248, 248, 242
-brighttext = 68, 71, 90
-highlight = 166, 129, 218
-highlightedtext = 248, 248, 242
-link = 139, 233, 253
-linkvisited = 139, 233, 253
+window = #44475a
+windowtext = #f8f8f2
+base = base
+alternatebase = #333645
+text = #f8f8f2
+tooltipbase = yellow
+tooltiptext = base
+button = #505369
+buttontext = #f8f8f2
+brighttext = base
+highlight = #a681da
+highlightedtext = #f8f8f2
+link = cyan
+linkvisited = cyan
+accent = #a681da
[GUI]
-helptext = 204, 172, 249
-fadedtext = 98, 114, 164
-errortext = 255, 85, 85
+helptext = #ccacf9
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #f8f8f2
+line = base:L150
+link = #ff79c6
+headertext = purple
+headertag = purple:D150
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #ffcce9
+hidden = faded
+shortcode = cyan
+keyword = #ff79c6
+tag = orange
+value = orange
+optional = green
+spellcheckline = red
+errorline = green
+replacetag = yellow
+modifier = cyan
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/espresso.conf b/novelwriter/assets/themes/espresso.conf
new file mode 100644
index 00000000..3d222896
--- /dev/null
+++ b/novelwriter/assets/themes/espresso.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Espresso
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Sainnhe Park and Antoine Cotten
+url = https://github.com/sainnhe/sonokai
+
+[Base]
+base = #2f2726
+default = #e5e4e2
+faded = #96817b
+red = #f86882
+orange = #f08d71
+yellow = #f0c66f
+green = #a6cd77
+cyan = #81d0c9
+blue = #89a8e0
+purple = #9fa0e1
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = base:L125
+windowtext = default
+base = base
+alternatebase = #51403c
+text = default
+tooltipbase = yellow:L125
+tooltiptext = base
+button = base
+buttontext = default
+brighttext = base
+highlight = #4e5f80
+highlightedtext = default
+link = blue
+linkvisited = blue
+accent = blue
+
+[GUI]
+helptext = blue
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #392e2c
+link = blue
+headertext = yellow:L125
+headertag = faded
+emphasis = orange
+dialog = yellow
+altdialog = orange
+note = yellow:L125
+hidden = faded
+shortcode = green
+keyword = red
+tag = green
+value = orange
+optional = yellow
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = blue
+texthighlight = yellow:108
diff --git a/novelwriter/assets/themes/everforest_dark.conf b/novelwriter/assets/themes/everforest_dark.conf
new file mode 100644
index 00000000..ff10cf93
--- /dev/null
+++ b/novelwriter/assets/themes/everforest_dark.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Everforest Dark
+mode = dark
+author = Myian
+credit = Sainnhe Park
+url = https://github.com/sainnhe/everforest
+
+[Base]
+base = #272e33
+default = #d3c6aa
+faded = #859289
+red = #e67e80
+orange = #e69875
+yellow = #dbbc7f
+green = #a7c080
+cyan = #83c092
+blue = #7fbbb3
+purple = #d699b6
+
+[Project]
+root = default
+folder = orange
+file = red
+title = green
+chapter = blue
+scene = purple
+note = faded
+active = cyan
+inactive = orange
+disabled = faded
+
+[Palette]
+window = #1e2326
+windowtext = default
+base = base
+alternatebase = #374145
+text = default
+tooltipbase = yellow
+tooltiptext = base
+button = #2e383c
+buttontext = default
+brighttext = base
+highlight = #a7c080
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = #a7c080
+
+[GUI]
+helptext = #a9a49d
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #343f44
+link = blue
+headertext = cyan
+headertag = cyan:128
+emphasis = blue
+dialog = green
+altdialog = cyan
+note = purple
+hidden = faded
+shortcode = orange
+keyword = orange
+tag = purple
+value = faded
+optional = faded
+spellcheckline = red
+errorline = cyan
+replacetag = purple
+modifier = red
+texthighlight = cyan:72
diff --git a/novelwriter/assets/themes/everforest_light.conf b/novelwriter/assets/themes/everforest_light.conf
new file mode 100644
index 00000000..901379ed
--- /dev/null
+++ b/novelwriter/assets/themes/everforest_light.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Everforest Light
+mode = light
+author = Myian
+credit = Sainnhe Park
+url = https://github.com/sainnhe/everforest
+
+[Base]
+base = #fffbef
+default = #5c6a72
+faded = #939f91
+red = #f85552
+orange = #f57d26
+yellow = #dfa000
+green = #8da101
+cyan = #35a77c
+blue = #3a94c5
+purple = #df69ba
+
+[Project]
+root = default
+folder = orange
+file = red
+title = green
+chapter = blue
+scene = purple
+note = faded
+active = cyan
+inactive = orange
+disabled = faded
+
+[Palette]
+window = #f2efdf
+windowtext = default
+base = base
+alternatebase = #e8e5d5
+text = default
+tooltipbase = #fef2d5
+tooltiptext = default
+button = #f8f5e4
+buttontext = default
+brighttext = base
+highlight = #93b259
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = #93b259
+
+[GUI]
+helptext = #829181
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #edeada
+link = blue
+headertext = cyan
+headertag = cyan:128
+emphasis = blue
+dialog = green
+altdialog = cyan
+note = purple
+hidden = faded
+shortcode = orange
+keyword = orange
+tag = purple
+value = faded
+optional = faded
+spellcheckline = red
+errorline = cyan
+replacetag = purple
+modifier = red
+texthighlight = cyan:64
diff --git a/novelwriter/assets/themes/floral_daydream.conf b/novelwriter/assets/themes/floral_daydream.conf
new file mode 100644
index 00000000..ecee703f
--- /dev/null
+++ b/novelwriter/assets/themes/floral_daydream.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Floral Daydream
+mode = light
+author = Myian
+
+[Base]
+base = #fff4f7
+default = #ac3b6f
+faded = #c285aa
+red = #e76b6d
+orange = #e08b46
+yellow = #cca333
+green = #59976b
+cyan = #5d9ea3
+blue = #658cc5
+purple = #9d77c9
+
+[Project]
+root = default
+folder = yellow
+file = orange
+title = blue
+chapter = purple
+scene = red
+note = #6dad7f
+active = green
+inactive = faded
+disabled = #e2bcce
+
+[Palette]
+window = #ffe3ea
+windowtext = default
+base = base
+alternatebase = #ffc7d8
+text = default
+tooltipbase = #fff0ba
+tooltiptext = #413309
+button = #ffffff
+buttontext = default
+brighttext = base
+highlight = #e4688d
+highlightedtext = #ffffff
+link = #4781d8
+linkvisited = #4781d8
+accent = #e4688d
+
+[GUI]
+helptext = #d14a73
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #974169
+line = #ffdbe4
+link = #4781d8
+headertext = #4781d8
+headertag = #4781d880
+emphasis = #8152b8
+dialog = #eb4073
+altdialog = #4781d8
+note = #31924c
+hidden = faded
+shortcode = #31924c
+keyword = #1a6d32
+tag = #31924c
+value = #ce7b1d
+optional = faded
+spellcheckline = #ff0000
+errorline = #1a6d32
+replacetag = #31924c
+modifier = #1a6d32
+texthighlight = #bae2c5
diff --git a/novelwriter/assets/themes/floral_midnight.conf b/novelwriter/assets/themes/floral_midnight.conf
new file mode 100644
index 00000000..c05bb27d
--- /dev/null
+++ b/novelwriter/assets/themes/floral_midnight.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Floral Midnight
+mode = dark
+author = Myian
+
+[Base]
+base = #212130
+default = #d3cfe7
+faded = #948dac
+red = #ff7b7d
+orange = #ffad6e
+yellow = #eedb94
+green = #79bd8b
+cyan = #8bcac8
+blue = #91b7ff
+purple = #b78ff0
+
+[Project]
+root = default
+folder = yellow
+file = orange
+title = blue
+chapter = purple
+scene = red
+note = green
+active = green
+inactive = faded
+disabled = #55516d
+
+[Palette]
+window = #181825
+windowtext = default
+base = base
+alternatebase = #3e395a
+text = default
+tooltipbase = yellow
+tooltiptext = yellow:D400
+button = #1f1d35
+buttontext = default
+brighttext = base
+highlight = #705a94
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = #b060c0
+
+[GUI]
+helptext = faded
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #3e395a
+link = blue
+headertext = blue
+headertag = blue:128
+emphasis = #ff9dd9
+dialog = #d19df3
+altdialog = blue
+note = #65ca80
+hidden = faded
+shortcode = #65ca80
+keyword = #a6fdbd
+tag = #65ca80
+value = yellow
+optional = faded
+spellcheckline = #ff4949
+errorline = #65ca80
+replacetag = #a6fdbd
+modifier = #a6fdbd
+texthighlight = green:96
diff --git a/novelwriter/assets/themes/full_moon.conf b/novelwriter/assets/themes/full_moon.conf
new file mode 100644
index 00000000..c6e2c864
--- /dev/null
+++ b/novelwriter/assets/themes/full_moon.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Full Moon
+mode = light
+author = Myian
+
+[Base]
+base = #ffffff
+default = #37404e
+faded = #93a2a8
+red = #d35654
+orange = #d17a09
+yellow = #caac00
+green = #579220
+cyan = #1b948a
+blue = #2d78b9
+purple = #c45fb3
+
+[Project]
+root = default
+folder = orange
+file = purple
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = #e7ebee
+windowtext = default
+base = base
+alternatebase = #d4d9dd
+text = default
+tooltipbase = #ffeea6
+tooltiptext = default
+button = base
+buttontext = default
+brighttext = base
+highlight = #5495ce
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = #5495ce
+
+[GUI]
+helptext = #586a72
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #586a72
+line = #e7ebee
+link = blue
+headertext = purple
+headertag = purple:128
+emphasis = blue
+dialog = #0e2a35
+altdialog = cyan
+note = green
+hidden = faded
+shortcode = #c5931f
+keyword = #c5931f
+tag = red
+value = orange
+optional = #c5931f
+spellcheckline = #f73c3f
+errorline = cyan
+replacetag = purple
+modifier = #c5931f
+texthighlight = blue:80
diff --git a/novelwriter/assets/themes/grey_dark.conf b/novelwriter/assets/themes/grey_dark.conf
new file mode 100644
index 00000000..665c7a1f
--- /dev/null
+++ b/novelwriter/assets/themes/grey_dark.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Grey Dark
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Veronica Berglyd Olsen
+url = https://github.com/vkbo/novelWriter
+
+[Base]
+base = #3e3e3e
+default = #c8c8c8
+faded = #949494
+red = #f2777a
+orange = #f99139
+yellow = #ffcc66
+green = #99cc99
+cyan = #66cccc
+blue = #6699cc
+purple = #cc99cc
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = #363636
+windowtext = default
+base = base
+alternatebase = #4e4e4e
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = base
+buttontext = default
+brighttext = base
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = #2c98f7
+
+[GUI]
+helptext = default:D120
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = #363636
+text = default
+line = default:32
+link = default
+headertext = default:L115
+headertag = default:D125
+emphasis = default
+dialog = default
+altdialog = default
+note = default
+hidden = faded
+shortcode = default:L115
+keyword = default:L115
+tag = default
+value = default
+optional = default:L115
+spellcheckline = red
+errorline = green
+replacetag = default:L115
+modifier = default:L115
+texthighlight = #ffffff40
diff --git a/novelwriter/assets/themes/grey_light.conf b/novelwriter/assets/themes/grey_light.conf
new file mode 100644
index 00000000..2106e5bf
--- /dev/null
+++ b/novelwriter/assets/themes/grey_light.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Grey Light
+mode = light
+author = Veronica Berglyd Olsen
+credit = Veronica Berglyd Olsen
+url = https://github.com/vkbo/novelWriter
+
+[Base]
+base = #ffffff
+default = #282828
+faded = #6c6c6c
+red = #a62a2d
+orange = #b36829
+yellow = #a68542
+green = #296629
+cyan = #269999
+blue = #3a70a6
+purple = #b35ab3
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = #efefef
+windowtext = default
+base = base
+alternatebase = #e0e0e0
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #efefef
+buttontext = default
+brighttext = base
+highlight = #3087c6
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = #3087c6
+
+[GUI]
+helptext = default:L225
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = default:32
+link = default
+headertext = default:D200
+headertag = default:L400
+emphasis = default
+dialog = default
+altdialog = default
+note = default
+hidden = faded
+shortcode = default:D200
+keyword = default:D200
+tag = default
+value = default
+optional = default:D200
+spellcheckline = red
+errorline = green
+replacetag = default:D200
+modifier = default:D200
+texthighlight = #00000040
diff --git a/novelwriter/assets/themes/horizon_dark.conf b/novelwriter/assets/themes/horizon_dark.conf
new file mode 100644
index 00000000..085e8c6d
--- /dev/null
+++ b/novelwriter/assets/themes/horizon_dark.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Horizon Dark
+mode = dark
+author = Myian
+credit = Jonathan Olaleye
+url = https://github.com/jolaleye/horizon-theme-vscode
+
+[Base]
+base = #1c1e26
+default = #d5d8da
+faded = #6c6f93
+red = #e95678
+orange = #f09383
+yellow = #fac29a
+green = #84c969
+cyan = #27d796
+blue = #25b2bc
+purple = #b877db
+
+[Project]
+root = default
+folder = purple
+file = yellow
+title = blue
+chapter = red
+scene = orange
+note = faded
+active = yellow
+inactive = faded
+disabled = #404263
+
+[Palette]
+window = base
+windowtext = default
+base = base
+alternatebase = #2e303e
+text = default
+tooltipbase = yellow
+tooltiptext = base
+button = #232530
+buttontext = default
+brighttext = base
+highlight = #fab28e
+highlightedtext = base
+link = #21bfc2
+linkvisited = #21bfc2
+accent = #FAB28E
+
+[GUI]
+helptext = default:192
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #232530
+link = blue
+headertext = red
+headertag = red:128
+emphasis = yellow
+dialog = orange
+altdialog = red
+note = blue
+hidden = faded
+shortcode = blue
+keyword = purple
+tag = blue
+value = faded
+optional = faded
+spellcheckline = #ff3d5d
+errorline = cyan
+replacetag = blue
+modifier = purple
+texthighlight = orange:80
diff --git a/novelwriter/assets/themes/horizon_light.conf b/novelwriter/assets/themes/horizon_light.conf
new file mode 100644
index 00000000..cee0a7f1
--- /dev/null
+++ b/novelwriter/assets/themes/horizon_light.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Horizon Bright
+mode = light
+author = Myian
+credit = Jonathan Olaleye
+url = https://github.com/jolaleye/horizon-theme-vscode
+
+[Base]
+base = #fdf0ed
+default = #1a1c23
+faded = #99827d
+red = #da103f
+orange = #dc3318
+yellow = #f77d26
+green = #5fa306
+cyan = #0fac72
+blue = #1d8991
+purple = #8931B9
+
+[Project]
+root = default
+folder = purple
+file = yellow
+title = blue
+chapter = red
+scene = orange
+note = faded
+active = #eb834f
+inactive = faded
+disabled = faded:L125
+
+[Palette]
+window = base
+windowtext = default
+base = base
+alternatebase = #fadad1
+text = default
+tooltipbase = #f9cbbe
+tooltiptext = default
+button = #fadad1
+buttontext = default
+brighttext = base
+highlight = #e68656
+highlightedtext = base
+link = #1eaeae
+linkvisited = #1eaeae
+accent = #f6661e
+
+[GUI]
+helptext = default:192
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #ffe7e0
+link = blue
+headertext = red
+headertag = red:128
+emphasis = #f6661e
+dialog = orange
+altdialog = red
+note = blue
+hidden = faded
+shortcode = blue
+keyword = purple
+tag = blue
+value = faded
+optional = faded
+spellcheckline = #ff3d5d
+errorline = #1eb980
+replacetag = blue
+modifier = purple
+texthighlight = orange:64
diff --git a/novelwriter/assets/themes/lcars.conf b/novelwriter/assets/themes/lcars.conf
new file mode 100644
index 00000000..ee553602
--- /dev/null
+++ b/novelwriter/assets/themes/lcars.conf
@@ -0,0 +1,75 @@
+[Main]
+name = LCARS
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Star Trek TNG
+url = https://github.com/vkbo/novelWriter
+
+[Base]
+base = #101010
+default = #c7c7c7
+faded = #757575
+red = #e24d5f
+orange = #ff9902
+yellow = #ffdc59
+green = #54e985
+cyan = #7ffff2
+blue = #009cff
+purple = #8aa5ff
+
+[Project]
+root = orange
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = blue
+inactive = red
+disabled = faded
+
+[Palette]
+window = base:L180
+windowtext = default
+base = base
+alternatebase = base:L300
+text = default
+tooltipbase = yellow:L140
+tooltiptext = base
+button = base:L140
+buttontext = default
+brighttext = base
+highlight = purple
+highlightedtext = base
+link = purple
+linkvisited = purple
+accent = orange
+
+[GUI]
+helptext = blue
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = yellow:48
+link = purple
+headertext = orange
+headertag = red
+emphasis = orange
+dialog = yellow
+altdialog = green
+note = purple
+hidden = faded
+shortcode = purple
+keyword = red
+tag = orange
+value = orange
+optional = purple
+spellcheckline = red
+errorline = red
+replacetag = purple
+modifier = red
+texthighlight = orange:96
diff --git a/novelwriter/assets/themes/light_owl.conf b/novelwriter/assets/themes/light_owl.conf
new file mode 100644
index 00000000..6a43afc0
--- /dev/null
+++ b/novelwriter/assets/themes/light_owl.conf
@@ -0,0 +1,95 @@
+[Main]
+name = Light Owl
+mode = light
+author = Veronica Berglyd Olsen
+credit = Sarah Drasner
+url = https://github.com/sdras/night-owl-vscode-theme
+
+##
+# Theme: Light Owl
+# Source: https://github.com/sdras/night-owl-vscode-theme
+# Credit: Sarah Drasner
+##
+# Colours:
+# Background = #fbfbfb
+# Current Line = #eaeaea
+# Selection = #e0e0e0
+# Foreground = #403f52
+# Comment = #989fb1
+# Red = #de3d3a
+# Orange = #e0af05
+# Yellow = #daaa01
+# Green = #08916a
+# Aqua = #2aa298
+# Blue = #288ed7
+# Purple = #964ac1
+##
+
+[Base]
+base = #fbfbfb
+default = #403f52
+faded = #989fb1
+red = #de3d3a
+orange = #e0af05
+yellow = #daaa01
+green = #08916a
+cyan = #2aa298
+blue = #288ed7
+purple = #964ac1
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = #eaeaea
+windowtext = default
+base = base
+alternatebase = #e0e0e0
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = base
+buttontext = default
+brighttext = base
+highlight = #3087c6
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = #3087c6
+
+[GUI]
+helptext = blue
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = default:32
+link = blue
+headertext = blue
+headertag = blue:160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = yellow:D175
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = orange
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = green
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/new_moon.conf b/novelwriter/assets/themes/new_moon.conf
new file mode 100644
index 00000000..4bbb2e58
--- /dev/null
+++ b/novelwriter/assets/themes/new_moon.conf
@@ -0,0 +1,75 @@
+[Main]
+name = New Moon
+mode = dark
+author = Myian
+credit = Tania Rascia
+url = https://github.com/taniarascia/new-moon
+
+[Base]
+base = #2d2d2d
+default = #d1d7e0
+faded = #777c85
+red = #f2777a
+orange = #fca369
+yellow = #ffeea6
+green = #92d192
+cyan = #76d4d6
+blue = #6ab0f3
+purple = #e1a6f2
+
+[Project]
+root = default
+folder = orange
+file = purple
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = #252525
+windowtext = default
+base = base
+alternatebase = #444444
+text = default
+tooltipbase = yellow
+tooltiptext = base
+button = #2d2d2d
+buttontext = default
+brighttext = base
+highlight = #4386c5
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = #4386c5
+
+[GUI]
+helptext = #999eaa
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #b3b9c5
+line = #444444
+link = blue
+headertext = purple
+headertag = purple:128
+emphasis = blue
+dialog = #ffffff
+altdialog = cyan
+note = green
+hidden = faded
+shortcode = yellow
+keyword = yellow
+tag = red
+value = #ffd479
+optional = yellow
+spellcheckline = #ff6265
+errorline = green
+replacetag = purple
+modifier = yellow
+texthighlight = blue:80
diff --git a/novelwriter/assets/themes/night_owl.conf b/novelwriter/assets/themes/night_owl.conf
new file mode 100644
index 00000000..716f7eb8
--- /dev/null
+++ b/novelwriter/assets/themes/night_owl.conf
@@ -0,0 +1,95 @@
+[Main]
+name = Night Owl
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Sarah Drasner
+url = https://github.com/sdras/night-owl-vscode-theme
+
+##
+# Theme: Night Owl
+# Source: https://github.com/sdras/night-owl-vscode-theme
+# Credit: Sarah Drasner
+##
+# Colours:
+# Background = #011627
+# Current Line = #434652
+# Selection = #32374d
+# Foreground = #d6deeb
+# Comment = #637777
+# Red = #f78c6c
+# Orange = #ecc48d
+# Yellow = #ffeb95
+# Green = #addb67
+# Aqua = #7fdbca
+# Blue = #82aaff
+# Purple = #c792ea
+##
+
+[Base]
+base = #011627
+default = #d6deeb
+faded = #637777
+red = #f78c6c
+orange = #ecc48d
+yellow = #ffeb95
+green = #addb67
+cyan = #7fdbca
+blue = #82aaff
+purple = #c792ea
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = base:L140
+windowtext = default
+base = base
+alternatebase = #32374d
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = base
+buttontext = default
+brighttext = base
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = #2c98f7
+
+[GUI]
+helptext = blue
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = default:48
+link = blue
+headertext = blue
+headertag = blue:160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = yellow:L115
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = orange
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = green
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/noctis.conf b/novelwriter/assets/themes/noctis.conf
new file mode 100644
index 00000000..235bb5dc
--- /dev/null
+++ b/novelwriter/assets/themes/noctis.conf
@@ -0,0 +1,107 @@
+[Main]
+name = Noctis
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Liviu Schera, and contributors
+url = https://github.com/liviuschera/noctis
+
+##
+# Noctis Lux : themes/noctis.json
+##
+# colors
+# selection.background : #107684 -> Palette.highlight
+# sideBar.background : #041d20 -> Palette.window
+# editor.background : #052529 -> Base.base
+# editor.foreground : #b2cacd -> Base.default
+# editor.selectionBackground : #169fb144 -> Syntax.line, Syntax.texthighlight
+# textLink.foreground : #40d4e7 -> Palette.link, Syntax.link
+# input.background : #052529 -> Palette.button
+# input.foreground : #cdd3de -> Palette.buttontext
+# notifications.foreground : #cdd3de -> Palette.tooltiptext
+# notifications.background : #073940 -> Palette.tooltipbase
+##
+# tokenColors
+# COMMENT : #5b858b -> Base.faded
+# TEXT : #b2cacd -> Syntax.text, Syntax.optional
+# KEYWORD : #df769b -> Base.red
+# VARIABLE : #e4b781 -> Palette.accent
+# ANNOTATION : #d67e5c -> Syntax.note, Syntax.emphasis
+# CONSTANT : #d5971a -> Base.yellow
+# TAG : #e66533 -> Base.orange
+# STRING : #49e9a6 -> Syntax.headertext, Palette.accent
+# STRINGINTERPOLATED : #16b673 -> Base.green
+# NUMBER : #7060eb -> Base.purple
+# FUNCTION : #16a3b6 -> Base.cyan
+# SUPPORT : #49d6e9 -> Syntax.replacetag
+# MISC : #49ace9 -> Base.blue
+# INVALID : #e3541c -> GUI.errortext
+##
+
+[Base]
+base = #052529
+default = #b2cacd
+faded = #5b858b
+red = #df769b
+orange = #e66533
+yellow = #d5971a
+green = #16b673
+cyan = #16a3b6
+blue = #49ace9
+purple = #7060eb
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = #041d20
+windowtext = default
+base = base
+alternatebase = base:L175
+text = default
+tooltipbase = #073940
+tooltiptext = #cdd3de
+button = base
+buttontext = #cdd3de
+brighttext = base
+highlight = #107684
+highlightedtext = default
+link = #40d4e7
+linkvisited = blue
+accent = #49e9a6
+
+[GUI]
+helptext = #e4b781
+fadedtext = faded
+errortext = #e3541c
+
+[Syntax]
+background = base
+text = #b2cacd
+line = #169fb132
+link = #40d4e7
+headertext = #49e9a6
+headertag = green:D125
+emphasis = #d67e5c
+dialog = green
+altdialog = blue
+note = #d67e5c
+hidden = faded
+shortcode = red
+keyword = yellow
+tag = blue
+value = red:D125
+optional = #b2cacd
+spellcheckline = red
+errorline = green
+replacetag = #49d6e9
+modifier = red
+texthighlight = #169fb144
diff --git a/novelwriter/assets/themes/noctis_lux.conf b/novelwriter/assets/themes/noctis_lux.conf
new file mode 100644
index 00000000..fa37b293
--- /dev/null
+++ b/novelwriter/assets/themes/noctis_lux.conf
@@ -0,0 +1,107 @@
+[Main]
+name = Noctis Lux
+mode = light
+author = Veronica Berglyd Olsen
+credit = Liviu Schera, and contributors
+url = https://github.com/liviuschera/noctis
+
+##
+# Noctis Lux : themes/lux.json
+##
+# colors
+# selection.background : #169fb155 -> Palette.highlight
+# sideBar.background : #f9f1e1 -> Palette.window
+# editor.background : #fef8ec -> Base.base
+# editor.foreground : #005661 -> Base.default
+# editor.selectionBackground : #ade2eb77 -> Syntax.line, Syntax.texthighlight
+# textLink.foreground : #00c6e0 -> Palette.link, Syntax.link
+# input.background : #fef8ec -> Palette.button
+# input.foreground : #6a7a7c -> Palette.buttontext
+# notifications.foreground : #005661 -> Palette.tooltiptext
+# notifications.background : #f2edde -> Palette.tooltipbase
+##
+# tokenColors
+# COMMENT : #8ca6a6 -> Base.faded
+# TEXT : #004d57 -> Syntax.text, Syntax.optional
+# KEYWORD : #ff5792 -> Base.red
+# VARIABLE : #fa8900 -> Palette.accent
+# ANNOTATION : #b3694d -> Syntax.note, Syntax.emphasis
+# CONSTANT : #a88c00 -> Base.yellow
+# TAG : #e64100 -> Base.orange
+# STRING : #00b368 -> Syntax.headertext, Palette.accent
+# STRINGINTERPOLATED : #009456 -> Base.green
+# NUMBER : #5842ff -> Base.purple
+# FUNCTION : #0095a8 -> Base.cyan
+# SUPPORT : #00bdd6 -> Syntax.replacetag
+# MISC : #0094f0 -> Base.blue
+# INVALID : #ff530f -> GUI.errortext
+##
+
+[Base]
+base = #fef8ec
+default = #005661
+faded = #8ca6a6
+red = #ff5792
+orange = #e64100
+yellow = #a88c00
+green = #009456
+cyan = #0095a8
+blue = #0094f0
+purple = #5842ff
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = #f9f1e1
+windowtext = default
+base = base
+alternatebase = base:D115
+text = default
+tooltipbase = #f2edde
+tooltiptext = #005661
+button = base
+buttontext = #6a7a7c
+brighttext = base
+highlight = #169fb155
+highlightedtext = default
+link = #00c6e0
+linkvisited = blue
+accent = #00b368
+
+[GUI]
+helptext = #fa8900
+fadedtext = faded
+errortext = #ff530f
+
+[Syntax]
+background = base
+text = #004d57
+line = #ade2eb56
+link = #00c6e0
+headertext = #00b368
+headertag = green:D125
+emphasis = #b3694d
+dialog = green
+altdialog = blue
+note = #b3694d
+hidden = faded
+shortcode = red
+keyword = yellow
+tag = blue
+value = red:D125
+optional = #004d57
+spellcheckline = red
+errorline = green
+replacetag = #00bdd6
+modifier = red
+texthighlight = #ade2eb77
diff --git a/novelwriter/assets/themes/nord.conf b/novelwriter/assets/themes/nord.conf
new file mode 100644
index 00000000..e2a2faf9
--- /dev/null
+++ b/novelwriter/assets/themes/nord.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Nord
+mode = dark
+author = Myian
+credit = Sven Greb
+url = https://github.com/nordtheme/nord
+
+[Base]
+base = #2e3440
+default = #d8dee9
+faded = #7b88a1
+red = #bf616a
+orange = #d08770
+yellow = #ebcb8b
+green = #a3be8c
+cyan = #88c0d0
+blue = #81a1c1
+purple = #b48ead
+
+[Project]
+root = default
+folder = yellow
+file = default
+title = purple
+chapter = blue
+scene = #8fbcbb
+note = #696f7e
+active = green
+inactive = faded
+disabled = #576279
+
+[Palette]
+window = #242933
+windowtext = default
+base = base
+alternatebase = #434c5e
+text = default
+tooltipbase = yellow
+tooltiptext = base
+button = #2e3440
+buttontext = default
+brighttext = base
+highlight = cyan
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = cyan
+
+[GUI]
+helptext = blue
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #3b4252
+link = purple
+headertext = cyan
+headertag = cyan:128
+emphasis = #8fbcbb
+dialog = blue
+altdialog = green
+note = orange
+hidden = faded
+shortcode = orange
+keyword = purple
+tag = #8fbcbb
+value = #8fbcbb
+optional = faded
+spellcheckline = #ff717f
+errorline = #b2da8f
+replacetag = orange
+modifier = purple
+texthighlight = cyan:64
diff --git a/novelwriter/assets/themes/nordlicht.conf b/novelwriter/assets/themes/nordlicht.conf
new file mode 100644
index 00000000..f8d9a846
--- /dev/null
+++ b/novelwriter/assets/themes/nordlicht.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Nordlicht
+mode = light
+author = Myian
+
+[Base]
+base = #ffffff
+default = #4c566a
+faded = #7b88a1
+red = #bf616a
+orange = #c97c64
+yellow = #daba7a
+green = #7cac78
+cyan = #6bacbe
+blue = #6b8ab8
+purple = #a36f9a
+
+[Project]
+root = default
+folder = #d4b067
+file = #6d7a96
+title = purple
+chapter = blue
+scene = #68a593
+note = #a5b3ca
+active = green
+inactive = faded
+disabled = #b2c0d6
+
+[Palette]
+window = #e5e9f0
+windowtext = default
+base = base
+alternatebase = #d8dee9
+text = default
+tooltipbase = #fff0d3
+tooltiptext = default
+button = #e5e9f0
+buttontext = default
+brighttext = base
+highlight = cyan
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = cyan
+
+[GUI]
+helptext = blue
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #eceff4
+link = purple
+headertext = #559db1
+headertag = #559db188
+emphasis = #549b86
+dialog = #5579a5
+altdialog = #619b5b
+note = orange
+hidden = faded
+shortcode = orange
+keyword = purple
+tag = #549481
+value = #549481
+optional = faded
+spellcheckline = #ff3b4f
+errorline = #58aa51
+replacetag = orange
+modifier = #a16e98
+texthighlight = cyan:64
diff --git a/novelwriter/assets/themes/otium_dark.conf b/novelwriter/assets/themes/otium_dark.conf
new file mode 100644
index 00000000..7319180a
--- /dev/null
+++ b/novelwriter/assets/themes/otium_dark.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Otium Dark
+mode = dark
+author = Myian
+
+[Base]
+base = #23242b
+default = #e9e7df
+faded = #7f868f
+red = #ff6e61
+orange = #ff9d66
+yellow = #ffcc6a
+green = #70ad79
+cyan = #4fb8a8
+blue = #5e83b1
+purple = #8479b4
+
+[Project]
+root = default
+folder = red
+file = default
+title = yellow
+chapter = green
+scene = blue
+note = faded
+active = default
+inactive = faded
+disabled = #4e545e
+
+[Palette]
+window = base
+windowtext = default
+base = base
+alternatebase = #3b3c47
+text = default
+tooltipbase = #b2cab6
+tooltiptext = base
+button = #262931
+buttontext = default
+brighttext = base
+highlight = #698a6e
+highlightedtext = #1b1c22
+link = green
+linkvisited = green
+accent = #698a6e
+
+[GUI]
+helptext = #a0a3a8
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = #1b1c22
+text = default
+line = #2a2b35
+link = #a6d0ed
+headertext = yellow
+headertag = yellow:128
+emphasis = yellow
+dialog = green
+altdialog = orange
+note = #a6d0ed
+hidden = faded
+shortcode = blue
+keyword = purple
+tag = #c5a9d7
+value = faded
+spellcheckline = red
+errorline = #a6d0ed
+replacetag = purple
+modifier = blue
+optional = faded
+texthighlight = #3b3c47
diff --git a/novelwriter/assets/themes/otium_light.conf b/novelwriter/assets/themes/otium_light.conf
new file mode 100644
index 00000000..062c7879
--- /dev/null
+++ b/novelwriter/assets/themes/otium_light.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Otium Light
+mode = light
+author = Myian
+
+[Base]
+base = #ebe9e4
+default = #3b3d42
+faded = #848691
+red = #c56961
+orange = #c98358
+yellow = #be9447
+green = #398144
+cyan = #3c918a
+blue = #567bac
+purple = #7a65ac
+
+[Project]
+root = default
+folder = red
+file = default:L165
+title = yellow
+chapter = #53945f
+scene = blue
+note = faded:L110
+active = default:L165
+inactive = faded:L110
+disabled = #adadad
+
+[Palette]
+window = base
+windowtext = default
+base = base
+alternatebase = base:D115
+text = default
+tooltipbase = #cce0cf
+tooltiptext = default
+button = #f8f7f3
+buttontext = default
+brighttext = base
+highlight = #6c9173
+highlightedtext = #ffffff
+link = green
+linkvisited = green
+accent = #6c9173
+
+[GUI]
+helptext = #57565e
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = #f8f7f3
+text = default
+line = #ebe9e4
+link = blue
+headertext = yellow
+headertag = yellow:128
+emphasis = #ad802a
+dialog = green
+altdialog = #c0652d
+note = #668bbd
+hidden = faded
+shortcode = blue
+keyword = purple
+tag = #9e88b8
+value = faded
+spellcheckline = #e43a2b
+errorline = blue
+replacetag = #8c77b2
+modifier = blue
+optional = faded
+texthighlight = base:D110
diff --git a/novelwriter/assets/themes/paragon.conf b/novelwriter/assets/themes/paragon.conf
new file mode 100644
index 00000000..7b05bee4
--- /dev/null
+++ b/novelwriter/assets/themes/paragon.conf
@@ -0,0 +1,74 @@
+[Main]
+name = Paragon
+mode = dark
+author = Myian
+credit = Diablo III
+
+[Base]
+base = #161d25
+default = #fdfdfd
+faded = #727283
+red = #ff4800
+orange = #ff8000
+yellow = #ffff00
+green = #49e949
+cyan = #6bd8bb
+blue = #99bbff
+purple = #ca5dee
+
+[Project]
+root = default
+folder = #c7b377
+file = default
+title = orange
+chapter = yellow
+scene = #6869ff
+note = cyan
+active = #00ffe1
+inactive = faded
+disabled = faded:D150
+
+[Palette]
+window = #10151b
+windowtext = default
+base = base
+alternatebase = #394452
+text = default
+tooltipbase = cyan
+tooltiptext = base
+button = base
+buttontext = default
+brighttext = base
+highlight = cyan:D125
+highlightedtext = base
+link = cyan
+linkvisited = cyan
+accent = cyan
+
+[GUI]
+helptext = #949baa
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #272d3a
+link = green
+headertext = green
+headertag = green:128
+emphasis = #99bbff
+dialog = cyan
+altdialog = green
+note = #c7b377
+hidden = faded
+shortcode = orange
+keyword = red
+tag = #6869ff
+value = #ffff64
+optional = faded
+spellcheckline = #ff0000
+errorline = cyan
+replacetag = purple
+modifier = red
+texthighlight = #99bbff60
diff --git a/novelwriter/assets/themes/primer_light.conf b/novelwriter/assets/themes/primer_light.conf
new file mode 100644
index 00000000..49850873
--- /dev/null
+++ b/novelwriter/assets/themes/primer_light.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Primer Light
+mode = light
+author = Myian
+credit = GitHub Inc.
+url = https://github.com/primer
+
+[Base]
+base = #ffffff
+default = #1f2328
+faded = #8c959f
+red = #cf222e
+orange = #e16f24
+yellow = #d4a72c
+green = #2da44e
+cyan = #339d9b
+blue = #0969da
+purple = #8557db
+
+[Project]
+root = #32383f
+folder = orange
+file = #49bcb7
+title = #116329
+chapter = #2e934a
+scene = #4ac26b
+note = yellow
+active = #24292f
+inactive = faded
+disabled = #afb8c1
+
+[Palette]
+window = #eaeef2
+windowtext = default
+base = base
+alternatebase = #dde3e8
+text = default
+tooltipbase = #fff8c5
+tooltiptext = default
+button = base
+buttontext = default
+brighttext = base
+highlight = green
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = green
+
+[GUI]
+helptext = #424a53
+fadedtext = #8c959f
+errortext = red
+
+[Syntax]
+background = base
+text = #424a53
+line = #eaeef2
+link = blue
+headertext = default
+headertag = #9ea7b0
+emphasis = #1a7f37
+dialog = #0d1117
+altdialog = cyan
+note = #bf8700
+hidden = #8c959f
+shortcode = #e85aad
+keyword = blue
+tag = #218bff
+value = orange
+optional = #54aeff
+spellcheckline = #fa4549
+errorline = cyan
+replacetag = purple
+modifier = #bc4c00
+texthighlight = #a7fbbe
diff --git a/novelwriter/assets/themes/primer_night.conf b/novelwriter/assets/themes/primer_night.conf
new file mode 100644
index 00000000..2b4e4156
--- /dev/null
+++ b/novelwriter/assets/themes/primer_night.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Primer Night
+mode = dark
+author = Myian
+credit = GitHub Inc.
+url = https://github.com/primer
+
+[Base]
+base = #0d1117
+default = #f0f6fc
+faded = #6e7681
+red = #ff7b72
+orange = #ffa657
+yellow = #f8e3a1
+green = #56d364
+cyan = #57CCC5
+blue = #79c0ff
+purple = #c398ff
+
+[Project]
+root = default
+folder = orange
+file = #8adfd7
+title = #238636
+chapter = #56d364
+scene = #aff5b4
+note = yellow
+active = default
+inactive = faded
+disabled = #484f58
+
+[Palette]
+window = #161b22
+windowtext = default
+base = base
+alternatebase = #30363d
+text = default
+tooltipbase = yellow
+tooltiptext = base
+button = #21262d
+buttontext = default
+brighttext = base
+highlight = #207c59
+highlightedtext = default
+link = blue
+linkvisited = blue
+accent = #207c59
+
+[GUI]
+helptext = #8b949e
+fadedtext = #6e7681
+errortext = #f85149
+
+[Syntax]
+background = base
+text = #b1bac4
+line = #21262d
+link = blue
+headertext = default
+headertag = #b1bac4
+emphasis = #7ee787
+dialog = #ffffff
+altdialog = #57ccc5
+note = #f8e3a1
+hidden = faded
+shortcode = #ff9bce
+keyword = #4493f8
+tag = blue
+value = #ffc680
+optional = #a5d6ff
+spellcheckline = #f85149
+errorline = cyan
+replacetag = purple
+modifier = orange
+texthighlight = #104C35
diff --git a/novelwriter/assets/themes/ruby_day.conf b/novelwriter/assets/themes/ruby_day.conf
new file mode 100644
index 00000000..5a56e472
--- /dev/null
+++ b/novelwriter/assets/themes/ruby_day.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Ruby Day
+mode = light
+author = Myian
+
+[Base]
+base = #ffeeee
+default = #444252
+faded = #8e8c9c
+red = #e46969
+orange = #df9355
+yellow = #dfb362
+green = #62a369
+cyan = #5c9da3
+blue = #6e73bb
+purple = #b67ac2
+
+[Project]
+root = default
+folder = orange
+file = #757285
+title = green
+chapter = red
+scene = blue
+note = purple
+active = default
+inactive = red
+disabled = faded
+
+[Palette]
+window = #f1d9d9
+windowtext = default
+base = base
+alternatebase = #e9c1c1
+text = default
+tooltipbase = #f0d0d0
+tooltiptext = default
+button = base
+buttontext = default
+brighttext = red
+highlight = #da3d3d
+highlightedtext = base
+link = #da3d3d
+linkvisited = #da3d3d
+accent = #c43939
+
+[GUI]
+helptext = #756777
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #343242
+line = #f3dbdd
+link = #5159cc
+headertext = #343242
+headertag = #34324280
+emphasis = #c05858
+dialog = #ce1d1d
+altdialog = #c45522
+note = #9f58ad
+hidden = faded
+shortcode = #2a9638
+keyword = #5159cc
+tag = #9f58ad
+value = #b98421
+optional = faded
+spellcheckline = #ff3333
+errorline = cyan
+replacetag = #5159cc
+modifier = #2a9638
+texthighlight = red:80
diff --git a/novelwriter/assets/themes/ruby_night.conf b/novelwriter/assets/themes/ruby_night.conf
new file mode 100644
index 00000000..37f60ac6
--- /dev/null
+++ b/novelwriter/assets/themes/ruby_night.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Ruby Night
+mode = dark
+author = Myian
+
+[Base]
+base = #18181c
+default = #dcd9e6
+faded = #817b94
+red = #ff6969
+orange = #f89d68
+yellow = #ebcf84
+green = #7fb88b
+cyan = #75c5ca
+blue = #7f8dcf
+purple = #c37ac5
+
+[Project]
+root = default
+folder = orange
+file = default
+title = green
+chapter = red
+scene = blue
+note = purple
+active = default
+inactive = red
+disabled = faded
+
+[Palette]
+window = #0e0e11
+windowtext = default
+base = base
+alternatebase = #29292e
+text = default
+tooltipbase = #fa9b9b
+tooltiptext = base
+button = #1c1c21
+buttontext = default
+brighttext = base
+highlight = #a52a2c
+highlightedtext = #ebd9d9
+link = red
+linkvisited = red
+accent = #a52a2c
+
+[GUI]
+helptext = #a5a1b3
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #29292e
+link = blue
+headertext = default
+headertag = default:128
+emphasis = #ff9d9d
+dialog = red
+altdialog = orange
+note = purple
+hidden = faded
+shortcode = green
+keyword = blue
+tag = purple
+value = yellow
+optional = faded
+spellcheckline = #ff2727
+errorline = cyan
+replacetag = blue
+modifier = green
+texthighlight = red:80
\ No newline at end of file
diff --git a/novelwriter/assets/themes/selenium_dark.conf b/novelwriter/assets/themes/selenium_dark.conf
new file mode 100644
index 00000000..a5c5cc1b
--- /dev/null
+++ b/novelwriter/assets/themes/selenium_dark.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Selenium Dark
+mode = dark
+author = Myian
+
+[Base]
+base = #15141a
+default = #a9a1b4
+faded = #786e87
+red = #c04358
+orange = #b1663a
+yellow = #917a3e
+green = #35834c
+cyan = #1b8186
+blue = #3268cc
+purple = #7d53b8
+
+[Project]
+root = default
+folder = red
+file = default
+title = purple
+chapter = blue
+scene = cyan
+note = #625b70
+active = #c7c0d2
+inactive = red
+disabled = #625b70
+
+[Palette]
+window = #0f0e13
+windowtext = #c7c0d2
+base = base
+alternatebase = #35303f
+text = #c7c0d2
+tooltipbase = default
+tooltiptext = base
+button = #1e1c25
+buttontext = default
+brighttext = base
+highlight = #625b70
+highlightedtext = #e4e0ec
+link = #9267d3
+linkvisited = #9267d3
+accent = red:D120
+
+[GUI]
+helptext = #888095
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #1e1c25
+link = blue
+headertext = #e4e0ec
+headertag = faded
+emphasis = #9267d3
+dialog = #e4e0ec
+altdialog = #c458ac
+note = red
+hidden = faded
+shortcode = cyan
+keyword = blue
+tag = red
+value = #c7c0d2
+optional = #c7c0d2
+spellcheckline = red:L120
+errorline = blue:L120
+replacetag = blue
+modifier = cyan
+texthighlight = #9267d370
diff --git a/novelwriter/assets/themes/selenium_light.conf b/novelwriter/assets/themes/selenium_light.conf
new file mode 100644
index 00000000..d1eefa1f
--- /dev/null
+++ b/novelwriter/assets/themes/selenium_light.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Selenium Light
+mode = light
+author = Myian
+
+[Base]
+base = #e7e3eb
+default = #463d52
+faded = #8a8197
+red = #c04358
+orange = #b1663a
+yellow = #917a3e
+green = #35834c
+cyan = #1b8186
+blue = #3268cc
+purple = #7d53b8
+
+[Project]
+root = default
+folder = red
+file = default
+title = purple
+chapter = blue
+scene = cyan
+note = faded
+active = default
+inactive = red
+disabled = faded
+
+[Palette]
+window = #d2cfda
+windowtext = #29252f
+base = base
+alternatebase = #c1bccd
+text = #29252f
+tooltipbase = #ebebf1
+tooltiptext = default
+button = #e6e3eb
+buttontext = default
+brighttext = base
+highlight = #797082
+highlightedtext = #f0eff3
+link = #623a9b
+linkvisited = #623a9b
+accent = red
+
+[GUI]
+helptext = default
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #62576f
+line = #d2cfda
+link = blue
+headertext = #29252f
+headertag = faded
+emphasis = purple
+dialog = #29252f
+altdialog = #c458ac
+note = red
+hidden = faded
+shortcode = cyan
+keyword = blue
+tag = red
+value = default
+optional = default
+spellcheckline = #e63653
+errorline = #3e77e2
+replacetag = blue
+modifier = cyan
+texthighlight = purple:64
diff --git a/novelwriter/assets/themes/sepia_dark.conf b/novelwriter/assets/themes/sepia_dark.conf
new file mode 100644
index 00000000..49deeedd
--- /dev/null
+++ b/novelwriter/assets/themes/sepia_dark.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Sepia Dark
+mode = dark
+author = Myian
+
+[Base]
+base = #302622
+default = #d6c4b2
+faded = #8d7a71
+red = #d16b6b
+orange = #d1886b
+yellow = #c49e58
+green = #919e59
+cyan = #71a392
+blue = #718ea8
+purple = #b8759e
+
+[Project]
+root = default
+folder = cyan
+file = default
+title = red
+chapter = yellow
+scene = green
+note = faded
+active = default
+inactive = red
+disabled = faded
+
+[Palette]
+window = #251b18
+windowtext = #ceb3a2
+base = base
+alternatebase = #4b3d37
+text = #ceb3a2
+tooltipbase = #f1daca
+tooltiptext = base
+button = #362520
+buttontext = default
+brighttext = base
+highlight = #885b4d
+highlightedtext = #e7d4c9
+link = orange
+linkvisited = orange
+accent = #885b4d
+
+[GUI]
+helptext = faded
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #c4a491
+line = #41342f
+link = cyan
+headertext = default
+headertag = default:160
+emphasis = #f1daca
+dialog = orange
+altdialog = red
+note = green
+hidden = faded
+shortcode = purple
+keyword = cyan
+tag = green
+value = yellow
+optional = yellow
+spellcheckline = #ff5d5d
+errorline = cyan
+replacetag = green
+modifier = purple
+texthighlight = orange:72
diff --git a/novelwriter/assets/themes/sepia_light.conf b/novelwriter/assets/themes/sepia_light.conf
new file mode 100644
index 00000000..a7c1d306
--- /dev/null
+++ b/novelwriter/assets/themes/sepia_light.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Sepia Light
+mode = light
+author = Myian
+
+[Base]
+base = #f1e7dc
+default = #635048
+faded = #a18375
+red = #d17a6b
+orange = #d1906b
+yellow = #c49e58
+green = #919e59
+cyan = #71a392
+blue = #718ea8
+purple = #b8759e
+
+[Project]
+root = #86685b
+folder = cyan
+file = #86685b
+title = red
+chapter = yellow
+scene = green
+note = #b6a096
+active = #86685b
+inactive = red
+disabled = #b6a096
+
+[Palette]
+window = #e2d3c1
+windowtext = default
+base = base
+alternatebase = #d8c4b2
+text = default
+tooltipbase = #e6dacd
+tooltiptext = default
+button = #f7efe6
+buttontext = default
+brighttext = base
+highlight = #a57f66
+highlightedtext = #f7efe6
+link = #ac5828
+linkvisited = #ac5828
+accent = #a57f66
+
+[GUI]
+helptext = #836050
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #74584c
+line = #e6dacd
+link = #458871
+headertext = default
+headertag = default:160
+emphasis = #6e2920
+dialog = #ac5828
+altdialog = #b44b4b
+note = #6e8021
+hidden = faded
+shortcode = #a75688
+keyword = #458871
+tag = #6e8021
+value = #af7f25
+optional = #af7f25
+spellcheckline = #ff5d5d
+errorline = cyan
+replacetag = #6e8021
+modifier = #a75688
+texthighlight = orange:96
diff --git a/novelwriter/assets/themes/snazzy.conf b/novelwriter/assets/themes/snazzy.conf
index 39ec3c87..0c36d884 100644
--- a/novelwriter/assets/themes/snazzy.conf
+++ b/novelwriter/assets/themes/snazzy.conf
@@ -1,48 +1,88 @@
[Main]
-name = Snazzy Light
-author = Veronica Berglyd Olsen (adaptation)
-credit = Florian Reuschel (color theme)
-url = https://github.com/loilo/vscode-snazzy-light
-license = MIT License
-licenseurl = https://github.com/loilo/vscode-snazzy-light/blob/master/LICENSE
+name = Snazzy Light
+mode = light
+author = Veronica Berglyd Olsen
+credit = Florian Reuschel, Sindre Sorhus
+url = https://github.com/loilo/vscode-snazzy-light
-[Icons]
-default = 86, 88, 105
-faded = 84, 85, 84
-red = 255, 92, 87
-orange = 245, 185, 0
-yellow = 207, 156, 0
-green = 45, 174, 88
-aqua = 19, 187, 183
-blue = 9, 161, 237
-purple = 247, 103, 187
+##
+# Colours:
+# Background = fafbfc : 250, 251, 252
+# Foreground = 565869 : 86, 88, 105
+# Comment = 9194a2 : 145, 148, 162
+# Yellow = cf9c00 : 207, 156, 0
+# Red = ff5c57 : 255, 92, 87
+# Pink = f767bb : 247, 103, 187
+# Blue = 09a1ed : 9, 161, 237
+# Cyan = 13bbb7 : 19, 187, 183
+# Green = 2dae58 : 45, 174, 88
+##
+
+[Base]
+base = #fafbfc
+default = #565869
+faded = #545554
+red = #ff5c57
+orange = #f5b900
+yellow = #cf9c00
+green = #2dae58
+cyan = #13bbb7
+blue = #09a1ed
+purple = #f767bb
[Project]
-root = 9, 161, 237
-folder = 207, 156, 0
-file = 84, 85, 84
-title = 45, 174, 88
-chapter = 255, 92, 87
-scene = 9, 161, 237
-note = 207, 156, 0
+root = blue
+folder = yellow
+file = faded
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
[Palette]
-window = 243, 244, 245
-windowtext = 86, 88, 105
-base = 250, 251, 252
-alternatebase = 234, 234, 235
-text = 86, 88, 105
-tooltipbase = 245, 233, 194
-tooltiptext = 86, 88, 105
-button = 250, 251, 252
-buttontext = 86, 88, 105
-brighttext = 255, 255, 255
-highlight = 9, 161, 237
-highlightedtext = 255, 255, 255
-link = 9, 161, 237
-linkvisited = 9, 161, 237
+window = #f3f4f5
+windowtext = default
+base = base
+alternatebase = #eaeaeb
+text = default
+tooltipbase = #f5e9c2
+tooltiptext = default
+button = base
+buttontext = default
+brighttext = base
+highlight = blue
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = blue
[GUI]
-helptext = 9, 161, 237
-fadedtext = 84, 85, 84
-errortext = 255, 92, 87
+helptext = blue
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = default:32
+link = blue
+headertext = green
+headertag = green:D125
+emphasis = purple
+dialog = blue
+altdialog = yellow
+note = cyan
+hidden = purple:D150
+shortcode = purple
+keyword = blue
+tag = green
+value = yellow
+optional = yellow
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = purple
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/solarized_dark.conf b/novelwriter/assets/themes/solarized_dark.conf
index 0f401eb9..4c73412f 100644
--- a/novelwriter/assets/themes/solarized_dark.conf
+++ b/novelwriter/assets/themes/solarized_dark.conf
@@ -1,48 +1,94 @@
[Main]
-name = Solarized Dark
-author = nullbasis
-credit = Ethan Schoonover
-url = https://ethanschoonover.com/solarized/
-license = MIT
-licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
+name = Solarized Dark
+mode = dark
+author = nullbasis and Veronica Berglyd Olsen
+credit = Ethan Schoonover
+url = https://ethanschoonover.com/solarized/
-[Icons]
-default = 230, 223, 206
-faded = 166, 161, 149
-red = 220, 50, 47
-orange = 203, 75, 22
-yellow = 181, 137, 0
-green = 133, 153, 0
-aqua = 42, 161, 152
-blue = 38, 139, 210
-purple = 211, 54, 130
+##
+# base03: #002b36
+# base02: #073642
+# base01: #586e75
+# base00: #657b83
+# base0: #839496
+# base1: #93a1a1
+# base2: #eee8d5
+# base3: #fdf6e3
+# yellow: #b58900
+# orange: #cb4b16
+# red: #dc322f
+# magenta: #d33682
+# violet: #6c71c4
+# blue: #268bd2
+# cyan: #2aa198
+# green: #859900
+##
+
+[Base]
+base = #002b36
+default = #fdf6e3
+faded = #eee8d5
+red = #dc322f
+orange = #cb4b16
+yellow = #b58900
+green = #859900
+cyan = #2aa198
+blue = #268bd2
+purple = #6c71c4
[Project]
-root = 42, 161, 152
-folder = 42, 161, 152
-file = 230, 223, 206
-title = 133, 153, 0
-chapter = 220, 50, 47
-scene = 38, 139, 210
-note = 181, 137, 0
+root = cyan
+folder = cyan
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
[Palette]
-window = 0, 43, 54
-windowtext = 253, 246, 227
-base = 7, 54, 66
-alternatebase = 88, 110, 117
-text = 253, 246, 227
-tooltipbase = 133, 153, 0
-tooltiptext = 0, 43, 54
-button = 7, 54, 66
-buttontext = 253, 246, 227
-brighttext = 7, 54, 66
-highlight = 42, 161, 152
-highlightedtext = 0, 43, 54
-link = 38, 139, 210
-linkvisited = 38, 139, 210
+window = #073642
+windowtext = default
+base = base
+alternatebase = #586e75
+text = default
+tooltipbase = yellow:L150
+tooltiptext = #002b36
+button = base
+buttontext = default
+brighttext = base
+highlight = cyan
+highlightedtext = default
+link = blue
+linkvisited = blue
+accent = cyan
[GUI]
-helptext = 101, 123, 131
-fadedtext = 101, 123, 131
-errortext = 220, 50, 47
+helptext = cyan
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #073642
+link = blue
+headertext = blue
+headertag = #657b83
+emphasis = blue
+dialog = cyan
+altdialog = red
+note = cyan:D125
+hidden = default:L200
+shortcode = blue
+keyword = green
+tag = yellow
+value = orange
+optional = cyan:D125
+spellcheckline = red
+errorline = red
+replacetag = green
+modifier = yellow
+texthighlight = yellow:128
diff --git a/novelwriter/assets/themes/solarized_light.conf b/novelwriter/assets/themes/solarized_light.conf
index 06f4cb20..61d701b7 100644
--- a/novelwriter/assets/themes/solarized_light.conf
+++ b/novelwriter/assets/themes/solarized_light.conf
@@ -1,48 +1,94 @@
[Main]
-name = Solarized Light
-author = nullbasis
-credit = Ethan Schoonover
-url = https://ethanschoonover.com/solarized/
-license = MIT
-licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
+name = Solarized Light
+mode = light
+author = nullbasis and Veronica Berglyd Olsen
+credit = Ethan Schoonover
+url = https://ethanschoonover.com/solarized/
-[Icons]
-default = 59, 68, 71
-faded = 78, 91, 95
-red = 220, 50, 47
-orange = 203, 75, 22
-yellow = 181, 137, 0
-green = 133, 153, 0
-aqua = 42, 161, 152
-blue = 38, 139, 210
-purple = 211, 54, 130
+##
+# base03: #002b36
+# base02: #073642
+# base01: #586e75
+# base00: #657b83
+# base0: #839496
+# base1: #93a1a1
+# base2: #eee8d5
+# base3: #fdf6e3
+# yellow: #b58900
+# orange: #cb4b16
+# red: #dc322f
+# magenta: #d33682
+# violet: #6c71c4
+# blue: #268bd2
+# cyan: #2aa198
+# green: #859900
+##
+
+[Base]
+base = #fdf6e3
+default = #002b36
+faded = #073642
+red = #dc322f
+orange = #cb4b16
+yellow = #b58900
+green = #859900
+cyan = #2aa198
+blue = #268bd2
+purple = #6c71c4
[Project]
-root = 42, 161, 152
-folder = 42, 161, 152
-file = 59, 68, 71
-title = 133, 153, 0
-chapter = 220, 50, 47
-scene = 38, 139, 210
-note = 181, 137, 0
+root = cyan
+folder = cyan
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
[Palette]
-window = 238, 232, 213
-windowtext = 0, 43, 54
-base = 253, 246, 227
-alternatebase = 147, 161, 161
-text = 0, 43, 54
-tooltipbase = 133, 153, 0
-tooltiptext = 0, 43, 54
-button = 238, 232, 213
-buttontext = 0, 43, 54
-brighttext = 253, 246, 227
-highlight = 42, 161, 152
-highlightedtext = 253, 246, 227
-link = 38, 139, 210
-linkvisited = 38, 139, 210
+window = #eee8d5
+windowtext = default
+base = base
+alternatebase = #93a1a1
+text = default
+tooltipbase = yellow:L150
+tooltiptext = #002b36
+button = base
+buttontext = default
+brighttext = base
+highlight = cyan
+highlightedtext = default
+link = blue
+linkvisited = blue
+accent = cyan
[GUI]
-helptext = 78, 91, 95
-fadedtext = 78, 91, 95
-errortext = 220, 50, 47
+helptext = cyan
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #eee8d5
+link = blue
+headertext = blue
+headertag = #657b83
+emphasis = blue
+dialog = cyan
+altdialog = red
+note = cyan:D125
+hidden = default:L200
+shortcode = blue
+keyword = green
+tag = yellow
+value = orange
+optional = cyan:D125
+spellcheckline = red
+errorline = red
+replacetag = green
+modifier = yellow
+texthighlight = yellow:128
diff --git a/novelwriter/assets/themes/sultana_light.conf b/novelwriter/assets/themes/sultana_light.conf
new file mode 100644
index 00000000..1f4e5195
--- /dev/null
+++ b/novelwriter/assets/themes/sultana_light.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Sultana Light
+mode = light
+author = Myian
+
+[Base]
+base = #fff6f6
+default = #494657
+faded = #a89d98
+red = #d3677e
+orange = #d6805e
+yellow = #cc9c49
+green = #819c42
+cyan = #5f9c88
+blue = #6591b6
+purple = #a279b9
+
+[Project]
+root = #5a5563
+folder = orange
+file = blue
+title = red
+chapter = yellow
+scene = green
+note = faded
+active = #77606e
+inactive = red
+disabled = faded
+
+[Palette]
+window = #f0e4e4
+windowtext = default
+base = base
+alternatebase = #e4cccc
+text = default
+tooltipbase = #ffeac6
+tooltiptext = default
+button = #fcf3f3
+buttontext = default
+brighttext = base
+highlight = #c57b85
+highlightedtext = #f9f4fc
+link = #5577bb
+linkvisited = #5577bb
+accent = #ad6978
+
+[GUI]
+helptext = default:192
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #77606e
+line = #f0e4e4
+link = #5577bb
+headertext = #66515e
+headertag = #66515e80
+emphasis = #5577bb
+dialog = #c54a5f
+altdialog = #4d968a
+note = #699128
+hidden = #aa9292
+shortcode = #4d968a
+keyword = #b89126
+tag = #4d968a
+value = #c77741
+optional = #aa9292
+spellcheckline = #ec5656
+errorline = #42a789
+replacetag = #ab69d1
+modifier = #b89126
+texthighlight = #b8788366
diff --git a/novelwriter/assets/themes/sultana_night.conf b/novelwriter/assets/themes/sultana_night.conf
new file mode 100644
index 00000000..bd55dd84
--- /dev/null
+++ b/novelwriter/assets/themes/sultana_night.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Sultana Night
+mode = dark
+author = Myian
+
+[Base]
+base = #2a222c
+default = #e2cdc9
+faded = #97889b
+red = #ec8ba0
+orange = #ffb29b
+yellow = #ffdfa9
+green = #bdd49d
+cyan = #9cd1bf
+blue = #92b5d4
+purple = #d39fd3
+
+[Project]
+root = default
+folder = orange
+file = blue
+title = red
+chapter = yellow
+scene = green
+note = faded
+active = default
+inactive = red
+disabled = #85748a
+
+[Palette]
+window = #221a23
+windowtext = default
+base = base
+alternatebase = #4a3e4d
+text = default
+tooltipbase = yellow
+tooltiptext = base
+button = #2d2130
+buttontext = default
+brighttext = base
+highlight = #9e5c70
+highlightedtext = default
+link = blue
+linkvisited = blue
+accent = #9e4c65
+
+[GUI]
+helptext = default:192
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #3e3341
+link = blue
+headertext = #f3eae0
+headertag = #f3eae080
+emphasis = blue
+dialog = red
+altdialog = cyan
+note = green
+hidden = faded
+shortcode = cyan
+keyword = yellow
+tag = cyan
+value = orange
+optional = faded
+spellcheckline = #ff7070
+errorline = #88ecba
+replacetag = purple
+modifier = yellow
+texthighlight = #9e5c7080
diff --git a/novelwriter/assets/themes/tango_dark.conf b/novelwriter/assets/themes/tango_dark.conf
new file mode 100644
index 00000000..8661ce27
--- /dev/null
+++ b/novelwriter/assets/themes/tango_dark.conf
@@ -0,0 +1,89 @@
+[Main]
+name = Tango Dark
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Tango Desktop Project
+url = https://en.wikipedia.org/wiki/Tango_Desktop_Project
+
+##
+# Colours:
+# Background = #30302f
+# Foreground = #eeeeec
+# Comment = #8f8f8d
+# Red = #ef2929
+# Orange = #c4a000
+# Yellow = #fce94f
+# Green = #8ae234
+# Cyan = #34e2e2
+# Blue = #3465a4
+# Purple = #ad7fa8
+##
+
+[Base]
+base = #2e3436
+default = #eeeeec
+faded = #babdb6
+red = #ef2929
+orange = #fcaf3e
+yellow = #fce94f
+green = #8ae234
+cyan = #34e2e2
+blue = #729fcf
+purple = #ad7fa8
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = base:L140
+windowtext = default
+base = base
+alternatebase = #454e51
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #555753
+buttontext = default
+brighttext = #555753
+highlight = blue
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = blue
+
+[GUI]
+helptext = orange
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = default:32
+link = blue
+headertext = blue
+headertag = blue:160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = yellow:L115
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = red
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = blue
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tango_light.conf b/novelwriter/assets/themes/tango_light.conf
new file mode 100644
index 00000000..379b0a72
--- /dev/null
+++ b/novelwriter/assets/themes/tango_light.conf
@@ -0,0 +1,89 @@
+[Main]
+name = Tango Light
+mode = light
+author = Veronica Berglyd Olsen
+credit = Tango Desktop Project
+url = https://en.wikipedia.org/wiki/Tango_Desktop_Project
+
+##
+# Colours:
+# Background = #eeeeec
+# Foreground = #2e3436
+# Comment = #8f8f8d
+# Red = #a40000
+# Orange = #c4a000
+# Yellow = #fce94f
+# Green = #8ae234
+# Cyan = #34e2e2
+# Blue = #3465a4
+# Purple = #ad7fa8
+##
+
+[Base]
+base = #eeeeec
+default = #2e3436
+faded = #888a85
+red = #a40000
+orange = #ce5c00
+yellow = #c4a000
+green = #4e9a06
+cyan = #069a9a
+blue = #204a87
+purple = #5c3566
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = base:D110
+windowtext = default
+base = base
+alternatebase = #e0e0e0
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #d3d7cf
+buttontext = default
+brighttext = #d3d7cf
+highlight = blue:L200
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = blue:L150
+
+[GUI]
+helptext = orange
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = default:32
+link = blue
+headertext = blue
+headertag = blue:160
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = yellow:D125
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = red
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = blue
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow.conf b/novelwriter/assets/themes/tomorrow.conf
new file mode 100644
index 00000000..15abcd32
--- /dev/null
+++ b/novelwriter/assets/themes/tomorrow.conf
@@ -0,0 +1,95 @@
+[Main]
+name = Tomorrow
+mode = light
+author = Veronica Berglyd Olsen
+credit = Chris Kempson
+url = https://github.com/chriskempson/tomorrow-theme
+
+##
+# Theme: Tomorrow
+# Source: https://github.com/chriskempson/tomorrow-theme
+# Credit: Chris Kempson
+##
+# Colours:
+# Background = #ffffff
+# Current Line = #efefef
+# Selection = #d6d6d6
+# Foreground = #4d4d4c
+# Comment = #8e908c
+# Red = #c82829
+# Orange = #f5871f
+# Yellow = #eab700
+# Green = #718c00
+# Aqua = #3e999f
+# Blue = #4271ae
+# Purple = #8959a8
+##
+
+[Base]
+base = #ffffff
+default = #484848
+faded = #6c6c6c
+red = #c82829
+orange = #f5871f
+yellow = #eab700
+green = #718c00
+cyan = #3e999f
+blue = #4271ae
+purple = #8959a8
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = #efefef
+windowtext = #000000
+base = base
+alternatebase = #d6d6d6
+text = #000000
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = #efefef
+buttontext = #000000
+brighttext = #efefef
+highlight = blue
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = blue
+
+[GUI]
+helptext = #5c5c5c
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #4d4d4c
+line = default:32
+link = blue
+headertext = blue
+headertag = blue:L135
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #b38c00
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = yellow
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = orange
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow_night.conf b/novelwriter/assets/themes/tomorrow_night.conf
new file mode 100644
index 00000000..ac213a76
--- /dev/null
+++ b/novelwriter/assets/themes/tomorrow_night.conf
@@ -0,0 +1,95 @@
+[Main]
+name = Tomorrow Night
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Chris Kempson
+url = https://github.com/chriskempson/tomorrow-theme
+
+##
+# Theme: Tomorrow Night
+# Source: https://github.com/chriskempson/tomorrow-theme
+# Credit: Chris Kempson
+##
+# Colours:
+# Background = #1d1f21
+# Current Line = #282a2e
+# Selection = #373b41
+# Foreground = #c5c8c6
+# Comment = #969896
+# Red = #cc6666
+# Orange = #de935f
+# Yellow = #f0c674
+# Green = #b5bd68
+# Aqua = #8abeb7
+# Blue = #81a2be
+# Purple = #b294bb
+##
+
+[Base]
+base = #1d1f21
+default = #c5c8c6
+faded = #969896
+red = #cc6666
+orange = #de935f
+yellow = #f0c674
+green = #b5bd68
+cyan = #8abeb7
+blue = #81a2be
+purple = #b294bb
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = #282a2e
+windowtext = default
+base = base
+alternatebase = #373b41
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = base
+buttontext = default
+brighttext = base
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = #2c98f7
+
+[GUI]
+helptext = #a4a4a4
+fadedtext = #949494
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = default:48
+link = blue
+headertext = blue
+headertag = blue:D150
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #f0dbb2
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = yellow
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = orange
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow_night_blue.conf b/novelwriter/assets/themes/tomorrow_night_blue.conf
new file mode 100644
index 00000000..05193e6f
--- /dev/null
+++ b/novelwriter/assets/themes/tomorrow_night_blue.conf
@@ -0,0 +1,95 @@
+[Main]
+name = Tomorrow Night Blue
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Chris Kempson
+url = https://github.com/chriskempson/tomorrow-theme
+
+##
+# Theme: Tomorrow Night Blue
+# Source: https://github.com/chriskempson/tomorrow-theme
+# Credit: Chris Kempson
+##
+# Colours:
+# Background = #002451
+# Current Line = #00346e
+# Selection = #003f8e
+# Foreground = #ffffff
+# Comment = #7285b7
+# Red = #ff9da4
+# Orange = #ffc58f
+# Yellow = #ffeead
+# Green = #d1f1a9
+# Aqua = #99ffff
+# Blue = #bbdaff
+# Purple = #ebbbff
+##
+
+[Base]
+base = #002451
+default = #ffffff
+faded = #7285b7
+red = #ff9da4
+orange = #ffc58f
+yellow = #ffeead
+green = #d1f1a9
+cyan = #99ffff
+blue = #bbdaff
+purple = #ebbbff
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = base:L125
+windowtext = default
+base = base
+alternatebase = #003f8e
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = base
+buttontext = default
+brighttext = base
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = #2c98f7
+
+[GUI]
+helptext = #a4a4a4
+fadedtext = #949494
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = default:32
+link = blue
+headertext = blue
+headertag = blue:D150
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #fff4cc
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = yellow
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = orange
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow_night_bright.conf b/novelwriter/assets/themes/tomorrow_night_bright.conf
new file mode 100644
index 00000000..514940fa
--- /dev/null
+++ b/novelwriter/assets/themes/tomorrow_night_bright.conf
@@ -0,0 +1,95 @@
+[Main]
+name = Tomorrow Night Bright
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Chris Kempson
+url = https://github.com/chriskempson/tomorrow-theme
+
+##
+# Theme: Tomorrow Night Bright
+# Source: https://github.com/chriskempson/tomorrow-theme
+# Credit: Chris Kempson
+##
+# Colours:
+# Background = #000000
+# Current Line = #2a2a2a
+# Selection = #424242
+# Foreground = #eaeaea
+# Comment = #969896
+# Red = #d54e53
+# Orange = #e78c45
+# Yellow = #e7c547
+# Green = #b9ca4a
+# Aqua = #70c0b1
+# Blue = #7aa6da
+# Purple = #c397d8
+##
+
+[Base]
+base = #000000
+default = #eaeaea
+faded = #969896
+red = #d54e53
+orange = #e78c45
+yellow = #e7c547
+green = #b9ca4a
+cyan = #70c0b1
+blue = #7aa6da
+purple = #c397d8
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = #181818
+windowtext = default
+base = base
+alternatebase = #424242
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = base
+buttontext = default
+brighttext = base
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = #2c98f7
+
+[GUI]
+helptext = #a4a4a4
+fadedtext = #949494
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = default:48
+link = blue
+headertext = blue
+headertag = blue:D150
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #e7d696
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = yellow
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = orange
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/tomorrow_night_eighties.conf b/novelwriter/assets/themes/tomorrow_night_eighties.conf
new file mode 100644
index 00000000..154b6ef4
--- /dev/null
+++ b/novelwriter/assets/themes/tomorrow_night_eighties.conf
@@ -0,0 +1,95 @@
+[Main]
+name = Tomorrow Night Eighties
+mode = dark
+author = Veronica Berglyd Olsen
+credit = Chris Kempson
+url = https://github.com/chriskempson/tomorrow-theme
+
+##
+# Theme: Tomorrow Night Eighties
+# Source: https://github.com/chriskempson/tomorrow-theme
+# Credit: Chris Kempson
+##
+# Colours:
+# Background = #2d2d2d
+# Current Line = #393939
+# Selection = #515151
+# Foreground = #cccccc
+# Comment = #999999
+# Red = #f2777a
+# Orange = #f99157
+# Yellow = #ffcc66
+# Green = #99cc99
+# Aqua = #66cccc
+# Blue = #6699cc
+# Purple = #cc99cc
+##
+
+[Base]
+base = #2d2d2d
+default = #cccccc
+faded = #999999
+red = #f2777a
+orange = #f99157
+yellow = #ffcc66
+green = #99cc99
+cyan = #66cccc
+blue = #6699cc
+purple = #cc99cc
+
+[Project]
+root = blue
+folder = yellow
+file = default
+title = green
+chapter = red
+scene = blue
+note = yellow
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = #393939
+windowtext = default
+base = base
+alternatebase = #515151
+text = default
+tooltipbase = #ffffc0
+tooltiptext = #15150d
+button = base
+buttontext = default
+brighttext = base
+highlight = #2c98f7
+highlightedtext = #ffffff
+link = blue
+linkvisited = blue
+accent = #2c98f7
+
+[GUI]
+helptext = #a4a4a4
+fadedtext = #949494
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = default:32
+link = blue
+headertext = blue
+headertag = blue:D150
+emphasis = orange
+dialog = green
+altdialog = yellow
+note = #ffe6b3
+hidden = faded
+shortcode = blue
+keyword = red
+tag = purple
+value = yellow
+optional = blue
+spellcheckline = red
+errorline = green
+replacetag = cyan
+modifier = orange
+texthighlight = yellow:96
diff --git a/novelwriter/assets/themes/vivid_black_green.conf b/novelwriter/assets/themes/vivid_black_green.conf
new file mode 100644
index 00000000..f2d17043
--- /dev/null
+++ b/novelwriter/assets/themes/vivid_black_green.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Vivid Black Green
+mode = dark
+author = Myian
+
+[Base]
+base = #0c0f16
+default = #f1f1f1
+faded = #888e9e
+red = #ff3a64
+orange = #ff823e
+yellow = #fff461
+green = #3dff5d
+cyan = #3ff3f0
+blue = #2f9aff
+purple = #ae70ff
+
+#magenta = #ff61e5
+
+[Project]
+root = default
+folder = yellow
+file = default
+title = blue
+chapter = cyan
+scene = green
+note = red
+active = green
+inactive = faded
+disabled = faded:D175
+
+[Palette]
+window = base
+windowtext = default
+base = base
+alternatebase = #323742
+text = default
+tooltipbase = green:L140
+tooltiptext = base
+button = #131622
+buttontext = default
+brighttext = base
+highlight = green:D125
+highlightedtext = base
+link = green
+linkvisited = green
+accent = green:D125
+
+[GUI]
+helptext = faded
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #1c1f27
+link = blue
+headertext = cyan
+headertag = cyan:128
+emphasis = blue
+dialog = green
+altdialog = cyan
+note = red
+hidden = faded
+shortcode = #ff61e5
+keyword = yellow
+tag = orange
+value = purple
+optional = faded
+spellcheckline = #ff3737
+errorline = cyan
+replacetag = orange
+modifier = #ff61e5
+texthighlight = green:80
diff --git a/novelwriter/assets/themes/vivid_black_red.conf b/novelwriter/assets/themes/vivid_black_red.conf
new file mode 100644
index 00000000..fd7d99f2
--- /dev/null
+++ b/novelwriter/assets/themes/vivid_black_red.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Vivid Black Red
+mode = dark
+author = Myian
+
+[Base]
+base = #0c0f16
+default = #f1f1f1
+faded = #888e9e
+red = #ff3a64
+orange = #ff823e
+yellow = #fff461
+green = #3dff5d
+cyan = #3ff3f0
+blue = #2f9aff
+purple = #ae70ff
+
+#magenta = #ff61e5
+
+[Project]
+root = default
+folder = yellow
+file = default
+title = purple
+chapter = #ff61e5
+scene = red
+note = green
+active = red
+inactive = faded
+disabled = faded:D175
+
+[Palette]
+window = base
+windowtext = default
+base = base
+alternatebase = #323742
+text = default
+tooltipbase = red:L150
+tooltiptext = base
+button = #131622
+buttontext = default
+brighttext = base
+highlight = red
+highlightedtext = base
+link = red
+linkvisited = red
+accent = red
+
+[GUI]
+helptext = faded
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #1c1f27
+link = blue
+headertext = yellow
+headertag = yellow:128
+emphasis = orange
+dialog = red
+altdialog = yellow
+note = green
+hidden = faded
+shortcode = cyan
+keyword = #ff61e5
+tag = purple
+value = blue
+optional = faded
+spellcheckline = #ff3737
+errorline = cyan
+replacetag = #ff61e5
+modifier = cyan
+texthighlight = red:80
\ No newline at end of file
diff --git a/novelwriter/assets/themes/vivid_white_green.conf b/novelwriter/assets/themes/vivid_white_green.conf
new file mode 100644
index 00000000..7c299bdc
--- /dev/null
+++ b/novelwriter/assets/themes/vivid_white_green.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Vivid White Green
+mode = light
+author = Myian
+
+[Base]
+base = #ffffff
+default = #222127
+faded = #78748a
+red = #e72b44
+orange = #e46802
+yellow = #bd9205
+green = #009e1a
+cyan = #0093a7
+blue = #3d58f1
+purple = #7c17ff
+
+#magenta = #ed14f5
+
+[Project]
+root = default
+folder = yellow
+file = faded
+title = blue
+chapter = cyan
+scene = green
+note = red
+active = green
+inactive = faded
+disabled = faded:L135
+
+[Palette]
+window = base
+windowtext = default
+base = base
+alternatebase = #d8dce0
+text = default
+tooltipbase = green:L275
+tooltiptext = default
+button = base
+buttontext = default
+brighttext = base
+highlight = green
+highlightedtext = base
+link = green
+linkvisited = green
+accent = green
+
+[GUI]
+helptext = default:L250
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #f3f3f8
+link = blue
+headertext = cyan
+headertag = cyan:128
+emphasis = blue
+dialog = green
+altdialog = cyan
+note = red
+hidden = faded
+shortcode = #ed14f5
+keyword = yellow
+tag = orange
+value = purple
+optional = faded
+spellcheckline = red
+errorline = cyan
+replacetag = orange
+modifier = #ed14f5
+texthighlight = green:72
diff --git a/novelwriter/assets/themes/vivid_white_red.conf b/novelwriter/assets/themes/vivid_white_red.conf
new file mode 100644
index 00000000..e77b42b3
--- /dev/null
+++ b/novelwriter/assets/themes/vivid_white_red.conf
@@ -0,0 +1,75 @@
+[Main]
+name = Vivid White Red
+mode = light
+author = Myian
+
+[Base]
+base = #ffffff
+default = #222127
+faded = #78748a
+red = #e72b44
+orange = #e46802
+yellow = #bd9205
+green = #009e1a
+cyan = #0093a7
+blue = #3d58f1
+purple = #7c17ff
+
+#magenta = #ed14f5
+
+[Project]
+root = default
+folder = yellow
+file = faded
+title = purple
+chapter = #ed14f5
+scene = red
+note = green
+active = red
+inactive = faded
+disabled = faded:L135
+
+[Palette]
+window = base
+windowtext = default
+base = base
+alternatebase = #d8dce0
+text = default
+tooltipbase = red:L175
+tooltiptext = default
+button = base
+buttontext = default
+brighttext = base
+highlight = red
+highlightedtext = base
+link = red
+linkvisited = red
+accent = red
+
+[GUI]
+helptext = default:L250
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #f3f3f8
+link = blue
+headertext = yellow
+headertag = yellow:128
+emphasis = orange
+dialog = red
+altdialog = #a37e03
+note = green
+hidden = faded
+shortcode = cyan
+keyword = #ed14f5
+tag = purple
+value = blue
+optional = faded
+spellcheckline = red
+errorline = cyan
+replacetag = #ed14f5
+modifier = cyan
+texthighlight = red:72
diff --git a/novelwriter/assets/themes/warpgate.conf b/novelwriter/assets/themes/warpgate.conf
new file mode 100644
index 00000000..1398732f
--- /dev/null
+++ b/novelwriter/assets/themes/warpgate.conf
@@ -0,0 +1,74 @@
+[Main]
+name = Warpgate
+mode = dark
+author = Myian
+credit = PlanetSide 2
+
+[Base]
+base = #06232a
+default = #f9fcfd
+faded = #729faa
+red = #fb564a
+orange = #f67e0a
+yellow = #fbcd42
+green = #5dff6a
+cyan = #2ee7f8
+blue = #74a0f6
+purple = #c07efc
+
+[Project]
+root = default
+folder = yellow
+file = default
+title = red
+chapter = purple
+scene = blue
+note = green
+active = green
+inactive = red
+disabled = faded
+
+[Palette]
+window = base:D150
+windowtext = default
+base = base
+alternatebase = #1a5463
+text = default
+tooltipbase = #c2ffaa
+tooltiptext = base
+button = #00252e
+buttontext = default
+brighttext = base
+highlight = #58d662
+highlightedtext = base
+link = green
+linkvisited = green
+accent = green
+
+[GUI]
+helptext = faded
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = base:L150
+link = green
+headertext = green
+headertag = green:128
+emphasis = #fff6aa
+dialog = cyan
+altdialog = #11bdc6
+note = green
+hidden = faded
+shortcode = orange
+keyword = orange
+tag = yellow
+value = yellow
+optional = faded
+spellcheckline = #ff4444
+errorline = green
+replacetag = green
+modifier = orange
+texthighlight = #1a5463
diff --git a/novelwriter/assets/themes/waterlily_dark.conf b/novelwriter/assets/themes/waterlily_dark.conf
new file mode 100644
index 00000000..92395b9c
--- /dev/null
+++ b/novelwriter/assets/themes/waterlily_dark.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Waterlily Dark
+mode = dark
+author = Myian
+
+[Base]
+base = #1e2b33
+default = #d7dbdd
+faded = #869aa5
+red = #ff7474
+orange = #fca469
+yellow = #f7eaa4
+green = #9ae37f
+cyan = #8ef3d1
+blue = #71caeb
+purple = #c89dfd
+
+[Project]
+root = default
+folder = blue
+file = purple
+title = green
+chapter = yellow
+scene = #ff9fbd
+note = faded
+active = #ff9fbd
+inactive = faded
+disabled = #374a5c
+
+[Palette]
+window = #151f25
+windowtext = default
+base = base
+alternatebase = #3b515f
+text = default
+tooltipbase = yellow
+tooltiptext = base
+button = base
+buttontext = default
+brighttext = base
+highlight = #87bcc0
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = #ff9fbd
+
+[GUI]
+helptext = faded
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = default
+line = #283842
+link = blue
+headertext = default
+headertag = faded
+emphasis = green
+dialog = #ff9fbd
+altdialog = orange
+note = blue
+hidden = faded
+shortcode = purple
+keyword = purple
+tag = yellow
+value = cyan
+optional = faded
+spellcheckline = #ff5d5d
+errorline = #4ae698
+replacetag = blue
+modifier = purple
+texthighlight = blue:64
diff --git a/novelwriter/assets/themes/waterlily_light.conf b/novelwriter/assets/themes/waterlily_light.conf
new file mode 100644
index 00000000..960baa67
--- /dev/null
+++ b/novelwriter/assets/themes/waterlily_light.conf
@@ -0,0 +1,73 @@
+[Main]
+name = Waterlily Light
+mode = light
+author = Myian
+
+[Base]
+base = #f5faf7
+default = #435c4e
+faded = #91ac96
+red = #d36868
+orange = #e08b63
+yellow = #cab330
+green = #62a729
+cyan = #31b6a4
+blue = #589dca
+purple = #bd7fe7
+
+[Project]
+root = #526b5d
+folder = blue
+file = purple
+title = green
+chapter = yellow
+scene = #e6859a
+note = faded
+active = #da7b8f
+inactive = faded
+disabled = #b6c9ba
+
+[Palette]
+window = #e2eee7
+windowtext = default
+base = base
+alternatebase = #c7e0d1
+text = default
+tooltipbase = #f3ebbb
+tooltiptext = default
+button = base
+buttontext = default
+brighttext = base
+highlight = #6bb3b3
+highlightedtext = base
+link = blue
+linkvisited = blue
+accent = #e6859a
+
+[GUI]
+helptext = default:192
+fadedtext = faded
+errortext = red
+
+[Syntax]
+background = base
+text = #54615a
+line = #e9f3ed
+link = #0b9caf
+headertext = #526b5d
+headertag = #526b5d80
+emphasis = #1e920e
+dialog = #d15574
+altdialog = #d86f3e
+note = #0b9caf
+hidden = #89a189
+shortcode = #0b9caf
+keyword = #9b5cc5
+tag = #bd911b
+value = #04a770
+optional = #89a189
+spellcheckline = #ff3e3e
+errorline = #04a770
+replacetag = #0b9caf
+modifier = #9b5cc5
+texthighlight = cyan:64
diff --git a/novelwriter/common.py b/novelwriter/common.py
index 2ad42935..8fd9386a 100644
--- a/novelwriter/common.py
+++ b/novelwriter/common.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import json
@@ -31,6 +31,7 @@ import xml.etree.ElementTree as ET
from configparser import ConfigParser
from datetime import datetime
+from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal, TypeGuard, TypeVar
from urllib.parse import urljoin
@@ -134,7 +135,7 @@ def checkPath(value: Any, default: Path) -> Path:
def isHandle(value: Any) -> TypeGuard[str]:
"""Check if a string is a valid novelWriter handle.
- Note: This is case sensitive. Must be lower case!
+ Note: This is case sensitive. Must be lower case.
"""
if not isinstance(value, str):
return False
@@ -218,7 +219,7 @@ def firstFloat(*args: Any) -> float:
##
def formatInt(value: int) -> str:
- """Formats an integer with k, M, G etc."""
+ """Format an integer with k, M, G etc."""
if not isinstance(value, int):
return "ERR"
@@ -444,6 +445,7 @@ def fontMatcher(font: QFont) -> QFont:
default Qt font matching algorithm doesn't handle well changing
application fonts at runtime.
"""
+ font.setStyleName(None) # Make sure no font style name is set from config, see #2502
info = QFontInfo(font)
if (famRequest := font.family()) != (famActual := info.family()):
logger.warning("Font mismatch: Requested '%s', but got '%s'", famRequest, famActual)
@@ -462,21 +464,21 @@ def fontMatcher(font: QFont) -> QFont:
def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable:
- """A replacement for Python lambdas that works for Qt slots."""
+ """A replacement for Python lambdas that works for Qt slots.""" # noqa: D401
def wrapper(*a_: Any) -> None:
func(*args, **kwargs)
return wrapper
def qtAddAction(parent: QWidget, label: str) -> QAction:
- """Helper to add action to widget and always return the action."""
+ """Helper to add action to widget and always return the action.""" # noqa: D401
action = QAction(label, parent)
parent.addAction(action)
return action
def qtAddMenu(parent: QMenuBar | QMenu, label: str) -> QMenu:
- """Helper to add menu to menu and always return the menu."""
+ """Helper to add menu to menu and always return the menu.""" # noqa: D401
menu = QMenu(label, parent)
parent.addMenu(menu)
return menu
@@ -485,7 +487,6 @@ def qtAddMenu(parent: QMenuBar | QMenu, label: str) -> QMenu:
def encodeMimeHandles(mimeData: QMimeData, handles: list[str]) -> None:
"""Encode handles into a mime data object."""
mimeData.setData(nwConst.MIME_HANDLE, b"|".join(h.encode() for h in handles))
- return
def decodeMimeHandles(mimeData: QMimeData) -> list[str]:
@@ -493,6 +494,21 @@ def decodeMimeHandles(mimeData: QMimeData) -> list[str]:
return mimeData.data(nwConst.MIME_HANDLE).data().decode().split("|")
+def utf16CharMap(text: str) -> list[int]:
+ """Compute mapping from Python string index to QString index.
+ Python strings are always one character per position in either
+ ASCII, UCS-2 or UCS-4. QStrings are in UTF-16, so wide characters
+ use 2 indices, and thus create an offset.
+ """
+ utf16Map = list(range(0, len(text) + 1))
+ offset = 0
+ for i, c in enumerate(text, 1):
+ if ord(c) > 0xffff:
+ offset += 1
+ utf16Map[i] = i + offset
+ return utf16Map
+
+
##
# Encoder Functions
##
@@ -519,7 +535,7 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
elif first in ("{", "["):
n += 1
indent = "\n"+" "*n
- if n > nmax and nmax > 0:
+ if n > nmax > 0:
buffer.append(chunk)
else:
buffer.append(chunk[0] + indent + chunk[1:])
@@ -527,13 +543,13 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
elif first in ("}", "]"):
n -= 1
indent = "\n"+" "*n
- if n >= nmax and nmax > 0:
+ if n >= nmax > 0:
buffer.append(chunk)
else:
buffer.append(indent + chunk)
elif first == ",":
- if n > nmax and nmax > 0:
+ if n > nmax > 0:
buffer.append(chunk)
else:
buffer.append(chunk[0] + indent + chunk[1:].lstrip())
@@ -551,7 +567,7 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
def xmlIndent(xml: ET.Element | ET.ElementTree) -> None:
"""A modified version of the XML indent function in the standard
library. It behaves more closely to how the one from lxml does.
- """
+ """ # noqa: D401
tree = xml.getroot() if isinstance(xml, ET.ElementTree) else xml
if not isinstance(tree, ET.Element):
return
@@ -581,8 +597,6 @@ def xmlIndent(xml: ET.Element | ET.ElementTree) -> None:
if last is not None:
last.tail = indentations[level]
- return
-
if len(tree):
indentChildren(tree, 0)
tree.tail = "\n"
@@ -597,7 +611,7 @@ def xmlElement(
attrib: dict | None = None,
tail: str | None = None,
) -> ET.Element:
- """A custom implementation of Element with more arguments."""
+ """A custom implementation of Element with more arguments.""" # noqa: D401
xSub = ET.Element(tag, attrib=attrib or {})
if text is not None:
if isinstance(text, bool):
@@ -617,7 +631,7 @@ def xmlSubElem(
attrib: dict | None = None,
tail: str | None = None,
) -> ET.Element:
- """A custom implementation of SubElement with more arguments."""
+ """A custom implementation of SubElement with more arguments.""" # noqa: D401
xSub = ET.SubElement(parent, tag, attrib=attrib or {})
if text is not None:
if isinstance(text, bool):
@@ -648,6 +662,7 @@ def readTextFile(path: str | Path) -> str:
def makeFileNameSafe(text: str) -> str:
"""Return a filename-safe string.
+
See: https://unicode.org/reports/tr15/#Norm_Forms
"""
text = unicodedata.normalize("NFKC", text).strip()
@@ -676,8 +691,11 @@ def openExternalPath(path: Path) -> bool:
# Classes
##
+_T_Enum = TypeVar("_T_Enum", bound=Enum)
+
+
class NWConfigParser(ConfigParser):
- """Common: Adapted Config Parser
+ """Common: Adapted Config Parser.
This is a subclass of the standard config parser that adds type safe
helper functions, and support for lists. It also turns off
@@ -686,7 +704,6 @@ class NWConfigParser(ConfigParser):
def __init__(self) -> None:
super().__init__(interpolation=None)
- return
def rdStr(self, section: str, option: str, default: str) -> str:
"""Read string value."""
@@ -737,3 +754,10 @@ class NWConfigParser(ConfigParser):
for i in range(min(len(data), len(result))):
result[i] = checkInt(data[i].strip(), result[i])
return result
+
+ def rdEnum(self, section: str, option: str, default: _T_Enum) -> _T_Enum:
+ """Read enum value."""
+ if self.has_option(section, option):
+ data = self.get(section, option, fallback="")
+ return type(default).__members__.get(data.upper(), default)
+ return default
diff --git a/novelwriter/config.py b/novelwriter/config.py
index 0c6d43bb..6ce9d434 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -22,7 +22,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import json
@@ -45,6 +45,7 @@ from novelwriter.common import (
formatTimeStamp, processDialogSymbols, simplified
)
from novelwriter.constants import nwFiles, nwQuotes, nwUnicode
+from novelwriter.enum import nwTheme
from novelwriter.error import formatException, logException
if TYPE_CHECKING:
@@ -55,13 +56,20 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
-DEF_GUI = "default"
-DEF_SYNTAX = "default_light"
+DEF_GUI_DARK = "default_dark"
+DEF_GUI_LIGHT = "default_light"
DEF_ICONS = "material_rounded_normal"
DEF_TREECOL = "theme"
class Config:
+ """User Config.
+
+ The main user config. The state of the config is stored in the
+ novelwriter.conf file between sessions. Most of the settings can be
+ modified by the user in the Preferences dialog, but some just record
+ various states of the GUI.
+ """
__slots__ = (
"_appPath", "_appRoot", "_backPath", "_backupPath", "_confPath", "_dLocale", "_dShortDate",
@@ -69,22 +77,23 @@ class Config:
"_manuals", "_nwLangPath", "_qLocale", "_qtLangPath", "_qtTrans", "_recentPaths",
"_recentProjects", "_splash", "allowOpenDial", "altDialogClose", "altDialogOpen",
"appHandle", "appName", "askBeforeBackup", "askBeforeExit", "autoSaveDoc", "autoSaveProj",
- "autoScroll", "autoScrollPos", "autoSelect", "backupOnClose", "cursorWidth", "dialogLine",
- "dialogStyle", "doJustify", "doReplace", "doReplaceDQuote", "doReplaceDash",
+ "autoScroll", "autoScrollPos", "autoSelect", "backupOnClose", "cursorWidth", "darkTheme",
+ "dialogLine", "dialogStyle", "doJustify", "doReplace", "doReplaceDQuote", "doReplaceDash",
"doReplaceDots", "doReplaceSQuote", "emphLabels", "fmtApostrophe", "fmtDQuoteClose",
"fmtDQuoteOpen", "fmtPadAfter", "fmtPadBefore", "fmtPadThin", "fmtSQuoteClose",
- "fmtSQuoteOpen", "focusWidth", "guiFont", "guiLocale", "guiSyntax", "guiTheme",
- "hasEnchant", "hideFocusFooter", "hideHScroll", "hideVScroll", "highlightEmph", "hostName",
- "iconColDocs", "iconColTree", "iconTheme", "incNotesWCount", "isDebug", "kernelVer",
- "lastNotes", "mainPanePos", "mainWinSize", "memInfo", "narratorBreak", "narratorDialog",
- "nativeFont", "osDarwin", "osLinux", "osType", "osUnknown", "osWindows", "outlinePanePos",
- "prefsWinSize", "scrollPastEnd", "searchCase", "searchLoop", "searchMatchCap",
- "searchNextFile", "searchProjCase", "searchProjRegEx", "searchProjWord", "searchRegEx",
- "searchWord", "showEditToolBar", "showFullPath", "showLineEndings", "showMultiSpaces",
- "showSessionTime", "showTabsNSpaces", "showViewerPanel", "spellLanguage", "stopWhenIdle",
- "tabWidth", "textFont", "textMargin", "textWidth", "useCharCount", "userIdleTime",
- "verPyQtString", "verPyQtValue", "verPyString", "verQtString", "verQtValue",
- "viewComments", "viewPanePos", "viewSynopsis", "welcomeWinSize",
+ "fmtSQuoteOpen", "focusWidth", "guiFont", "guiLocale", "hasEnchant", "hideFocusFooter",
+ "hideHScroll", "hideVScroll", "highlightEmph", "hostName", "iconColDocs", "iconColTree",
+ "iconTheme", "incNotesWCount", "isDebug", "kernelVer", "lastNotes", "lightTheme",
+ "lineHighlight", "mainPanePos", "mainWinSize", "memInfo", "narratorBreak",
+ "narratorDialog", "nativeFont", "osDarwin", "osLinux", "osType", "osUnknown", "osWindows",
+ "outlinePanePos", "prefsWinSize", "scrollPastEnd", "searchCase", "searchLoop",
+ "searchMatchCap", "searchNextFile", "searchProjCase", "searchProjRegEx", "searchProjWord",
+ "searchRegEx", "searchWord", "showEditToolBar", "showFullPath", "showLineEndings",
+ "showMultiSpaces", "showSessionTime", "showTabsNSpaces", "showViewerPanel",
+ "spellLanguage", "stopWhenIdle", "tabWidth", "textFont", "textMargin", "textWidth",
+ "themeMode", "useCharCount", "userIdleTime", "verPyQtString", "verPyQtValue",
+ "verPyString", "verQtString", "verQtValue", "viewComments", "viewNotes", "viewPanePos",
+ "viewSynopsis", "welcomeWinSize",
)
LANG_NW = 1
@@ -153,14 +162,15 @@ class Config:
# General GUI Settings
self.guiLocale = self._qLocale.name()
- self.guiTheme = DEF_GUI # GUI theme
- self.guiSyntax = DEF_SYNTAX # Syntax theme
- self.guiFont = QFont() # Main GUI font
- self.hideVScroll = False # Hide vertical scroll bars on main widgets
- self.hideHScroll = False # Hide horizontal scroll bars on main widgets
- self.lastNotes = "0x0" # The latest release notes that have been shown
- self.nativeFont = True # Use native font dialog
- self.useCharCount = False # Use character count as primary count
+ self.lightTheme = DEF_GUI_LIGHT # Light GUI theme
+ self.darkTheme = DEF_GUI_DARK # Dark GUI theme
+ self.themeMode = nwTheme.AUTO # Colour theme mode
+ self.guiFont = QFont() # Main GUI font
+ self.hideVScroll = False # Hide vertical scroll bars on main widgets
+ self.hideHScroll = False # Hide horizontal scroll bars on main widgets
+ self.lastNotes = "0x0" # The latest release notes that have been shown
+ self.nativeFont = True # Use native font dialog
+ self.useCharCount = False # Use character count as primary count
# Icons
self.iconTheme = DEF_ICONS # Icons theme
@@ -178,8 +188,8 @@ class Config:
# Project Settings
self.autoSaveProj = 60 # Interval for auto-saving project, in seconds
self.autoSaveDoc = 30 # Interval for auto-saving document, in seconds
- self.emphLabels = True # Add emphasis to H1 and H2 item labels
- self.backupOnClose = False # Flag for running automatic backups
+ self.emphLabels = False # Add emphasis to H1 and H2 item labels
+ self.backupOnClose = True # Flag for running automatic backups
self.askBeforeBackup = True # Flag for asking before running automatic backup
self.askBeforeExit = True # Flag for asking before exiting the app
@@ -189,6 +199,7 @@ class Config:
self.textMargin = 40 # Editor/viewer text margin
self.tabWidth = 40 # Editor tabulator width
self.cursorWidth = 1 # Editor cursor width
+ self.lineHighlight = False # Highlight current line in editor
self.focusWidth = 800 # Focus Mode text width
self.hideFocusFooter = False # Hide document footer in Focus Mode
@@ -198,7 +209,7 @@ class Config:
self.doJustify = False # Justify text
self.showTabsNSpaces = False # Show tabs and spaces in editor
self.showLineEndings = False # Show line endings in editor
- self.showMultiSpaces = True # Highlight multiple spaces in the text
+ self.showMultiSpaces = False # Highlight multiple spaces in the text
self.doReplace = True # Enable auto-replace as you type
self.doReplaceSQuote = True # Smart single quotes
@@ -206,7 +217,7 @@ class Config:
self.doReplaceDash = True # Replace multiple hyphens with dashes
self.doReplaceDots = True # Replace three dots with ellipsis
- self.autoScroll = False # Typewriter-like scrolling
+ self.autoScroll = True # Typewriter-like scrolling
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
self.scrollPastEnd = True # Scroll past end of document, and centre cursor
@@ -245,6 +256,7 @@ class Config:
self.showSessionTime = True # Show the session time in the status bar
self.viewComments = True # Comments are shown in the viewer
self.viewSynopsis = True # Synopsis is shown in the viewer
+ self.viewNotes = True # Notes are shown in the viewer
# Search Box States
self.searchCase = False
@@ -295,8 +307,6 @@ class Config:
# Packages
self.hasEnchant = False # The pyenchant package
- return
-
##
# Properties
##
@@ -345,7 +355,6 @@ class Config:
def setLastAuthor(self, value: str) -> None:
"""Set tle last used author name."""
self._lastAuthor = simplified(value)
- return
def setMainWinSize(self, width: int, height: int) -> None:
"""Set the size of the main window, but only if the change is
@@ -357,17 +366,14 @@ class Config:
self.mainWinSize[0] = width
if abs(self.mainWinSize[1] - height) > 5:
self.mainWinSize[1] = height
- return
def setWelcomeWinSize(self, width: int, height: int) -> None:
"""Set the size of the Preferences dialog window."""
self.welcomeWinSize = [width, height]
- return
def setPreferencesWinSize(self, width: int, height: int) -> None:
"""Set the size of the Preferences dialog window."""
self.prefsWinSize = [width, height]
- return
def setLastPath(self, key: str, path: str | Path) -> None:
"""Set the last used path. Only the folder is saved, so if the
@@ -379,12 +385,10 @@ class Config:
path = path.parent
if path.is_dir():
self._recentPaths.setPath(key, path)
- return
def setBackupPath(self, path: Path | str) -> None:
"""Set the current backup path."""
self._backupPath = checkPath(path, self._backPath)
- return
def setGuiFont(self, value: QFont | str | None) -> None:
"""Update the GUI's font style from settings."""
@@ -403,9 +407,8 @@ class Config:
else:
font = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.guiFont = fontMatcher(font)
- logger.debug("GUI font set to: %s", describeFont(font))
+ logger.debug("Main font set to: %s", describeFont(font))
QApplication.setFont(self.guiFont)
- return
def setTextFont(self, value: QFont | str | None) -> None:
"""Set the text font if it exists. If it doesn't, or is None,
@@ -431,14 +434,13 @@ class Config:
font = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.textFont = fontMatcher(font)
logger.debug("Text font set to: %s", describeFont(self.textFont))
- return
##
# Methods
##
def homePath(self) -> Path:
- """The user's home folder."""
+ """Return the user's home folder."""
return self._homePath
def dataPath(self, target: str | None = None) -> Path:
@@ -520,7 +522,6 @@ class Config:
"""Send a message to the splash screen."""
if self._splash:
self._splash.showStatus(message)
- return
##
# Config Actions
@@ -551,11 +552,10 @@ class Config:
self._confPath.mkdir(exist_ok=True)
self._dataPath.mkdir(exist_ok=True)
- # Also create the syntax, themes and icons folders if possible
+ # Also create the themes and icons folders if possible
if self._dataPath.is_dir():
(self._dataPath / "cache").mkdir(exist_ok=True)
(self._dataPath / "icons").mkdir(exist_ok=True)
- (self._dataPath / "syntax").mkdir(exist_ok=True)
(self._dataPath / "themes").mkdir(exist_ok=True)
self._recentPaths.loadCache()
@@ -564,8 +564,6 @@ class Config:
logger.debug("Config instance initialised")
- return
-
def initLocalisation(self, nwApp: QApplication) -> None:
"""Initialise the localisation of the GUI."""
self.splashMessage("Loading localisation ...")
@@ -593,8 +591,6 @@ class Config:
nwApp.installTranslator(qTrans)
self._qtTrans[lngFile] = qTrans
- return
-
def loadConfig(self, splash: NSplashScreen | None = None) -> bool:
"""Load preferences from file and replace default settings."""
self._splash = splash
@@ -626,8 +622,9 @@ class Config:
# Main
sec = "Main"
self.setGuiFont(conf.rdStr(sec, "font", ""))
- self.guiTheme = conf.rdStr(sec, "theme", self.guiTheme)
- self.guiSyntax = conf.rdStr(sec, "syntax", self.guiSyntax)
+ self.lightTheme = conf.rdStr(sec, "lighttheme", self.lightTheme)
+ self.darkTheme = conf.rdStr(sec, "darktheme", self.darkTheme)
+ self.themeMode = conf.rdEnum(sec, "thememode", self.themeMode)
self.iconTheme = conf.rdStr(sec, "icons", self.iconTheme)
self.iconColTree = conf.rdStr(sec, "iconcoltree", self.iconColTree)
self.iconColDocs = conf.rdBool(sec, "iconcoldocs", self.iconColDocs)
@@ -665,6 +662,7 @@ class Config:
self.textMargin = conf.rdInt(sec, "margin", self.textMargin)
self.tabWidth = conf.rdInt(sec, "tabwidth", self.tabWidth)
self.cursorWidth = conf.rdInt(sec, "cursorwidth", self.cursorWidth)
+ self.lineHighlight = conf.rdBool(sec, "linehighlight", self.lineHighlight)
self.focusWidth = conf.rdInt(sec, "focuswidth", self.focusWidth)
self.hideFocusFooter = conf.rdBool(sec, "hidefocusfooter", self.hideFocusFooter)
self.doJustify = conf.rdBool(sec, "justify", self.doJustify)
@@ -708,6 +706,7 @@ class Config:
self.showSessionTime = conf.rdBool(sec, "showsessiontime", self.showSessionTime)
self.viewComments = conf.rdBool(sec, "viewcomments", self.viewComments)
self.viewSynopsis = conf.rdBool(sec, "viewsynopsis", self.viewSynopsis)
+ self.viewNotes = conf.rdBool(sec, "viewnotes", self.viewNotes)
self.searchCase = conf.rdBool(sec, "searchcase", self.searchCase)
self.searchWord = conf.rdBool(sec, "searchword", self.searchWord)
self.searchRegEx = conf.rdBool(sec, "searchregex", self.searchRegEx)
@@ -721,7 +720,7 @@ class Config:
# Check Values
# ============
- self._prepareFont(self.guiFont, "GUI")
+ self._prepareFont(self.guiFont, "main")
self._prepareFont(self.textFont, "document")
# If we're using straight quotes, disable auto-replace
@@ -751,8 +750,9 @@ class Config:
conf["Main"] = {
"font": self.guiFont.toString(),
- "theme": str(self.guiTheme),
- "syntax": str(self.guiSyntax),
+ "lighttheme": str(self.lightTheme),
+ "darktheme": str(self.darkTheme),
+ "thememode": self.themeMode.name,
"icons": str(self.iconTheme),
"iconcoltree": str(self.iconColTree),
"iconcoldocs": str(self.iconColDocs),
@@ -790,6 +790,7 @@ class Config:
"margin": str(self.textMargin),
"tabwidth": str(self.tabWidth),
"cursorwidth": str(self.cursorWidth),
+ "lineHighlight": str(self.lineHighlight),
"focuswidth": str(self.focusWidth),
"hidefocusfooter": str(self.hideFocusFooter),
"justify": str(self.doJustify),
@@ -833,6 +834,7 @@ class Config:
"showsessiontime": str(self.showSessionTime),
"viewcomments": str(self.viewComments),
"viewsynopsis": str(self.viewSynopsis),
+ "viewnotes": str(self.viewNotes),
"searchcase": str(self.searchCase),
"searchword": str(self.searchWord),
"searchregex": str(self.searchRegEx),
@@ -862,7 +864,6 @@ class Config:
def finishStartup(self) -> None:
"""Call after startup is complete."""
self._splash = None
- return
##
# Internal Functions
@@ -884,7 +885,6 @@ class Config:
else:
self.hasEnchant = True
logger.debug("Checking package 'pyenchant': OK")
- return
def _prepareFont(self, font: QFont, kind: str) -> None:
"""Check Unicode availability in font. This also initialises any
@@ -895,16 +895,15 @@ class Config:
for char in nwUnicode.UI_SYMBOLS:
if not metrics.inFont(char): # type: ignore
logger.warning("No glyph U+%04x in font", ord(char)) # pragma: no cover
- return
class RecentProjects:
+ """A record of recently opened projects."""
def __init__(self, config: Config) -> None:
self._conf = config
self._data: dict[str, dict[str, str | int]] = {}
self._map: dict[str, str] = {}
- return
def loadCache(self) -> bool:
"""Load the cache file for recent projects."""
@@ -966,14 +965,12 @@ class RecentProjects:
self.saveCache()
except Exception:
pass
- return
def remove(self, path: str | Path) -> None:
"""Try to remove a path from the recent projects cache."""
if self._data.pop(str(path), None) is not None:
logger.debug("Removed recent: %s", path)
self.saveCache()
- return
def _setEntry(
self, puuid: str, path: str, title: str, words: int, chars: int, saved: int
@@ -988,24 +985,22 @@ class RecentProjects:
}
if puuid:
self._map[puuid] = path
- return
class RecentPaths:
+ """A record of recently used file paths."""
KEYS: Final[list[str]] = ["default", "project", "import", "outline", "stats"]
def __init__(self, config: Config) -> None:
self._conf = config
self._data = {}
- return
def setPath(self, key: str, path: Path | str) -> None:
"""Set a path for a given key, and save the cache."""
if key in self.KEYS:
self._data[key] = str(path)
self.saveCache()
- return
def getPath(self, key: str) -> str | None:
"""Get a path for a given key, or return None."""
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index 191bcc1c..5ea3f8ff 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
from typing import Final
@@ -28,21 +28,23 @@ from typing import Final
from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
from novelwriter.enum import (
- nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape
+ nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape,
+ nwTheme
)
def trConst(text: str) -> str:
- """Wrapper function for locally translating constants."""
+ """Translate a constant."""
return QCoreApplication.translate("Constant", text)
def trStats(text: str) -> str:
- """Wrapper function for locally translating stats constants."""
+ """Translate a stats constants."""
return QCoreApplication.translate("Stats", text)
class nwConst:
+ """Various Constants."""
# Date and Time Formats
FMT_TSTAMP = "%Y-%m-%d %H:%M:%S" # Default format
@@ -69,6 +71,7 @@ class nwConst:
class nwRegEx:
+ """Common RegExes."""
URL = r"https?://(?:www\.|(?!www))[\w/()@:%_\+-.~#?&=]+"
WORDS = r"\b[^\s\-\+\/–—\[\]:]+\b"
@@ -76,11 +79,13 @@ class nwRegEx:
FMT_EI = r"(?.
-"""
+""" # noqa
from __future__ import annotations
import json
@@ -211,7 +211,7 @@ class FilterMode(Enum):
class BuildSettings:
- """Core: Build Settings Class
+ """Core: Build Settings Class.
This class manages the build settings for a Manuscript build job.
The settings can be packed/unpacked to/from a dictionary for JSON.
@@ -229,7 +229,6 @@ class BuildSettings:
self._included = set()
self._settings = {k: v[1] for k, v in SETTINGS_TEMPLATE.items()}
self._changed = False
- return
@classmethod
def fromDict(cls, data: dict) -> BuildSettings:
@@ -315,7 +314,6 @@ class BuildSettings:
def setName(self, name: str) -> None:
"""Set the build setting display name."""
self._name = str(name)
- return
def setBuildID(self, value: str | uuid.UUID) -> None:
"""Set a UUID build ID."""
@@ -324,13 +322,11 @@ class BuildSettings:
self._uuid = str(uuid.uuid4())
elif value != self._uuid:
self._uuid = value
- return
def setOrder(self, value: int) -> None:
"""Set the build order."""
if isinstance(value, int):
self._order = value
- return
def setLastBuildPath(self, path: Path | str | None) -> None:
"""Set the last used build path."""
@@ -341,41 +337,35 @@ class BuildSettings:
else:
self._path = CONFIG.homePath()
self._changed = True
- return
def setLastBuildName(self, name: str) -> None:
"""Set the last used build name."""
self._build = str(name).strip()
self._changed = True
- return
def setLastFormat(self, value: nwBuildFmt) -> None:
"""Set the last used build format."""
if isinstance(value, nwBuildFmt):
self._format = value
self._changed = True
- return
def setFiltered(self, tHandle: str) -> None:
"""Set an item as filtered."""
self._excluded.discard(tHandle)
self._included.discard(tHandle)
self._changed = True
- return
def setIncluded(self, tHandle: str) -> None:
"""Set an item as explicitly included."""
self._excluded.discard(tHandle)
self._included.add(tHandle)
self._changed = True
- return
def setExcluded(self, tHandle: str) -> None:
"""Set an item as explicitly excluded."""
self._excluded.add(tHandle)
self._included.discard(tHandle)
self._changed = True
- return
def setAllowRoot(self, tHandle: str, state: bool) -> None:
"""Set a specific root folder as allowed or not."""
@@ -385,14 +375,12 @@ class BuildSettings:
elif state is False:
self._skipRoot.add(tHandle)
self._changed = True
- return
def setValue(self, key: str, value: T_BuildValue) -> None:
"""Set a specific value for a build setting."""
if (d := SETTINGS_TEMPLATE.get(key)) and len(d) == 2 and isinstance(value, d[0]):
self._changed |= (value != self._settings[key])
self._settings[key] = value
- return
##
# Methods
@@ -463,7 +451,6 @@ class BuildSettings:
called when the changes have been safely saved or passed on.
"""
self._changed = False
- return
def pack(self) -> dict:
"""Pack all content into a JSON compatible dictionary."""
@@ -516,8 +503,6 @@ class BuildSettings:
self._changed = False
- return
-
@classmethod
def duplicate(cls, source: BuildSettings) -> BuildSettings:
"""Make a copy of another build."""
@@ -529,7 +514,7 @@ class BuildSettings:
class BuildCollection:
- """Core: Build Collection Class
+ """Core: Build Collection Class.
This object holds all the build setting objects defined by the given
project. The build settings are saved as a single JSON file in the
@@ -542,7 +527,6 @@ class BuildCollection:
self._defaultBuild = ""
self._builds: dict[str, BuildSettings] = {}
self._loadCollection()
- return
def __len__(self) -> int:
"""Return the number of builds."""
@@ -581,21 +565,18 @@ class BuildCollection:
build.setOrder(i)
self._lastBuild = lastBuild
self._saveCollection()
- return
def setDefaultBuild(self, buildID: str) -> None:
"""Set the default build id."""
if buildID != self._defaultBuild:
self._defaultBuild = buildID
self._saveCollection()
- return
def setBuild(self, build: BuildSettings) -> None:
"""Set build settings data in the collection."""
if isinstance(build, BuildSettings):
self._builds[build.buildID] = build
self._saveCollection()
- return
##
# Methods
@@ -605,7 +586,6 @@ class BuildCollection:
"""Remove a build from the collection."""
self._builds.pop(buildID, None)
self._saveCollection()
- return
def builds(self) -> Iterable[tuple[str, str]]:
"""Iterate over all available builds."""
diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py
index a4b2a0b1..5ec81031 100644
--- a/novelwriter/core/coretools.py
+++ b/novelwriter/core/coretools.py
@@ -23,7 +23,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -52,7 +52,9 @@ logger = logging.getLogger(__name__)
class DocMerger:
- """Document tool for merging a set of documents into a single new
+ """Tool: Merge Documents.
+
+ Document tool for merging a set of documents into a single new
document. The parameters are defined by the user using the
GuiDocMerge dialog.
"""
@@ -62,7 +64,6 @@ class DocMerger:
self._error = ""
self._target = None
self._text = []
- return
@property
def targetHandle(self) -> str | None:
@@ -85,7 +86,6 @@ class DocMerger:
"""
self._target = self._project.tree[tHandle]
self._text = []
- return
def newTargetDoc(self, sHandle: str, label: str) -> None:
"""Create a brand new target document based on a source handle
@@ -101,7 +101,6 @@ class DocMerger:
nwItem.notifyToRefresh()
self._target = nwItem
self._text = []
- return
def appendText(self, sHandle: str, addComment: bool, cmtPrefix: str) -> None:
"""Append text from an existing document to the text buffer."""
@@ -112,7 +111,6 @@ class DocMerger:
status, _ = item.getImportStatus()
text = f"% {cmtPrefix} {info}: {item.itemName} [{status}]\n\n{text}"
self._text.append(text)
- return
def writeTargetDoc(self) -> bool:
"""Write the accumulated text into the designated target
@@ -158,7 +156,8 @@ class DocSplitter:
self._srcHandle = sHandle
self._srcItem = srcItem
- return
+ def __len__(self) -> int:
+ return len(self._rawData)
##
# Methods
@@ -174,7 +173,6 @@ class DocSplitter:
"""
self._parHandle = pHandle
self._inFolder = False
- return
def newParentFolder(self, pHandle: str, folderLabel: str) -> None:
"""Create a new folder that will be the top level parent item
@@ -188,7 +186,6 @@ class DocSplitter:
nwItem.notifyToRefresh()
self._parHandle = nHandle
self._inFolder = True
- return
def splitDocument(self, splitData: list, splitText: list[str]) -> None:
"""Loop through the split data record and perform the split job
@@ -200,12 +197,9 @@ class DocSplitter:
chunk = buffer[lineNo:]
buffer = buffer[:lineNo]
self._rawData.insert(0, (chunk, hLevel, hLabel))
- return
def writeDocuments(self, docHierarchy: bool) -> Iterable[bool]:
- """An iterator that will write each document in the buffer, and
- return its new handle, parent handle, and sibling handle.
- """
+ """Write each document in the buffer and yield if successful."""
if self._srcHandle and self._srcItem and self._parHandle:
pHandle = self._parHandle
hHandle = [self._parHandle, None, None, None, None]
@@ -256,7 +250,6 @@ class DocDuplicator:
def __init__(self, project: NWProject) -> None:
self._project = project
- return
##
# Methods
@@ -270,7 +263,9 @@ class DocDuplicator:
after = True
if items:
hMap: dict[str, str | None] = {t: None for t in items}
+ SHARED.initMainProgress(len(items))
for tHandle in items:
+ SHARED.incMainProgress()
if oldItem := self._project.tree[tHandle]:
pHandle = hMap.get(oldItem.itemParent or "") or oldItem.itemParent
if newItem := self._project.tree.duplicate(tHandle, pHandle, after):
@@ -282,17 +277,21 @@ class DocDuplicator:
after = False
else:
break
+ SHARED.clearMainProgress()
return result
class DocSearch:
+ """Tool: Search Documents.
+
+ A global document search class.
+ """
def __init__(self) -> None:
self._regEx = re.compile(r"")
- self._opts = re.UNICODE | re.IGNORECASE
+ self._opts = re.IGNORECASE
self._words = False
self._escape = True
- return
##
# Methods
@@ -300,32 +299,30 @@ class DocSearch:
def setCaseSensitive(self, state: bool) -> None:
"""Set the case sensitive search flag."""
- self._opts = re.UNICODE
- if not state:
- self._opts |= re.IGNORECASE
- return
+ self._opts = 0 if state else re.IGNORECASE
def setWholeWords(self, state: bool) -> None:
"""Set the whole words search flag."""
self._words = state
- return
def setUserRegEx(self, state: bool) -> None:
"""Set the escape flag to the opposite state."""
self._escape = not state
- return
def iterSearch(
self, project: NWProject, search: str
) -> Iterable[tuple[NWItem, list[tuple[int, int, str]], bool]]:
- """Iteratively search through documents in a project."""
+ """Iterate through documents in a project and apply search."""
self._regEx = re.compile(self._buildPattern(search), self._opts)
logger.debug("Searching with pattern '%s'", self._regEx.pattern)
storage = project.storage
+ SHARED.initMainProgress(len(project.tree))
for item in project.tree:
+ SHARED.incMainProgress()
if item.isFileType():
results, capped = self.searchText(storage.getDocumentText(item.itemHandle))
yield item, results, capped
+ SHARED.clearMainProgress()
return
def searchText(self, text: str) -> tuple[list[tuple[int, int, str]], bool]:
@@ -368,7 +365,6 @@ class ProjectBuilder:
def __init__(self) -> None:
self._path = None
self.tr = partial(QCoreApplication.translate, "ProjectBuilder")
- return
@property
def projPath(self) -> Path | None:
@@ -609,6 +605,6 @@ class ProjectBuilder:
project.data.setSaveCount(0)
project.data.setAutoCount(0)
project.data.setEditTime(0)
+ project.index.rebuild()
project.saveProject()
project.closeProject()
- return
diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py
index 8e89095d..32bccb1d 100644
--- a/novelwriter/core/docbuild.py
+++ b/novelwriter/core/docbuild.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -53,7 +53,7 @@ logger = logging.getLogger(__name__)
class NWBuildDocument:
- """Core: Manuscript Document Build Class
+ """Core: Manuscript Document Build Class.
This is the core tool that assembles a project and outputs a
manuscript, based on a build definition object (BuildSettings).
@@ -72,7 +72,6 @@ class NWBuildDocument:
self._cache = None
self._count = False
self._outline = False
- return
##
# Properties
@@ -106,7 +105,6 @@ class NWBuildDocument:
def addDocument(self, tHandle: str) -> None:
"""Add a document to the build queue manually."""
self._queue.append(tHandle)
- return
def queueAll(self) -> None:
"""Queue all document as defined by the build settings."""
@@ -115,7 +113,6 @@ class NWBuildDocument:
for item in self._project.tree:
if filtered.get(item.itemHandle, False):
self._queue.append(item.itemHandle)
- return
def iterBuildPreview(self, newPage: bool) -> Iterable[tuple[int, bool]]:
"""Build a preview QTextDocument."""
@@ -131,7 +128,7 @@ class NWBuildDocument:
return
def iterBuildDocument(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
- """Wrapper for builders based on format."""
+ """Select a builder based on format."""
self._error = None
self._cache = None
@@ -341,12 +338,10 @@ class NWBuildDocument:
scale*self._build.getFloat("format.rightMargin"),
)
- filtered = self._build.buildItemFilter(
+ return self._build.buildItemFilter(
self._project, withRoots=self._build.getBool("text.addNoteHeadings")
)
- return filtered
-
def _doBuild(self, bldObj: Tokenizer, tHandle: str, convert: bool = True) -> bool:
"""Build a single document and add it to the build object."""
tItem = self._project.tree[tHandle]
diff --git a/novelwriter/core/document.py b/novelwriter/core/document.py
index e0f469e9..1cb6809c 100644
--- a/novelwriter/core/document.py
+++ b/novelwriter/core/document.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import hashlib
@@ -42,7 +42,7 @@ logger = logging.getLogger(__name__)
class NWDocument:
- """Core: Document Class
+ """Core: Document Class.
A Class wrapping a single novelWriter document file. It represents
a project item of nwItemType FILE. The file is not guaranteed to
@@ -68,8 +68,6 @@ class NWDocument:
if self._handle is not None:
self._item = self._project.tree[tHandle]
- return
-
def __repr__(self) -> str:
return f""
@@ -357,5 +355,3 @@ class NWDocument:
else:
logger.debug("Unknown meta data: '%s'", metaLine.strip())
-
- return
diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py
index 07933690..2a9aba3a 100644
--- a/novelwriter/core/index.py
+++ b/novelwriter/core/index.py
@@ -22,7 +22,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import json
@@ -56,7 +56,7 @@ KEY_SOURCE = "0123456789bcdfghjklmnpqrstvwxz"
class Index:
- """Core: Project Index
+ """Core: Project Index.
This class holds the entire index for a given project. The index
contains the data that isn't stored in the project items themselves.
@@ -99,8 +99,6 @@ class Index:
self._indexChange = 0.0
self._rootChange = {}
- return
-
def __repr__(self) -> str:
return f""
@@ -129,7 +127,6 @@ class Index:
def setNovelModelExtraColumn(self, extra: nwNovelExtra) -> None:
"""Set the data content type of the novel model extra column."""
self._novelExtra = extra
- return
##
# Public Methods
@@ -142,20 +139,21 @@ class Index:
self._indexChange = 0.0
self._rootChange = {}
SHARED.emitIndexCleared(self._project)
- return
def rebuild(self) -> None:
"""Rebuild the entire index from scratch."""
self.clear()
+ SHARED.initMainProgress(len(self._project.tree))
for nwItem in self._project.tree:
if nwItem.isFileType():
text = self._project.storage.getDocumentText(nwItem.itemHandle)
self.scanText(nwItem.itemHandle, text, blockSignal=True)
+ SHARED.incMainProgress()
self._indexBroken = False
SHARED.emitIndexAvailable(self._project)
for tHandle in self._novelModels:
self.refreshNovelModel(tHandle)
- return
+ SHARED.clearMainProgress()
def deleteHandle(self, tHandle: str) -> None:
"""Delete all entries of a given document handle."""
@@ -165,7 +163,6 @@ class Index:
del self._tagsIndex[tTag]
del self._itemIndex[tHandle]
SHARED.emitIndexChangedTags(self._project, [], delTags)
- return
def reIndexHandle(self, tHandle: str | None) -> None:
"""Put a file back into the index. This is used when files are
@@ -175,7 +172,6 @@ class Index:
if tHandle and self._project.tree.checkType(tHandle, nwItemType.FILE):
logger.debug("Re-indexing item '%s'", tHandle)
self.scanText(tHandle, self._project.storage.getDocumentText(tHandle))
- return
def refreshHandle(self, tHandle: str) -> None:
"""Update the class for all tags of a handle."""
@@ -185,7 +181,6 @@ class Index:
self.deleteHandle(tHandle)
else:
self._tagsIndex.updateClass(tHandle, item.itemClass.name)
- return
def indexChangedSince(self, checkTime: int | float) -> bool:
"""Check if the index has changed since a given time."""
@@ -208,7 +203,6 @@ class Index:
model.setExtraColumn(self._novelExtra)
self._appendSubTreeToModel(tHandle, model)
model.endResetModel()
- return
def updateNovelModelData(self, nwItem: NWItem) -> bool:
"""Refresh a novel model."""
@@ -425,8 +419,6 @@ class Index:
if updated or deleted:
SHARED.emitIndexChangedTags(self._project, updated, deleted)
- return
-
def _scanInactive(self, nwItem: NWItem, text: str) -> None:
"""Scan an inactive document for meta data."""
for line in text.splitlines():
@@ -435,7 +427,6 @@ class Index:
if hDepth != "H0":
nwItem.setMainHeading(hDepth)
break
- return
def _splitHeading(self, line: str) -> tuple[str, str]:
"""Split a heading into its heading level and text value."""
@@ -459,7 +450,6 @@ class Index:
"""Count text stats and save the counts to the index."""
cC, wC, pC = standardCounter(text)
self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC)
- return
def _indexKeyword(
self, tHandle: str, line: str, sTitle: str, itemClass: nwItemClass, tags: dict[str, bool]
@@ -495,7 +485,6 @@ class Index:
model.setExtraColumn(self._novelExtra)
self._appendSubTreeToModel(tHandle, model)
self._novelModels[tHandle] = model
- return
def _appendSubTreeToModel(self, tHandle: str, model: NovelModel) -> None:
"""Append all active novel documents to a novel model."""
@@ -506,7 +495,6 @@ class Index:
and node.item.isActive
):
model.append(node)
- return
##
# Check @ Lines
@@ -680,15 +668,13 @@ class Index:
"words": hItem.wordCount,
}
- result = [(
+ return [(
tKey,
tData[tKey]["level"],
tData[tKey]["title"],
tData[tKey]["words"]
) for tKey in tOrder]
- return result
-
def getCounts(self, tHandle: str, sTitle: str | None = None) -> tuple[int, int, int]:
"""Return the counts for a file, or a section of a file,
starting at title sTitle if it is provided.
@@ -793,7 +779,7 @@ class Index:
# =====================
class TagsIndex:
- """Core: Tags Index Wrapper Class
+ """Core: Tags Index Wrapper Class.
A wrapper class that holds the reverse lookup tags index. This is
just a simple wrapper around a single dictionary to keep tighter
@@ -804,14 +790,12 @@ class TagsIndex:
def __init__(self) -> None:
self._tags: dict[str, dict[str, str]] = {}
- return
def __contains__(self, tagKey: str) -> bool:
return tagKey.lower() in self._tags
def __delitem__(self, tagKey: str) -> None:
self._tags.pop(tagKey.lower(), None)
- return
def __getitem__(self, tagKey: str) -> dict | None:
return self._tags.get(tagKey.lower(), None)
@@ -823,7 +807,6 @@ class TagsIndex:
def clear(self) -> None:
"""Clear the index."""
self._tags = {}
- return
def items(self) -> ItemsView:
"""Return a dictionary view of all tags."""
@@ -839,7 +822,6 @@ class TagsIndex:
"heading": sTitle,
"class": className,
}
- return
def tagName(self, tagKey: str, default: str = "") -> str:
"""Get the name of a given tag."""
@@ -879,7 +861,6 @@ class TagsIndex:
for entry in self._tags.values():
if entry.get("handle") == tHandle:
entry["class"] = className
- return
##
# Pack/Unpack
@@ -922,11 +903,9 @@ class TagsIndex:
self.add(name, display, handle, heading, className)
- return
-
class IndexCache:
- """Core: Item Index Lookup Data Class
+ """Core: Item Index Lookup Data Class.
A small data class passed between all objects of the Item Index
which provides lookup capabilities and caching for shared data.
@@ -938,14 +917,13 @@ class IndexCache:
self.tags: TagsIndex = tagsIndex
self.story: set[str] = set()
self.note: set[str] = set()
- return
# The Item Index Objects
# ======================
class ItemIndex:
- """Core: Item Index Wrapper Class
+ """Core: Item Index Wrapper Class.
A wrapper object holding the indexed items. This is a wrapper
class around a single storage dictionary with a set of utility
@@ -960,14 +938,12 @@ class ItemIndex:
self._project = project
self._cache = IndexCache(tagsIndex)
self._items: dict[str, IndexNode] = {}
- return
def __contains__(self, tHandle: str) -> bool:
return tHandle in self._items
def __delitem__(self, tHandle: str) -> None:
self._items.pop(tHandle, None)
- return
def __getitem__(self, tHandle: str) -> IndexNode | None:
return self._items.get(tHandle, None)
@@ -979,14 +955,12 @@ class ItemIndex:
def clear(self) -> None:
"""Clear the index."""
self._items = {}
- return
def add(self, tHandle: str, nwItem: NWItem) -> None:
"""Add a new item to the index. This will overwrite the item if
it already exists.
"""
self._items[tHandle] = IndexNode(self._cache, tHandle, nwItem)
- return
def allStoryKeys(self) -> set[str]:
"""Return all story structure keys."""
@@ -1061,7 +1035,6 @@ class ItemIndex:
"""
if tHandle in self._items:
self._items[tHandle].setHeadingCounts(sTitle, cC, wC, pC)
- return
def setHeadingComment(
self, tHandle: str, sTitle: str,
@@ -1070,25 +1043,21 @@ class ItemIndex:
"""Set a story comment for a heading on a given item."""
if tHandle in self._items:
self._items[tHandle].setHeadingComment(sTitle, comment, key, text)
- return
def setHeadingTag(self, tHandle: str, sTitle: str, tagKey: str) -> None:
"""Set the main tag for a heading on a given item."""
if tHandle in self._items:
self._items[tHandle].setHeadingTag(sTitle, tagKey)
- return
def addHeadingRef(self, tHandle: str, sTitle: str, tagKeys: list[str], refType: str) -> None:
"""Set the reference tags for a heading on a given item."""
if tHandle in self._items:
self._items[tHandle].addHeadingRef(sTitle, tagKeys, refType)
- return
def addNoteKey(self, tHandle: str, style: T_NoteTypes, key: str) -> None:
"""Set notes key for a given item."""
if tHandle in self._items:
self._items[tHandle].addNoteKey(style, key)
- return
def genNewNoteKey(self, tHandle: str, style: T_NoteTypes) -> str:
"""Set notes key for a given item."""
@@ -1128,5 +1097,3 @@ class ItemIndex:
tItem = IndexNode(self._cache, tHandle, nwItem)
tItem.unpackData(tData)
self._items[tHandle] = tItem
-
- return
diff --git a/novelwriter/core/indexdata.py b/novelwriter/core/indexdata.py
index b74ce4c4..b7867bd3 100644
--- a/novelwriter/core/indexdata.py
+++ b/novelwriter/core/indexdata.py
@@ -23,7 +23,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -50,7 +50,7 @@ NOTE_TYPES: list[T_NoteTypes] = ["footnotes", "comments"]
class IndexNode:
- """Core: Single Index Item Node Class
+ """Core: Single Index Item Node Class.
This object represents the index data of a project item (NWItem).
It holds a record of all the headings in the text, and the meta data
@@ -68,7 +68,6 @@ class IndexNode:
self._headings: dict[str, IndexHeading] = {TT_NONE: IndexHeading(self._cache, TT_NONE)}
self._notes: dict[str, set[str]] = {}
self._count = 0
- return
def __repr__(self) -> str:
return f""
@@ -107,39 +106,33 @@ class IndexNode:
if TT_NONE in self._headings:
self._headings.pop(TT_NONE)
self._headings[tHeading.key] = tHeading
- return
def setHeadingCounts(self, sTitle: str, cCount: int, wCount: int, pCount: int) -> None:
"""Set the character, word and paragraph count of a heading."""
if sTitle in self._headings:
self._headings[sTitle].setCounts([cCount, wCount, pCount])
- return
def setHeadingComment(self, sTitle: str, comment: nwComment, key: str, text: str) -> None:
"""Set the comment text of a heading."""
if sTitle in self._headings:
self._headings[sTitle].setComment(comment.name, key, text)
- return
def setHeadingTag(self, sTitle: str, tag: str) -> None:
"""Set the tag of a heading."""
if sTitle in self._headings:
self._headings[sTitle].setTag(tag)
- return
def addHeadingRef(self, sTitle: str, tags: list[str], keyword: str) -> None:
"""Add a reference key and all its types to a heading."""
if sTitle in self._headings:
for tag in tags:
self._headings[sTitle].addReference(tag, keyword)
- return
def addNoteKey(self, style: T_NoteTypes, key: str) -> None:
"""Add a note key to the index."""
if style not in self._notes:
self._notes[style] = set()
self._notes[style].add(key)
- return
##
# Data Methods
@@ -195,11 +188,10 @@ class IndexNode:
self._notes[style] = set(keys)
else:
raise KeyError("Index node contains an invalid key")
- return
class IndexHeading:
- """Core: Single Index Heading Class
+ """Core: Single Index Heading Class.
This object represents a section of text in a project item
associated with a single (valid) heading. It holds a separate record
@@ -224,7 +216,6 @@ class IndexHeading:
self._tag = ""
self._refs: dict[str, set[str]] = {}
self._comments: dict[str, str] = {}
- return
def __repr__(self) -> str:
return f""
@@ -289,12 +280,10 @@ class IndexHeading:
"""Set the level of the heading if it's a valid value."""
if level in nwStyles.H_VALID:
self._level = level
- return
def setLine(self, line: int) -> None:
"""Set the line number of a heading."""
self._line = max(0, checkInt(line, 0))
- return
def setCounts(self, counts: Sequence[int]) -> None:
"""Set the character, word and paragraph count. Make sure the
@@ -306,7 +295,6 @@ class IndexHeading:
max(0, checkInt(counts[1], 0)),
max(0, checkInt(counts[2], 0)),
)
- return
def setComment(self, comment: str, key: str, text: str) -> None:
"""Set the text for a comment and make sure it is a string."""
@@ -319,12 +307,10 @@ class IndexHeading:
case "note" if key:
self._cache.note.add(key)
self._comments[f"note.{key}"] = str(text)
- return
def setTag(self, tag: str) -> None:
"""Set the tag for references, and make sure it is a string."""
self._tag = str(tag).lower()
- return
def addReference(self, tag: str, keyword: str) -> None:
"""Add a record of a reference tag, and what keyword types it is
@@ -335,7 +321,6 @@ class IndexHeading:
if tag not in self._refs:
self._refs[tag] = set()
self._refs[tag].add(keyword)
- return
##
# Getters
@@ -409,4 +394,3 @@ class IndexHeading:
self.setComment(comment, compact(kind), str(entry))
else:
raise KeyError("Unknown key in heading entry")
- return
diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py
index a6b92734..77b8d6c2 100644
--- a/novelwriter/core/item.py
+++ b/novelwriter/core/item.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -44,7 +44,7 @@ logger = logging.getLogger(__name__)
class NWItem:
- """Core: Item Data Class
+ """Core: Item Data Class.
This class holds all the project information about a project item.
Each item must be associated with a project and have a valid handle.
@@ -84,16 +84,14 @@ class NWItem:
self._wordInit = 0 # Initial character count
self._charInit = 0 # Initial word count
- return
-
def __repr__(self) -> str:
return f""
def __bool__(self) -> bool:
- """The truthiness of the class. The handle used to be initiated
- to None, but this is no longer the case. It should always
- evaluate to True since 2.1-beta1, although unpack and the NWTree
- class can leave it as an empty string.
+ """Check the truthiness of the class. The handle used to be
+ initiated to None, but this is no longer the case. It should
+ always evaluate to True since 2.1-beta1, although unpack and the
+ NWTree class can leave it as an empty string.
"""
return bool(self._handle)
@@ -206,15 +204,13 @@ class NWItem:
meta["cursorPos"] = str(self._cursorPos)
name["active"] = yesNo(self._active)
- data = {
+ return {
"name": str(self._name),
"itemAttr": item,
"metaAttr": meta,
"nameAttr": name,
}
- return data
-
def unpack(self, data: dict) -> bool:
"""Set the values from a data dictionary."""
item = data.get("itemAttr", {})
@@ -298,13 +294,11 @@ class NWItem:
def notifyToRefresh(self) -> None:
"""Notify GUI that item info needs to be refreshed."""
self._project.tree.refreshItems([self._handle])
- return
def notifyNovelStructureChange(self) -> None:
"""Notify that the structure of a novel has changed."""
if self._root and self._class == nwItemClass.NOVEL:
self._project.tree.novelStructureChanged(self._root)
- return
##
# Lookup Methods
@@ -363,13 +357,13 @@ class NWItem:
"""
if self.isFileType():
key = "checked" if self._active else "unchecked"
- color = "green" if self._active else "red"
+ color = "active" if self._active else "inactive"
text = trConst(nwLabels.ACTIVE_NAME[key])
- icon = SHARED.theme.getIcon(key, color)
else:
+ key = "noncheckable"
+ color = "disabled"
text = ""
- icon = SHARED.theme.getIcon("noncheckable", "faded")
- return text, icon
+ return text, SHARED.theme.getIcon(key, color)
##
# Checker Methods
@@ -457,8 +451,6 @@ class NWItem:
if self._import is None:
self.setImport("New") # This forces a default value lookup
- return
-
##
# Set Item Values
##
@@ -469,7 +461,6 @@ class NWItem:
self._name = simplified(name)
else:
self._name = ""
- return
def setParent(self, handle: Any) -> None:
"""Set the parent handle, and ensure it is valid."""
@@ -479,7 +470,6 @@ class NWItem:
self._parent = handle
else:
self._parent = None
- return
def setRoot(self, handle: Any) -> None:
"""Set the root handle, and ensure it is valid."""
@@ -489,7 +479,6 @@ class NWItem:
self._root = handle
else:
self._root = None
- return
def setOrder(self, order: Any) -> None:
"""Set the item order, and ensure that it is valid. This value
@@ -497,7 +486,6 @@ class NWItem:
the moment.
"""
self._order = checkInt(order, 0)
- return
def setType(self, value: Any) -> None:
"""Set the item type from either a proper nwItemType, or set it
@@ -510,7 +498,6 @@ class NWItem:
else:
logger.error("Unrecognised item type '%s'", value)
self._type = nwItemType.NO_TYPE
- return
def setClass(self, value: Any) -> None:
"""Set the item class from either a proper nwItemClass, or set
@@ -523,7 +510,6 @@ class NWItem:
else:
logger.error("Unrecognised item class '%s'", value)
self._class = nwItemClass.NO_CLASS
- return
def setLayout(self, value: Any) -> None:
"""Set the item layout from either a proper nwItemLayout, or set
@@ -536,21 +522,18 @@ class NWItem:
else:
logger.error("Unrecognised item layout '%s'", value)
self._layout = nwItemLayout.NO_LAYOUT
- return
def setStatus(self, value: Any) -> None:
"""Set the item status by looking it up in the valid status
items of the current project.
"""
self._status = self._project.data.itemStatus.check(value)
- return
def setImport(self, value: Any) -> None:
"""Set the item importance by looking it up in the valid import
items of the current project.
"""
self._import = self._project.data.itemImport.check(value)
- return
def setActive(self, state: Any) -> None:
"""Set the active flag."""
@@ -558,7 +541,6 @@ class NWItem:
self._active = state
else:
self._active = False
- return
def setExpanded(self, state: Any) -> None:
"""Set the expanded status of an item in the project tree."""
@@ -566,7 +548,6 @@ class NWItem:
self._expanded = state
else:
self._expanded = False
- return
##
# Set Document Meta Data
@@ -576,7 +557,6 @@ class NWItem:
"""Set the main heading level."""
if value in nwStyles.H_LEVEL:
self._heading = value
- return
def setCharCount(self, count: Any) -> None:
"""Set the character count, and ensure that it is an integer."""
@@ -584,7 +564,6 @@ class NWItem:
self._charCount = max(0, count)
else:
self._charCount = 0
- return
def setWordCount(self, count: Any) -> None:
"""Set the word count, and ensure that it is an integer."""
@@ -592,7 +571,6 @@ class NWItem:
self._wordCount = max(0, count)
else:
self._wordCount = 0
- return
def setParaCount(self, count: Any) -> None:
"""Set the paragraph count, and ensure that it is an integer."""
@@ -600,7 +578,6 @@ class NWItem:
self._paraCount = max(0, count)
else:
self._paraCount = 0
- return
def setCursorPos(self, position: Any) -> None:
"""Set the cursor position, and ensure that it is an integer."""
@@ -608,4 +585,3 @@ class NWItem:
self._cursorPos = max(0, position)
else:
self._cursorPos = 0
- return
diff --git a/novelwriter/core/itemmodel.py b/novelwriter/core/itemmodel.py
index 89696997..10ebaf6d 100644
--- a/novelwriter/core/itemmodel.py
+++ b/novelwriter/core/itemmodel.py
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -66,7 +66,7 @@ T_NodeData = str | QIcon | QFont | Qt.AlignmentFlag | None
class ProjectNode:
- """Core: Project Model Node Class
+ """Core: Project Model Node Class.
The project tree structure is saved as nodes in a tree, starting
from a root node. This class makes up these nodes.
@@ -103,7 +103,6 @@ class ProjectNode:
self._count = 0
self.refresh()
self.updateCount()
- return
def __repr__(self) -> str:
return (
@@ -114,7 +113,7 @@ class ProjectNode:
)
def __bool__(self) -> bool:
- """A node should always evaluate to True."""
+ # A node should always evaluate to True.
return True
##
@@ -162,15 +161,12 @@ class ProjectNode:
self._cache[C_STATUS_TIP] = sText
self._cache[C_STATUS_ACCESS] = sText
- return
-
def updateCount(self, propagate: bool = True) -> None:
"""Update counts, and propagate upwards in the tree."""
self._count = self._item.mainCount + sum(c._count for c in self._children) # noqa: SLF001
self._cache[C_COUNT_TEXT] = f"{self._count:n}"
if propagate and (parent := self._parent):
parent.updateCount()
- return
##
# Data Access
@@ -223,7 +219,6 @@ class ProjectNode:
self._children.append(child)
self._refreshChildrenPos()
self._item.notifyNovelStructureChange()
- return
def takeChild(self, pos: int) -> ProjectNode | None:
"""Remove a child item and return it."""
@@ -243,7 +238,6 @@ class ProjectNode:
self._children.insert(target, node)
self._refreshChildrenPos()
self._item.notifyNovelStructureChange()
- return
def setExpanded(self, state: bool) -> None:
"""Set the node's expanded state."""
@@ -251,7 +245,6 @@ class ProjectNode:
self._item.setExpanded(True)
else:
self._item.setExpanded(False)
- return
##
# Internal Functions
@@ -262,14 +255,12 @@ class ProjectNode:
for node in self._children:
children.append(node)
node._recursiveAppendChildren(children) # noqa: SLF001
- return
def _refreshChildrenPos(self) -> None:
"""Update the row value on all children."""
for n, child in enumerate(self._children):
child._row = n # noqa: SLF001
child.item.setOrder(n)
- return
def _updateRelationships(self, child: ProjectNode) -> None:
"""Update a child item's relationships."""
@@ -282,11 +273,10 @@ class ProjectNode:
child.item.setParent(None)
child.item.setRoot(child.item.itemHandle)
child.item.setClassDefaults(child.item.itemClass)
- return
class ProjectModel(QAbstractItemModel):
- """Core: Project Model Class
+ """Core: Project Model Class.
This class provides the interface for the tree widget used on the
GUI. It implements the QModelIndex based interface required, adds
@@ -302,11 +292,9 @@ class ProjectModel(QAbstractItemModel):
self._root = ProjectNode(NWItem(tree.project, INV_ROOT))
self._root.item.setName("Invisible Root")
logger.debug("Ready: ProjectModel")
- return
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: ProjectModel")
- return
##
# Properties
@@ -363,7 +351,7 @@ class ProjectModel(QAbstractItemModel):
##
def supportedDropActions(self) -> Qt.DropAction:
- """Return supported drop actions"""
+ """Return supported drop actions."""
return Qt.DropAction.MoveAction
def mimeTypes(self) -> list[str]:
@@ -445,7 +433,6 @@ class ProjectModel(QAbstractItemModel):
self.beginInsertRows(parent, row, row)
node.addChild(child, row)
self.endInsertRows()
- return
def removeChild(self, parent: QModelIndex, pos: int) -> ProjectNode | None:
"""Remove a node from the model and return it."""
@@ -469,7 +456,6 @@ class ProjectModel(QAbstractItemModel):
self.beginMoveRows(index.parent(), pos, pos, index.parent(), end)
parent.moveChild(pos, new)
self.endMoveRows()
- return
def multiMove(self, indices: list[QModelIndex], target: QModelIndex, pos: int = -1) -> None:
"""Move multiple items to a new location."""
@@ -497,7 +483,6 @@ class ProjectModel(QAbstractItemModel):
node._updateRelationships(child) # noqa: SLF001
child.item.notifyToRefresh()
node.item.notifyToRefresh()
- return
##
# Other Methods
@@ -506,7 +491,6 @@ class ProjectModel(QAbstractItemModel):
def clear(self) -> None:
"""Clear the project model."""
self._root.children.clear()
- return
def allExpanded(self) -> list[QModelIndex]:
"""Return a list of all expanded items."""
diff --git a/novelwriter/core/novelmodel.py b/novelwriter/core/novelmodel.py
index 0447f526..7b1cd426 100644
--- a/novelwriter/core/novelmodel.py
+++ b/novelwriter/core/novelmodel.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -54,6 +54,7 @@ T_NodeData = str | QIcon | QPixmap | Qt.AlignmentFlag | None
class NovelModel(QAbstractTableModel):
+ """Core: Novel Model CLass."""
__slots__ = ("_columns", "_extraKey", "_extraLabel", "_more", "_rows")
@@ -64,11 +65,9 @@ class NovelModel(QAbstractTableModel):
self._columns = 3
self._extraKey = ""
self._extraLabel = ""
- return
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: NovelModel")
- return
##
# Properties
@@ -102,7 +101,6 @@ class NovelModel(QAbstractTableModel):
self._columns = 4
self._extraKey = nwKeyWords.PLOT_KEY
self._extraLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])
- return
##
# Model Interface
@@ -147,7 +145,6 @@ class NovelModel(QAbstractTableModel):
def clear(self) -> None:
"""Clear the model."""
self._rows.clear()
- return
def append(self, node: IndexNode) -> None:
"""Append a node to the model."""
@@ -155,7 +152,6 @@ class NovelModel(QAbstractTableModel):
for key, head in node.items():
if key != "T0000":
self._rows.append(self._generateEntry(handle, key, head))
- return
def refresh(self, node: IndexNode) -> bool:
"""Refresh an index node."""
diff --git a/novelwriter/core/options.py b/novelwriter/core/options.py
index 2753c086..de8c5759 100644
--- a/novelwriter/core/options.py
+++ b/novelwriter/core/options.py
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import json
@@ -80,7 +80,7 @@ VALID_MAP: dict[str, set[str]] = {
class OptionState:
- """Core: GUI Options Storage
+ """Core: GUI Options Storage.
A class for storing the state of the GUI. The data is stored per
project. Settings that should be project-independent are stored in
@@ -90,7 +90,6 @@ class OptionState:
def __init__(self, project: NWProject) -> None:
self._project = project
self._state = {}
- return
##
# Load and Save Cache
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index ed5a8284..2a10fd92 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import json
@@ -57,6 +57,7 @@ logger = logging.getLogger(__name__)
class NWProjectState(Enum):
+ """The state of the loaded project."""
UNKNOWN = 0
LOCKED = 1
@@ -65,6 +66,11 @@ class NWProjectState(Enum):
class NWProject:
+ """Core: novelWriter Project Class.
+
+ This class is the parent class of the project, and holds instances
+ of project data, the project tree, and the project index.
+ """
__slots__ = (
"_changed", "_data", "_index", "_langData", "_options", "_session",
@@ -92,17 +98,13 @@ class NWProject:
logger.debug("Ready: NWProject")
- return
-
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: NWProject")
- return
def clear(self) -> None:
"""Clear the project."""
self._tree.clear()
self._index.clear()
- return
##
# Properties
@@ -224,7 +226,7 @@ class NWProject:
return True
- def copyFileContent(self, tHandle: str, sHandle: str) -> bool:
+ def copyFileContent(self, tHandle: str, sHandle: str, newTitle: str | None = None) -> bool:
"""Copy content to a new document after it is created. This
will not run if the file exists and is not empty.
"""
@@ -239,6 +241,14 @@ class NWProject:
logger.debug("Populating '%s' with text from '%s'", tHandle, sHandle)
text = self._storage.getDocumentText(sHandle)
+ if (
+ newTitle and (lines := text.split("\n")) and lines
+ and lines[0].startswith(("# ", "## ", "### ", "#### ", "#! ", "##! ", "###! "))
+ ):
+ prefix, _, _ = lines[0].partition(" ")
+ lines[0] = f"{prefix} {newTitle}"
+ text = "\n".join(lines)
+
self._storage.getDocument(tHandle).writeDocument(text)
sItem.setLayout(tItem.itemLayout)
self._index.reIndexHandle(tHandle)
@@ -255,7 +265,6 @@ class NWProject:
if rHandle and (tHandle := SHARED.project.newFile(tag.title(), rHandle)):
self.writeNewFile(tHandle, 1, False, f"@tag: {tag}\n\n")
self._tree.refreshItems([tHandle])
- return
##
# Project Methods
@@ -433,7 +442,6 @@ class NWProject:
self._tree.writeToCFile()
self._session.appendSession(idleTime)
self._storage.closeSession()
- return
def backupProject(self, doNotify: bool) -> bool:
"""Create a zip file of the entire project."""
@@ -483,15 +491,14 @@ class NWProject:
def setDefaultStatusImport(self) -> None:
"""Set the default status and importance values."""
- self._data.itemStatus.add(None, self.tr("New"), (120, 120, 120), "STAR", 0)
- self._data.itemStatus.add(None, self.tr("Note"), (205, 171, 143), "TRIANGLE", 0)
- self._data.itemStatus.add(None, self.tr("Draft"), (143, 240, 164), "CIRCLE_T", 0)
- self._data.itemStatus.add(None, self.tr("Finished"), (249, 240, 107), "STAR", 0)
- self._data.itemImport.add(None, self.tr("New"), (120, 120, 120), "SQUARE", 0)
- self._data.itemImport.add(None, self.tr("Minor"), (220, 138, 221), "BLOCK_2", 0)
- self._data.itemImport.add(None, self.tr("Major"), (220, 138, 221), "BLOCK_3", 0)
- self._data.itemImport.add(None, self.tr("Main"), (220, 138, 221), "BLOCK_4", 0)
- return
+ self._data.itemStatus.add(None, self.tr("New"), "faded", "STAR", 0)
+ self._data.itemStatus.add(None, self.tr("Note"), "red", "TRIANGLE", 0)
+ self._data.itemStatus.add(None, self.tr("Draft"), "yellow", "CIRCLE_T", 0)
+ self._data.itemStatus.add(None, self.tr("Finished"), "green", "STAR", 0)
+ self._data.itemImport.add(None, self.tr("New"), "purple", "SQUARE", 0)
+ self._data.itemImport.add(None, self.tr("Minor"), "purple", "BLOCK_2", 0)
+ self._data.itemImport.add(None, self.tr("Major"), "purple", "BLOCK_3", 0)
+ self._data.itemImport.add(None, self.tr("Main"), "purple", "BLOCK_4", 0)
def setProjectLang(self, language: str | None) -> None:
"""Set the project-specific language."""
@@ -500,7 +507,6 @@ class NWProject:
self._data.setLanguage(language)
self._loadProjectLocalisation()
self.setProjectChanged(True)
- return
def setProjectChanged(self, status: bool) -> bool:
"""Toggle the project changed flag, and propagate the
@@ -519,7 +525,6 @@ class NWProject:
"""Update the total word and character count values."""
wNovel, wNotes, cNovel, cNotes = self._tree.sumCounts()
self._data.setCurrCounts(wNovel=wNovel, wNotes=wNotes, cNovel=cNovel, cNotes=cNotes)
- return
def countStatus(self) -> None:
"""Count how many times the various status flags are used in the
@@ -533,7 +538,6 @@ class NWProject:
self._data.itemStatus.increment(nwItem.itemStatus)
else:
self._data.itemImport.increment(nwItem.itemImport)
- return
def updateStatus(self, kind: T_StatusKind, update: T_UpdateEntry) -> None:
"""Update status or import entries."""
@@ -545,7 +549,11 @@ class NWProject:
self._data.itemImport.update(update)
SHARED.emitStatusLabelsChanged(self, kind)
self._tree.refreshAllItems()
- return
+
+ def updateTheme(self) -> None:
+ """Update theme elements."""
+ self._data.itemStatus.refreshIcons()
+ self._data.itemImport.refreshIcons()
def localLookup(self, word: str | int) -> str:
"""Look up a word or number in the translation map for the
diff --git a/novelwriter/core/projectdata.py b/novelwriter/core/projectdata.py
index 15201970..12471e11 100644
--- a/novelwriter/core/projectdata.py
+++ b/novelwriter/core/projectdata.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -41,7 +41,7 @@ logger = logging.getLogger(__name__)
class NWProjectData:
- """Core: Project Data Class
+ """Core: Project Data Class.
The class holds all project data from the main XML file, aside from
the list of project items.
@@ -86,8 +86,6 @@ class NWProjectData:
self._status = NWStatus(NWStatus.STATUS)
self._import = NWStatus(NWStatus.IMPORT)
- return
-
##
# Properties
##
@@ -191,13 +189,11 @@ class NWProjectData:
"""Increment the save count by one."""
self._saveCount += 1
self._project.setProjectChanged(True)
- return
def incAutoCount(self) -> None:
"""Increment the auto save count by one."""
self._autoCount += 1
self._project.setProjectChanged(True)
- return
##
# Getters
@@ -219,67 +215,57 @@ class NWProjectData:
elif value != self._uuid:
self._uuid = value
self._project.setProjectChanged(True)
- return
def setName(self, value: str | None) -> None:
"""Set a new project name."""
if value != self._name:
self._name = simplified(str(value or ""))
self._project.setProjectChanged(True)
- return
def setAuthor(self, value: str | None) -> None:
"""Set the author value."""
if value != self._author:
self._author = simplified(str(value or ""))
self._project.setProjectChanged(True)
- return
def setSaveCount(self, value: Any) -> None:
"""Set the save count from last session."""
self._saveCount = checkInt(value, 0)
self._project.setProjectChanged(True)
- return
def setAutoCount(self, value: Any) -> None:
"""Set the auto save count from last session."""
self._autoCount = checkInt(value, 0)
self._project.setProjectChanged(True)
- return
def setEditTime(self, value: Any) -> None:
"""Set the edit time from last session."""
self._editTime = checkInt(value, 0)
self._project.setProjectChanged(True)
- return
def setDoBackup(self, value: Any) -> None:
"""Set the do write backup flag."""
if value != self._doBackup:
self._doBackup = checkBool(value, False)
self._project.setProjectChanged(True)
- return
def setLanguage(self, value: str | None) -> None:
"""Set the project language."""
if value != self._language:
self._language = checkStringNone(value, None)
self._project.setProjectChanged(True)
- return
def setSpellCheck(self, value: Any) -> None:
"""Set the spell check flag."""
if value != self._spellCheck:
self._spellCheck = checkBool(value, False)
self._project.setProjectChanged(True)
- return
def setSpellLang(self, value: str | None) -> None:
"""Set the spell check language."""
if value != self._spellLang:
self._spellLang = checkStringNone(value, None)
self._project.setProjectChanged(True)
- return
def setLastHandle(self, value: str | None, component: str) -> None:
"""Set a last used handle into the handle registry for a given
@@ -288,7 +274,6 @@ class NWProjectData:
if isinstance(component, str):
self._lastHandle[component] = checkStringNone(value, None)
self._project.setProjectChanged(True)
- return
def setLastHandles(self, value: dict) -> None:
"""Set the full last handles dictionary to a new set of values.
@@ -299,7 +284,6 @@ class NWProjectData:
if key in self._lastHandle:
self._lastHandle[key] = str(entry) if isHandle(entry) else None
self._project.setProjectChanged(True)
- return
def setInitCounts(
self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None
@@ -321,7 +305,6 @@ class NWProjectData:
count = checkInt(cNotes, 0)
self._initCounts[3] = count
self._currCounts[3] = count
- return
def setCurrCounts(
self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None
@@ -335,7 +318,6 @@ class NWProjectData:
self._currCounts[2] = checkInt(cNovel, 0)
if cNotes is not None:
self._currCounts[3] = checkInt(cNotes, 0)
- return
def setAutoReplace(self, value: dict) -> None:
"""Set the auto-replace dictionary."""
@@ -345,4 +327,3 @@ class NWProjectData:
if isinstance(entry, str):
self._autoReplace[key] = simplified(entry)
self._project.setProjectChanged(True)
- return
diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py
index 1e0e146b..8434b0fa 100644
--- a/novelwriter/core/projectxml.py
+++ b/novelwriter/core/projectxml.py
@@ -22,7 +22,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -46,7 +46,7 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
FILE_VERSION = "1.5" # The current project file format version
-FILE_REVISION = "5" # The current project file format revision
+FILE_REVISION = "6" # The current project file format revision
HEX_VERSION = 0x0105
NUM_VERSION = {
@@ -72,7 +72,7 @@ class XMLReadState(Enum):
class ProjectXMLReader:
- """Core: Project XML Reader
+ """Core: Project XML Reader.
All data is read into a NWProjectData instance, which must be
provided.
@@ -111,6 +111,8 @@ class ProjectXMLReader:
nodes. 2.5.
Rev 5: Added novelChars and notesChars attributes to content
node. 2.7 RC 1.
+ Rev 6: Replaced red, green and blue attributes with a single
+ color attribute. 2.8 Beta 1.
"""
def __init__(self, path: str | Path) -> None:
@@ -122,7 +124,6 @@ class ProjectXMLReader:
self._appVersion = ""
self._hexVersion = 0x0
self._timeStamp = ""
- return
##
# Properties
@@ -252,8 +253,6 @@ class ProjectXMLReader:
elif xItem.tag == "editTime": # Moved to attribute in 1.5
data.setEditTime(xItem.text)
- return
-
def _parseProjectSettings(self, xSection: ET.Element, data: NWProjectData) -> None:
"""Parse the settings section of the XML file."""
logger.debug("Parsing section")
@@ -292,8 +291,6 @@ class ProjectXMLReader:
elif xItem.tag == "notesWordCount": # Moved to content attribute in 1.5
data.setInitCounts(wNotes=xItem.text)
- return
-
def _parseProjectContent(
self, xSection: ET.Element, data: NWProjectData, content: list
) -> None:
@@ -354,8 +351,6 @@ class ProjectXMLReader:
"nameAttr": name,
})
- return
-
def _parseProjectContentLegacy(
self, xSection: ET.Element, data: NWProjectData, content: list
) -> None:
@@ -432,20 +427,20 @@ class ProjectXMLReader:
"nameAttr": name,
})
- return
-
def _parseStatusImport(self, xItem: ET.Element, sObject: NWStatus) -> None:
"""Parse a status or importance entry."""
for xEntry in xItem:
if xEntry.tag == "entry":
key = xEntry.attrib.get("key", None)
- red = checkInt(xEntry.attrib.get("red", 0), 0)
- green = checkInt(xEntry.attrib.get("green", 0), 0)
- blue = checkInt(xEntry.attrib.get("blue", 0), 0)
+ red = checkInt(xEntry.attrib.get("red", 0), 0) # Removed in 1.5 R6
+ green = checkInt(xEntry.attrib.get("green", 0), 0) # Removed in 1.5 R6
+ blue = checkInt(xEntry.attrib.get("blue", 0), 0) # Removed in 1.5 R6
+ color = xEntry.attrib.get("color") # Added in 1.5 R6
count = checkInt(xEntry.attrib.get("count", 0), 0)
shape = xEntry.attrib.get("shape", "")
- sObject.add(key, xEntry.text or "", (red, green, blue), shape, count)
- return
+ if color is None:
+ color = f"{red}, {green}, {blue}"
+ sObject.add(key, xEntry.text or "", color, shape, count)
def _parseDictKeyText(self, xItem: ET.Element) -> dict:
"""Parse a dictionary stored with key as an attribute and the
@@ -465,7 +460,7 @@ class ProjectXMLReader:
class ProjectXMLWriter:
- """Core: Project XML Writer
+ """Core: Project XML Writer.
The project writer class will only write a file according to the
very latest spec.
@@ -474,7 +469,6 @@ class ProjectXMLWriter:
def __init__(self, path: str | Path) -> None:
self._path = Path(path)
self._error = None
- return
##
# Properties
@@ -575,7 +569,6 @@ class ProjectXMLWriter:
"""Pack a single value into an XML element."""
xItem = ET.SubElement(xParent, name, attrib=attrib or {})
xItem.text = str(value) or ""
- return
def _packDictKeyValue(self, xParent: ET.Element, name: str, data: dict) -> None:
"""Pack the entries of a dictionary into an XML element."""
@@ -584,4 +577,3 @@ class ProjectXMLWriter:
if len(key) > 0:
xEntry = ET.SubElement(xItem, "entry", attrib={"key": key})
xEntry.text = str(value) or ""
- return
diff --git a/novelwriter/core/sessions.py b/novelwriter/core/sessions.py
index 6936ed99..58625c81 100644
--- a/novelwriter/core/sessions.py
+++ b/novelwriter/core/sessions.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import json
@@ -43,7 +43,7 @@ logger = logging.getLogger(__name__)
class NWSessionLog:
- """Core: Session JSON Lines Log File
+ """Core: Session JSON Lines Log File.
The class that wraps the session log file, which is in JSON Lines
format. That is, one JSON object per line.
@@ -52,7 +52,6 @@ class NWSessionLog:
def __init__(self, project: NWProject) -> None:
self._project = project
self._start = 0.0
- return
##
# Properties
@@ -70,7 +69,6 @@ class NWSessionLog:
def startSession(self) -> None:
"""Start the writing session."""
self._start = time()
- return
def appendSession(self, idleTime: float) -> bool:
"""Append session statistics to the sessions log file."""
diff --git a/novelwriter/core/spellcheck.py b/novelwriter/core/spellcheck.py
index a9e8b443..e2b0dd56 100644
--- a/novelwriter/core/spellcheck.py
+++ b/novelwriter/core/spellcheck.py
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import json
@@ -44,7 +44,7 @@ logger = logging.getLogger(__name__)
class NWSpellEnchant:
- """Core: Enchant Spell Checking Wrapper
+ """Core: Enchant Spell Checking Wrapper.
This is a rapper class for Enchant to keep the API consistent
between spell check tools.
@@ -57,11 +57,9 @@ class NWSpellEnchant:
self._language = None
self._broker = None
logger.debug("Ready: NWSpellEnchant")
- return
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: NWSpellEnchant")
- return
##
# Properties
@@ -106,21 +104,19 @@ class NWSpellEnchant:
for word in self._userDict:
self._enchant.add_to_session(word)
- return
-
##
# Methods
##
def checkWord(self, word: str) -> bool:
- """Wrapper function for pyenchant."""
+ """Forward check to pyenchant."""
try:
return bool(self._enchant.check(word))
except Exception:
return True
def suggestWords(self, word: str) -> list[str]:
- """Wrapper function for pyenchant."""
+ """Ask pyenchant for suggestions."""
try:
return self._enchant.suggest(word)
except Exception:
@@ -172,24 +168,29 @@ class FakeEnchant:
self.tag = ""
self.provider = FakeProvider()
- return
-
def check(self, word: str) -> bool:
+ """Return True for all words."""
return True
def suggest(self, word: str) -> list[str]:
+ """Return an empty suggestion list."""
return []
def add_to_session(self, word: str) -> None:
+ """Do nothing."""
return
class UserDictionary:
+ """Core: User Word Dictionary.
+
+ This class holds all the user's own words for spell checking
+ purposes. The dictionary is per-project.
+ """
def __init__(self, project: NWProject) -> None:
self._project = project
self._words = set()
- return
def __contains__(self, word: str) -> bool:
return word in self._words
@@ -219,7 +220,6 @@ class UserDictionary:
except Exception:
logger.error("Failed to load user dictionary")
logException()
- return
def save(self) -> None:
"""Save the user's dictionary."""
@@ -232,4 +232,3 @@ class UserDictionary:
except Exception:
logger.error("Failed to save user dictionary")
logException()
- return
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index f7d9b584..fc8bf85c 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import dataclasses
@@ -35,6 +35,7 @@ from PyQt6.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygon
from novelwriter import SHARED
from novelwriter.common import simplified
+from novelwriter.constants import nwLabels
from novelwriter.enum import nwStatusShape
from novelwriter.types import QtPaintAntiAlias, QtTransparent
@@ -43,12 +44,16 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
+CUSTOM_COL = "custom"
+
@dataclasses.dataclass
class StatusEntry:
+ """DataClass: Status Label Values."""
name: str
color: QColor
+ theme: str
shape: nwStatusShape
icon: QIcon
count: int = 0
@@ -62,13 +67,14 @@ class StatusEntry:
return status
-NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0)
+NO_ENTRY = StatusEntry("", QColor(0, 0, 0), CUSTOM_COL, nwStatusShape.SQUARE, QIcon(), 0)
T_UpdateEntry = list[tuple[str | None, StatusEntry]]
T_StatusKind = Literal["s", "i"]
class NWStatus:
+ """Core: Status/Importance Label Class."""
STATUS = "s"
IMPORT = "i"
@@ -80,7 +86,6 @@ class NWStatus:
self._default = None
self._prefix = prefix[:1]
self._height = SHARED.theme.baseIconHeight
- return
def __len__(self) -> int:
return len(self._store)
@@ -97,15 +102,12 @@ class NWStatus:
# Methods
##
- def add(self, key: str | None, name: str, color: tuple[int, int, int],
- shape: str, count: int) -> str:
+ def add(self, key: str | None, name: str, color: str, shape: str, count: int) -> str:
"""Add or update a status entry. If the key is invalid, a new
key is generated.
"""
- if isinstance(color, tuple) and len(color) == 3:
- qColor = QColor(*color)
- else:
- qColor = QColor(100, 100, 100)
+ qColor = SHARED.theme.parseColor(color)
+ theme = color if color in nwLabels.THEME_COLORS else CUSTOM_COL
try:
iShape = nwStatusShape[shape]
@@ -115,7 +117,7 @@ class NWStatus:
key = self._checkKey(key)
name = simplified(name)
icon = self.createIcon(self._height, qColor, iShape)
- self._store[key] = StatusEntry(name, qColor, iShape, icon, count)
+ self._store[key] = StatusEntry(name, qColor, theme, iShape, icon, count)
if self._default is None:
self._default = key
@@ -132,8 +134,6 @@ class NWStatus:
if self._default not in self._store:
self._default = next(iter(self._store)) if self._store else None
- return
-
def check(self, value: str) -> str:
"""Check the key against the stored status names."""
if self._isKey(value) and value in self._store:
@@ -146,23 +146,23 @@ class NWStatus:
"""Clear the counts of references to the status entries."""
for entry in self._store.values():
entry.count = 0
- return
def increment(self, key: str | None) -> None:
"""Increment the counter for a given entry."""
if key and key in self._store:
self._store[key].count += 1
- return
def pack(self) -> Iterable[tuple[str, dict]]:
"""Pack the status entries into a dictionary."""
for key, entry in self._store.items():
+ if entry.theme == CUSTOM_COL:
+ color = entry.color.name(QColor.NameFormat.HexRgb)
+ else:
+ color = entry.theme
yield (entry.name, {
"key": key,
"count": str(entry.count),
- "red": str(entry.color.red()),
- "green": str(entry.color.green()),
- "blue": str(entry.color.blue()),
+ "color": color,
"shape": entry.shape.name,
})
return
@@ -179,12 +179,20 @@ class NWStatus:
try:
shape = nwStatusShape[str(data[0])]
color = QColor(str(data[1]))
+ theme = CUSTOM_COL if data[1].startswith("#") else data[1]
icon = NWStatus.createIcon(self._height, color, shape)
- return StatusEntry(simplified(data[2]), color, shape, icon)
+ return StatusEntry(simplified(data[2]), color, theme, shape, icon)
except Exception:
logger.error("Could not parse entry %s", str(data))
return None
+ def refreshIcons(self) -> None:
+ """Refresh all icons."""
+ for entry in self._store.values():
+ if entry.theme != CUSTOM_COL:
+ entry.color = SHARED.theme.parseColor(entry.theme)
+ entry.icon = NWStatus.createIcon(self._height, entry.color, entry.shape)
+
@staticmethod
def createIcon(height: int, color: QColor, shape: nwStatusShape) -> QIcon:
"""Generate an icon for a status label."""
@@ -240,7 +248,6 @@ class _ShapeCache:
def __init__(self) -> None:
self._cache: dict[nwStatusShape, QPainterPath] = {}
- return
def getShape(self, shape: nwStatusShape) -> QPainterPath:
"""Return a painter shape for an icon."""
diff --git a/novelwriter/core/storage.py b/novelwriter/core/storage.py
index b8c21948..dfa0269b 100644
--- a/novelwriter/core/storage.py
+++ b/novelwriter/core/storage.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import json
@@ -47,6 +47,7 @@ logger = logging.getLogger(__name__)
class NWStorageOpen(Enum):
+ """The status of a storage location."""
UNKOWN = 0
NOT_FOUND = 1
@@ -56,6 +57,7 @@ class NWStorageOpen(Enum):
class NWStorageCreate(Enum):
+ """The status of a new storage location."""
NOT_EMPTY = 0
OS_ERROR = 1
@@ -63,7 +65,7 @@ class NWStorageCreate(Enum):
class NWStorage:
- """Core: Project Storage Class
+ """Core: Project Storage Class.
The class that handles all paths related to the project storage.
"""
@@ -81,7 +83,6 @@ class NWStorage:
self._openMode = self.MODE_INACTIVE
self._ready = False
self._exception = None
- return
def clear(self) -> None:
"""Reset internal variables."""
@@ -90,7 +91,6 @@ class NWStorage:
self._lockFilePath = None
self._openMode = self.MODE_INACTIVE
self._ready = False
- return
##
# Properties
@@ -252,13 +252,11 @@ class NWStorage:
"""Lock the session when the project is successfully opened."""
if self._ready:
self._writeLockFile()
- return
def closeSession(self) -> None:
"""Run tasks related to closing the session."""
self._clearLockFile()
self.clear()
- return
##
# Content Access Methods
@@ -394,7 +392,7 @@ class NWStorage:
class _LegacyStorage:
- """Core: Legacy Storage Converter Utils
+ """Core: Legacy Storage Converter Utils.
A class with various functions to convert old file formats and
file/folder layouts to the current project format.
@@ -402,7 +400,6 @@ class _LegacyStorage:
def __init__(self, project: NWProject) -> None:
self._project = project
- return
def legacyDataFolder(self, path: Path, child: Path) -> None:
"""Handle the content of a legacy data folder from a version 1.0
@@ -484,8 +481,6 @@ class _LegacyStorage:
except Exception as exc:
logger.warning("Failed to delete: %s", item, exc_info=exc)
- return
-
##
# Internal Functions
##
diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py
index 14158041..4ee4b626 100644
--- a/novelwriter/core/tree.py
+++ b/novelwriter/core/tree.py
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -50,7 +50,7 @@ MAX_DEPTH = 999 # Cap of tree traversing for loops (recursion limit)
class NWTree:
- """Core: Project Tree Data Class
+ """Core: Project Tree Data Class.
Only one instance of this class should exist in the project class.
This class holds all the project items of the project as instances
@@ -71,18 +71,16 @@ class NWTree:
self._trash = None
self._ready = False
logger.debug("Ready: NWTree")
- return
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: NWTree")
- return
def __len__(self) -> int:
- """The number of items in the project."""
+ """Return the number of items in the project."""
return len(self._items)
def __bool__(self) -> bool:
- """True if there are any items in the project."""
+ """Return True if there are any items in the project."""
return bool(self._items)
def __getitem__(self, tHandle: str | None) -> NWItem | None:
@@ -95,7 +93,7 @@ class NWTree:
return None
def __contains__(self, tHandle: str) -> bool:
- """Checks if a handle exists in the tree."""
+ """Check if a handle exists in the tree."""
return tHandle in self._items
def __iter__(self) -> Iterator[NWItem]:
@@ -142,7 +140,6 @@ class NWTree:
self._trash = None
oldModel.deleteLater()
del oldModel
- return
def add(self, item: NWItem, pos: int = -1) -> bool:
"""Add a project item into the project tree."""
@@ -260,8 +257,6 @@ class NWTree:
self._model.endInsertRows()
self._model.layoutChanged.emit()
- return
-
def pickParent(self, sNode: ProjectNode, hLevel: int, isNote: bool) -> tuple[str | None, int]:
"""Pick an appropriate parent handle for adding a new item."""
if sNode.item.isFolderType() or sNode.item.isRootType():
@@ -299,7 +294,6 @@ class NWTree:
indexE = self._model.indexFromNode(node, 3)
self._model.dataChanged.emit(indexS, indexE)
self._itemChange(node.item, nwChange.UPDATE)
- return
def refreshAllItems(self) -> None:
"""Refresh all items in the tree."""
@@ -309,13 +303,11 @@ class NWTree:
self._model.root.refresh()
self._model.root.updateCount(propagate=False)
self._model.layoutChanged.emit()
- return
def novelStructureChanged(self, tHandle: str) -> None:
"""Emit a novel structure change signal."""
if self._ready:
SHARED.novelStructureChanged.emit(tHandle)
- return
def checkConsistency(self, prefix: str) -> tuple[int, int]:
"""Check the project tree consistency. Also check the content
@@ -496,7 +488,6 @@ class NWTree:
SHARED.emitProjectItemChanged(self._project, tHandle, change)
if item.isRootType():
SHARED.emitRootFolderChanged(self._project, tHandle, change)
- return
def _getTrashNode(self) -> ProjectNode | None:
"""Get the trash node. If it doesn't exist, create it."""
diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py
index 3c7070df..59d4251f 100644
--- a/novelwriter/dialogs/about.py
+++ b/novelwriter/dialogs/about.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -45,6 +45,7 @@ logger = logging.getLogger(__name__)
class GuiAbout(NDialog):
+ """GUI: About novelWriter Dialog."""
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -109,11 +110,8 @@ class GuiAbout(NDialog):
logger.debug("Ready: GuiAbout")
- return
-
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiAbout")
- return
##
# Events
@@ -123,7 +121,6 @@ class GuiAbout(NDialog):
"""Capture the close event and perform cleanup."""
event.accept()
self.softDelete()
- return
##
# Internal Functions
@@ -135,7 +132,6 @@ class GuiAbout(NDialog):
self.txtCredits.setHtml(html)
else:
self.txtCredits.setHtml("Error loading credits text ...")
- return
def _setStyleSheet(self) -> None:
"""Set stylesheet text document."""
@@ -143,4 +139,3 @@ class GuiAbout(NDialog):
self.txtCredits.setStyleSheet(
f"QTextBrowser {{border: none; background: {baseCol};}} "
)
- return
diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py
index 9345efd6..2c392374 100644
--- a/novelwriter/dialogs/docmerge.py
+++ b/novelwriter/dialogs/docmerge.py
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -42,6 +42,7 @@ logger = logging.getLogger(__name__)
class GuiDocMerge(NDialog):
+ """GUI: Document Merge Tool."""
D_HANDLE = QtUserRole
@@ -110,11 +111,8 @@ class GuiDocMerge(NDialog):
logger.debug("Ready: GuiDocMerge")
- return
-
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiDocMerge")
- return
def data(self) -> dict:
"""Return the user's choices."""
@@ -150,7 +148,6 @@ class GuiDocMerge(NDialog):
if sHandle := self._data.get("sHandle"):
itemList = self._data.get("origItems", [])
self._loadContent(sHandle, itemList)
- return
##
# Internal Functions
@@ -170,4 +167,3 @@ class GuiDocMerge(NDialog):
item.setData(self.D_HANDLE, tHandle)
item.setCheckState(Qt.CheckState.Checked)
self.listBox.addItem(item)
- return
diff --git a/novelwriter/dialogs/docsplit.py b/novelwriter/dialogs/docsplit.py
index a67aafa7..5850aff3 100644
--- a/novelwriter/dialogs/docsplit.py
+++ b/novelwriter/dialogs/docsplit.py
@@ -21,20 +21,20 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
from PyQt6.QtCore import pyqtSlot
from PyQt6.QtWidgets import (
- QAbstractItemView, QComboBox, QDialogButtonBox, QGridLayout, QLabel,
- QListWidget, QListWidgetItem, QVBoxLayout, QWidget
+ QAbstractItemView, QDialogButtonBox, QGridLayout, QLabel, QListWidget,
+ QListWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import SHARED
from novelwriter.extensions.configlayout import NColorLabel
-from novelwriter.extensions.modified import NDialog
+from novelwriter.extensions.modified import NComboBox, NDialog
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtAccepted, QtDialogCancel, QtDialogOk, QtUserRole
@@ -42,6 +42,7 @@ logger = logging.getLogger(__name__)
class GuiDocSplit(NDialog):
+ """GUI: Document Split Tool."""
LINE_ROLE = QtUserRole
LEVEL_ROLE = QtUserRole + 1
@@ -79,7 +80,7 @@ class GuiDocSplit(NDialog):
self.listBox.setMinimumWidth(400)
self.listBox.setMinimumHeight(180)
- self.splitLevel = QComboBox(self)
+ self.splitLevel = NComboBox(self)
self.splitLevel.addItem(self.tr("Split on Heading Level 1 (Partition)"), 1)
self.splitLevel.addItem(self.tr("Split up to Heading Level 2 (Chapter)"), 2)
self.splitLevel.addItem(self.tr("Split up to Heading Level 3 (Scene)"), 3)
@@ -139,11 +140,8 @@ class GuiDocSplit(NDialog):
logger.debug("Ready: GuiDocSplit")
- return
-
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiDocSplit")
- return
def data(self) -> tuple[dict, list[str]]:
"""Return the user's choices. Also save the users options for
@@ -197,7 +195,6 @@ class GuiDocSplit(NDialog):
"""Reload the content of the list box."""
if sHandle := self._data.get("sHandle"):
self._loadContent(sHandle)
- return
##
# Internal Functions
diff --git a/novelwriter/dialogs/editlabel.py b/novelwriter/dialogs/editlabel.py
index 97bb269c..b8b4bae4 100644
--- a/novelwriter/dialogs/editlabel.py
+++ b/novelwriter/dialogs/editlabel.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -34,6 +34,7 @@ logger = logging.getLogger(__name__)
class GuiEditLabel(NDialog):
+ """GUI: Edit Item Label Dialog."""
def __init__(self, parent: QWidget, text: str = "") -> None:
super().__init__(parent=parent)
@@ -72,11 +73,8 @@ class GuiEditLabel(NDialog):
logger.debug("Ready: GuiEditLabel")
- return
-
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiEditLabel")
- return
@property
def itemLabel(self) -> str:
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index fbeb0523..758f7908 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -35,7 +35,7 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import compact, describeFont, processDialogSymbols, uniqueCompact
-from novelwriter.config import DEF_GUI, DEF_ICONS, DEF_SYNTAX, DEF_TREECOL
+from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS, DEF_TREECOL
from novelwriter.constants import nwLabels, nwQuotes, nwUnicode, trConst
from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.extensions.configlayout import NColorLabel, NScrollableForm
@@ -50,6 +50,7 @@ logger = logging.getLogger(__name__)
class GuiPreferences(NDialog):
+ """GUI: Preferences Dialog."""
newPreferencesReady = pyqtSignal(bool, bool, bool, bool)
@@ -125,11 +126,8 @@ class GuiPreferences(NDialog):
logger.debug("Ready: GuiPreferences")
- return
-
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiPreferences")
- return
def buildForm(self) -> None:
"""Build the settings form."""
@@ -165,22 +163,34 @@ class GuiPreferences(NDialog):
)
# Colour Theme
- self.guiTheme = NComboBox(self)
- self.guiTheme.setMinimumWidth(200)
- for theme, name in SHARED.theme.listThemes():
- self.guiTheme.addItem(name, theme)
- self.guiTheme.setCurrentData(CONFIG.guiTheme, DEF_GUI)
+ self.lightTheme = NComboBox(self)
+ self.lightTheme.setMinimumWidth(200)
+ self.darkTheme = NComboBox(self)
+ self.darkTheme.setMinimumWidth(200)
+ for key, theme in SHARED.theme.colourThemes.items():
+ if theme.dark:
+ self.darkTheme.addItem(theme.name, key)
+ else:
+ self.lightTheme.addItem(theme.name, key)
+
+ self.lightTheme.setCurrentData(CONFIG.lightTheme, DEF_GUI_LIGHT)
+ self.darkTheme.setCurrentData(CONFIG.darkTheme, DEF_GUI_DARK)
self.mainForm.addRow(
- self.tr("Colour theme"), self.guiTheme,
- self.tr("User interface colour theme."), stretch=(3, 2)
+ self.tr("Light colour theme"), self.lightTheme,
+ self.tr("You can change theme mode from the sidebar."), stretch=(3, 2)
+ )
+ self.mainForm.addRow(
+ self.tr("Dark colour theme"), self.darkTheme,
+ self.tr("You can change theme mode from the sidebar."), stretch=(3, 2)
)
# Icon Theme
self.iconTheme = NComboBox(self)
self.iconTheme.setMinimumWidth(200)
- for theme, name in SHARED.theme.iconCache.listThemes():
- self.iconTheme.addItem(name, theme)
+ for key, theme in SHARED.theme.iconCache.iconThemes.items():
+ self.iconTheme.addItem(theme.name, key)
+
self.iconTheme.setCurrentData(CONFIG.iconTheme, DEF_ICONS)
self.mainForm.addRow(
@@ -242,18 +252,6 @@ class GuiPreferences(NDialog):
self.sidebar.addButton(title, section)
self.mainForm.addGroupLabel(title, section)
- # Document Colour Theme
- self.guiSyntax = NComboBox(self)
- self.guiSyntax.setMinimumWidth(200)
- for syntax, name in SHARED.theme.listSyntax():
- self.guiSyntax.addItem(name, syntax)
- self.guiSyntax.setCurrentData(CONFIG.guiSyntax, DEF_SYNTAX)
-
- self.mainForm.addRow(
- self.tr("Document colour theme"), self.guiSyntax,
- self.tr("Colour theme for the editor and viewer."), stretch=(3, 2)
- )
-
# Document Font Family
self.textFont = QLineEdit(self)
self.textFont.setReadOnly(True)
@@ -294,6 +292,7 @@ class GuiPreferences(NDialog):
# Tree Icon Colours
self.iconColTree = NComboBox(self)
self.iconColTree.setMinimumWidth(200)
+ self.iconColTree.addItem(self.tr("Theme Colours"), DEF_TREECOL)
for key, label in nwLabels.THEME_COLORS.items():
self.iconColTree.addItem(trConst(label), key)
self.iconColTree.setCurrentData(CONFIG.iconColTree, DEF_TREECOL)
@@ -543,6 +542,13 @@ class GuiPreferences(NDialog):
unit=self.tr("px")
)
+ # Highlight Current Line
+ self.lineHighlight = NSwitch(self)
+ self.lineHighlight.setChecked(CONFIG.lineHighlight)
+ self.mainForm.addRow(
+ self.tr("Highlight current line"), self.lineHighlight
+ )
+
# Show Tabs and Spaces
self.showTabsNSpaces = NSwitch(self)
self.showTabsNSpaces.setChecked(CONFIG.showTabsNSpaces)
@@ -693,7 +699,7 @@ class GuiPreferences(NDialog):
self.showMultiSpaces = NSwitch(self)
self.showMultiSpaces.setChecked(CONFIG.showMultiSpaces)
self.mainForm.addRow(
- self.tr("Highlight multiple or trailing spaces"), self.showMultiSpaces,
+ self.tr("Highlight multiple spaces between words"), self.showMultiSpaces,
self.tr("Applies to the document editor only.")
)
@@ -848,8 +854,6 @@ class GuiPreferences(NDialog):
self.mainForm.finalise()
self.sidebar.setSelected(1)
- return
-
##
# Events
##
@@ -860,7 +864,6 @@ class GuiPreferences(NDialog):
self._saveWindowSize()
event.accept()
self.softDelete()
- return
def keyPressEvent(self, event: QKeyEvent) -> None:
"""Overload keyPressEvent and only accept escape. The main
@@ -870,7 +873,6 @@ class GuiPreferences(NDialog):
if event.matches(QKeySequence.StandardKey.Cancel):
self.close()
event.ignore()
- return
##
# Private Slots
@@ -880,13 +882,11 @@ class GuiPreferences(NDialog):
def _sidebarClicked(self, section: int) -> None:
"""Process a user request to switch page."""
self.mainForm.scrollToSection(section)
- return
@pyqtSlot()
def _gotoSearch(self) -> None:
"""Go to the setting indicated by the search text."""
self.mainForm.scrollToLabel(self.searchText.text().strip())
- return
@pyqtSlot()
def _selectGuiFont(self) -> None:
@@ -896,7 +896,6 @@ class GuiPreferences(NDialog):
self.guiFont.setText(describeFont(font))
self.guiFont.setCursorPosition(0)
self._guiFont = font
- return
@pyqtSlot()
def _selectTextFont(self) -> None:
@@ -906,7 +905,6 @@ class GuiPreferences(NDialog):
self.textFont.setText(describeFont(font))
self.textFont.setCursorPosition(0)
self._textFont = font
- return
@pyqtSlot()
def _backupFolder(self) -> None:
@@ -917,13 +915,11 @@ class GuiPreferences(NDialog):
):
self.backupPath = path
self.mainForm.setHelpText("backupPath", self.tr("Path: {0}").format(path))
- return
@pyqtSlot(bool)
def _toggledBackupOnClose(self, state: bool) -> None:
"""Toggle switch that depends on the backup on close switch."""
self.askBeforeBackup.setEnabled(state)
- return
@pyqtSlot(str)
def _insertDialogLineSymbol(self, symbol: str) -> None:
@@ -931,7 +927,6 @@ class GuiPreferences(NDialog):
current = self.dialogLine.text()
values = processDialogSymbols(f"{current} {symbol}")
self.dialogLine.setText(" ".join(values))
- return
@pyqtSlot(bool)
def _toggleAutoReplaceMain(self, state: bool) -> None:
@@ -941,7 +936,6 @@ class GuiPreferences(NDialog):
self.doReplaceDash.setEnabled(state)
self.doReplaceDots.setEnabled(state)
self.fmtPadThin.setEnabled(state)
- return
@pyqtSlot()
def _changeSingleQuoteOpen(self) -> None:
@@ -949,7 +943,6 @@ class GuiPreferences(NDialog):
quote, status = GuiQuoteSelect.getQuote(self, current=self.fmtSQuoteOpen.text())
if status:
self.fmtSQuoteOpen.setText(quote)
- return
@pyqtSlot()
def _changeSingleQuoteClose(self) -> None:
@@ -957,7 +950,6 @@ class GuiPreferences(NDialog):
quote, status = GuiQuoteSelect.getQuote(self, current=self.fmtSQuoteClose.text())
if status:
self.fmtSQuoteClose.setText(quote)
- return
@pyqtSlot()
def _changeDoubleQuoteOpen(self) -> None:
@@ -965,7 +957,6 @@ class GuiPreferences(NDialog):
quote, status = GuiQuoteSelect.getQuote(self, current=self.fmtDQuoteOpen.text())
if status:
self.fmtDQuoteOpen.setText(quote)
- return
@pyqtSlot()
def _changeDoubleQuoteClose(self) -> None:
@@ -973,7 +964,6 @@ class GuiPreferences(NDialog):
quote, status = GuiQuoteSelect.getQuote(self, current=self.fmtDQuoteClose.text())
if status:
self.fmtDQuoteClose.setText(quote)
- return
##
# Internal Functions
@@ -982,7 +972,6 @@ class GuiPreferences(NDialog):
def _saveWindowSize(self) -> None:
"""Save the dialog window size."""
CONFIG.setPreferencesWinSize(self.width(), self.height())
- return
def _doSave(self) -> None:
"""Save the values set in the form."""
@@ -993,18 +982,23 @@ class GuiPreferences(NDialog):
# Appearance
guiLocale = self.guiLocale.currentData()
- guiTheme = self.guiTheme.currentData()
+ lightTheme = self.lightTheme.currentData()
+ darkTheme = self.darkTheme.currentData()
iconTheme = self.iconTheme.currentData()
useCharCount = self.useCharCount.isChecked()
- updateTheme |= CONFIG.guiTheme != guiTheme
+ updateTheme |= CONFIG.lightTheme != lightTheme
+ updateTheme |= CONFIG.darkTheme != darkTheme
updateTheme |= CONFIG.iconTheme != iconTheme
needsRestart |= CONFIG.guiLocale != guiLocale
needsRestart |= CONFIG.guiFont != self._guiFont
refreshTree |= CONFIG.useCharCount != useCharCount
+ updateSyntax |= CONFIG.lightTheme != lightTheme
+ updateSyntax |= CONFIG.darkTheme != darkTheme
CONFIG.guiLocale = guiLocale
- CONFIG.guiTheme = guiTheme
+ CONFIG.lightTheme = lightTheme
+ CONFIG.darkTheme = darkTheme
CONFIG.iconTheme = iconTheme
CONFIG.hideVScroll = self.hideVScroll.isChecked()
CONFIG.hideHScroll = self.hideHScroll.isChecked()
@@ -1013,11 +1007,6 @@ class GuiPreferences(NDialog):
CONFIG.setGuiFont(self._guiFont)
# Document Style
- guiSyntax = self.guiSyntax.currentData()
-
- updateSyntax |= CONFIG.guiSyntax != guiSyntax
-
- CONFIG.guiSyntax = guiSyntax
CONFIG.showFullPath = self.showFullPath.isChecked()
CONFIG.incNotesWCount = self.incNotesWCount.isChecked()
CONFIG.setTextFont(self._textFont)
@@ -1058,9 +1047,14 @@ class GuiPreferences(NDialog):
CONFIG.tabWidth = self.tabWidth.value()
# Text Editing
+ lineHighlight = self.lineHighlight.isChecked()
+
+ updateSyntax |= CONFIG.lineHighlight != lineHighlight
+
CONFIG.spellLanguage = self.spellLanguage.currentData()
CONFIG.autoSelect = self.autoSelect.isChecked()
CONFIG.cursorWidth = self.cursorWidth.value()
+ CONFIG.lineHighlight = lineHighlight
CONFIG.showTabsNSpaces = self.showTabsNSpaces.isChecked()
CONFIG.showLineEndings = self.showLineEndings.isChecked()
@@ -1121,5 +1115,3 @@ class GuiPreferences(NDialog):
self.newPreferencesReady.emit(needsRestart, refreshTree, updateTheme, updateSyntax)
self.close()
-
- return
diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py
index 623be345..5e16d52e 100644
--- a/novelwriter/dialogs/projectsettings.py
+++ b/novelwriter/dialogs/projectsettings.py
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import csv
@@ -33,14 +33,14 @@ from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QCloseEvent, QColor
from PyQt6.QtWidgets import (
QAbstractItemView, QApplication, QColorDialog, QDialogButtonBox,
- QFileDialog, QGridLayout, QHBoxLayout, QLineEdit, QMenu, QStackedWidget,
- QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
+ QFileDialog, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
+ QStackedWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import formatFileFilter, qtAddAction, qtLambda, simplified
from novelwriter.constants import nwLabels, trConst
-from novelwriter.core.status import NWStatus, StatusEntry
+from novelwriter.core.status import CUSTOM_COL, NWStatus, StatusEntry
from novelwriter.enum import nwStatusShape
from novelwriter.extensions.configlayout import NColorLabel, NFixedPage, NScrollableForm
from novelwriter.extensions.modified import NComboBox, NDialog, NIconToolButton
@@ -55,6 +55,7 @@ logger = logging.getLogger(__name__)
class GuiProjectSettings(NDialog):
+ """GUI: Project Settings DIalog."""
PAGE_SETTINGS = 0
PAGE_STATUS = 1
@@ -137,11 +138,8 @@ class GuiProjectSettings(NDialog):
logger.debug("Ready: GuiProjectSettings")
- return
-
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiProjectSettings")
- return
##
# Events
@@ -152,7 +150,6 @@ class GuiProjectSettings(NDialog):
self._saveSettings()
event.accept()
self.softDelete()
- return
##
# Private Slots
@@ -169,7 +166,6 @@ class GuiProjectSettings(NDialog):
self.mainStack.setCurrentWidget(self.importPage)
elif pageId == self.PAGE_REPLACE:
self.mainStack.setCurrentWidget(self.replacePage)
- return
@pyqtSlot()
def _doSave(self) -> None:
@@ -203,8 +199,6 @@ class GuiProjectSettings(NDialog):
QApplication.processEvents()
self.close()
- return
-
##
# Internal Functions
##
@@ -223,8 +217,6 @@ class GuiProjectSettings(NDialog):
options.setValue("GuiProjectSettings", "importColW", importColW)
options.setValue("GuiProjectSettings", "replaceColW", replaceColW)
- return
-
class _SettingsPage(NScrollableForm):
@@ -295,8 +287,6 @@ class _SettingsPage(NScrollableForm):
self.finalise()
- return
-
class _StatusPage(NFixedPage):
@@ -326,6 +316,7 @@ class _StatusPage(NFixedPage):
self._changed = False
self._color = QColor(100, 100, 100)
self._shape = nwStatusShape.SQUARE
+ self._theme = CUSTOM_COL
self._icons = {}
self._iPx = SHARED.theme.baseIconHeight
@@ -384,11 +375,26 @@ class _StatusPage(NFixedPage):
self.exportButton.clicked.connect(self._exportLabels)
# Edit Form
- self.labelText = QLineEdit(self)
- self.labelText.setMaxLength(40)
- self.labelText.setPlaceholderText(self.tr("Select item to edit"))
- self.labelText.setEnabled(False)
- self.labelText.textEdited.connect(self._onNameEdit)
+ self.editName = QLineEdit(self)
+ self.editName.setMaxLength(40)
+ self.editName.setPlaceholderText(self.tr("Select item to edit"))
+ self.editName.setEnabled(False)
+ self.editName.textEdited.connect(self._onNameEdit)
+
+ self.labelName = QLabel(self.tr("Label"), self)
+ self.labelName.setBuddy(self.editName)
+
+ # Icon Colours
+ self.iconColor = NComboBox(self)
+ self.iconColor.setMinimumWidth(200)
+ self.iconColor.setEnabled(False)
+ self.iconColor.addItem(self.tr("Custom"), CUSTOM_COL)
+ for key, label in nwLabels.THEME_COLORS.items():
+ self.iconColor.addItem(trConst(label), key)
+ self.iconColor.currentIndexChanged.connect(self._onThemeSelect)
+
+ self.labelColor = QLabel(self.tr("Colour"), self)
+ self.labelColor.setBuddy(self.iconColor)
buttonStyle = (
"QToolButton {padding: 0 4px;} "
@@ -425,6 +431,9 @@ class _StatusPage(NFixedPage):
self.shapeButton.setStyleSheet(buttonStyle)
self.shapeButton.setEnabled(False)
+ self.labelShape = QLabel(self.tr("Shape"), self)
+ self.labelShape.setBuddy(self.iconColor)
+
# Assemble
self.listControls = QVBoxLayout()
self.listControls.addWidget(self.addButton)
@@ -435,10 +444,15 @@ class _StatusPage(NFixedPage):
self.listControls.addWidget(self.importButton)
self.listControls.addWidget(self.exportButton)
- self.editBox = QHBoxLayout()
- self.editBox.addWidget(self.labelText, 1)
- self.editBox.addWidget(self.colorButton, 0)
- self.editBox.addWidget(self.shapeButton, 0)
+ self.editBox = QGridLayout()
+ self.editBox.addWidget(self.labelName, 0, 0)
+ self.editBox.addWidget(self.editName, 0, 1, 1, 5)
+ self.editBox.addWidget(self.labelColor, 1, 0)
+ self.editBox.addWidget(self.iconColor, 1, 1)
+ self.editBox.addWidget(self.colorButton, 1, 2)
+ self.editBox.addWidget(self.labelShape, 1, 3)
+ self.editBox.addWidget(self.shapeButton, 1, 4)
+ self.editBox.setColumnStretch(5, 1)
self.innerBox = QGridLayout()
self.innerBox.addWidget(self.listBox, 0, 0)
@@ -454,8 +468,6 @@ class _StatusPage(NFixedPage):
self.setCentralLayout(self.outerBox)
self._setButtonIcons()
- return
-
@property
def changed(self) -> bool:
"""The user changed these settings."""
@@ -494,16 +506,22 @@ class _StatusPage(NFixedPage):
entry.name = name
item.setText(self.C_LABEL, name)
self._changed = True
- return
+
+ @pyqtSlot(int)
+ def _onThemeSelect(self, index: int) -> None:
+ """Update the colour handling on theme selection change."""
+ self._theme = str(self.iconColor.currentData())
+ self._setButtonIcons()
+ self._updateIcon()
@pyqtSlot()
def _onColorSelect(self) -> None:
"""Open a dialog to select the status icon colour."""
if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid():
self._color = color
+ self._theme = CUSTOM_COL
self._setButtonIcons()
self._updateIcon()
- return
@pyqtSlot()
def _onItemCreate(self) -> None:
@@ -511,9 +529,9 @@ class _StatusPage(NFixedPage):
color = QColor(100, 100, 100)
shape = nwStatusShape.SQUARE
icon = NWStatus.createIcon(self._iPx, color, shape)
- self._addItem(None, StatusEntry(self.tr("New Item"), color, shape, icon, 0))
+ theme = str(self.iconColor.currentData())
+ self._addItem(None, StatusEntry(self.tr("New Item"), color, theme, shape, icon, 0))
self._changed = True
- return
@pyqtSlot()
def _onItemDelete(self) -> None:
@@ -526,7 +544,6 @@ class _StatusPage(NFixedPage):
else:
self.listBox.takeTopLevelItem(iRow)
self._changed = True
- return
@pyqtSlot()
def _onSelectionChanged(self) -> None:
@@ -537,25 +554,28 @@ class _StatusPage(NFixedPage):
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
self._color = entry.color
self._shape = entry.shape
+ self._theme = entry.theme
self._setButtonIcons()
- self.labelText.setText(entry.name)
- self.labelText.selectAll()
- self.labelText.setFocus()
+ self.editName.setText(entry.name)
+ self.editName.selectAll()
+ self.editName.setFocus()
- self.labelText.setEnabled(True)
+ self.editName.setEnabled(True)
+ self.iconColor.setEnabled(True)
self.colorButton.setEnabled(True)
self.shapeButton.setEnabled(True)
else:
self._color = QColor(100, 100, 100)
self._shape = nwStatusShape.SQUARE
+ self._theme = CUSTOM_COL
self._setButtonIcons()
- self.labelText.setText("")
+ self.editName.setText("")
- self.labelText.setEnabled(False)
+ self.editName.setEnabled(False)
+ self.iconColor.setEnabled(False)
self.colorButton.setEnabled(False)
self.shapeButton.setEnabled(False)
- return
@pyqtSlot()
def _importLabels(self) -> None:
@@ -592,7 +612,6 @@ class _StatusPage(NFixedPage):
writer.writerow([entry.shape.name, entry.color.name(), entry.name])
except Exception as exc:
SHARED.error("Could not write file.", exc=exc)
- return
##
# Internal Functions
@@ -603,19 +622,18 @@ class _StatusPage(NFixedPage):
self._shape = shape
self._setButtonIcons()
self._updateIcon()
- return
def _updateIcon(self) -> None:
"""Apply changes made to a status icon."""
if item := self._getSelectedItem():
- icon = NWStatus.createIcon(self._iPx, self._color, self._shape)
+ icon = NWStatus.createIcon(self._iPx, self._pickColor(), self._shape)
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
entry.color = self._color
entry.shape = self._shape
+ entry.theme = self._theme
entry.icon = icon
item.setIcon(self.C_LABEL, icon)
self._changed = True
- return
def _addItem(self, key: str | None, entry: StatusEntry) -> None:
"""Add a status item to the list."""
@@ -626,7 +644,6 @@ class _StatusPage(NFixedPage):
item.setData(self.C_DATA, self.D_KEY, key)
item.setData(self.C_DATA, self.D_ENTRY, entry)
self.listBox.addTopLevelItem(item)
- return
def _moveItem(self, step: int) -> None:
"""Move and item up or down step."""
@@ -639,7 +656,6 @@ class _StatusPage(NFixedPage):
self.listBox.clearSelection()
cItem.setSelected(True)
self._changed = True
- return
def _getSelectedItem(self) -> QTreeWidgetItem | None:
"""Get the currently selected item."""
@@ -658,10 +674,16 @@ class _StatusPage(NFixedPage):
def _setButtonIcons(self) -> None:
"""Set the colour of the colour button."""
- icon = NWStatus.createIcon(self._iPx, self._color, nwStatusShape.SQUARE)
+ icon = NWStatus.createIcon(self._iPx, self._pickColor(), nwStatusShape.SQUARE)
+ self.iconColor.setCurrentData(self._theme, CUSTOM_COL)
self.colorButton.setIcon(icon)
self.shapeButton.setIcon(self._icons[self._shape])
- return
+
+ def _pickColor(self) -> QColor:
+ """Get the correct colour value based on selections."""
+ if self._theme == CUSTOM_COL:
+ return self._color
+ return SHARED.theme.getBaseColor(self._theme)
class _ReplacePage(NFixedPage):
@@ -743,8 +765,6 @@ class _ReplacePage(NFixedPage):
self.setCentralLayout(self.outerBox)
- return
-
@property
def changed(self) -> bool:
"""The user changed these settings."""
@@ -777,7 +797,6 @@ class _ReplacePage(NFixedPage):
if (item := self._getSelectedItem()) and (key := self._stripKey(text)):
item.setText(self.C_KEY, f"<{key}>")
self._changed = True
- return
@pyqtSlot(str)
def _onValueEdit(self, text: str) -> None:
@@ -785,7 +804,6 @@ class _ReplacePage(NFixedPage):
if item := self._getSelectedItem():
item.setText(self.C_REPL, text)
self._changed = True
- return
@pyqtSlot()
def _onSelectionChanged(self) -> None:
@@ -804,14 +822,12 @@ class _ReplacePage(NFixedPage):
self.editValue.setText("")
self.editKey.setEnabled(False)
self.editValue.setEnabled(False)
- return
@pyqtSlot()
def _onEntryCreated(self) -> None:
"""Add a new list entry."""
key = f""
self.listBox.addTopLevelItem(QTreeWidgetItem([key, ""]))
- return
@pyqtSlot()
def _onEntryDeleted(self) -> None:
@@ -819,7 +835,6 @@ class _ReplacePage(NFixedPage):
if item := self._getSelectedItem():
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(item))
self._changed = True
- return
##
# Internal Functions
diff --git a/novelwriter/dialogs/quotes.py b/novelwriter/dialogs/quotes.py
index 931558a2..6030ecfe 100644
--- a/novelwriter/dialogs/quotes.py
+++ b/novelwriter/dialogs/quotes.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -43,6 +43,7 @@ logger = logging.getLogger(__name__)
class GuiQuoteSelect(NDialog):
+ """GUI: Quote Selector Dialog."""
_selected = ""
@@ -108,11 +109,8 @@ class GuiQuoteSelect(NDialog):
logger.debug("Ready: GuiQuoteSelect")
- return
-
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiQuoteSelect")
- return
@property
def selectedQuote(self) -> str:
@@ -140,4 +138,3 @@ class GuiQuoteSelect(NDialog):
quote = items[0].data(self.D_KEY)
self.previewLabel.setText(quote)
self._selected = quote
- return
diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py
index a667738a..19e2da9d 100644
--- a/novelwriter/dialogs/wordlist.py
+++ b/novelwriter/dialogs/wordlist.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -48,6 +48,7 @@ logger = logging.getLogger(__name__)
class GuiWordList(NDialog):
+ """GUI: User Dictionary Edit Tool."""
newWordListReady = pyqtSignal()
@@ -128,11 +129,8 @@ class GuiWordList(NDialog):
logger.debug("Ready: GuiWordList")
- return
-
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiWordList")
- return
##
# Events
@@ -143,7 +141,6 @@ class GuiWordList(NDialog):
self._saveGuiSettings()
event.accept()
self.softDelete()
- return
##
# Private Slots
@@ -159,14 +156,12 @@ class GuiWordList(NDialog):
if items := self.listBox.findItems(word, Qt.MatchFlag.MatchExactly):
self.listBox.setCurrentItem(items[0])
self.listBox.scrollToItem(items[0], QAbstractItemView.ScrollHint.PositionAtCenter)
- return
@pyqtSlot()
def _doDelete(self) -> None:
"""Delete the selected items."""
for item in self.listBox.selectedItems():
self.listBox.takeItem(self.listBox.row(item))
- return
@pyqtSlot()
def _doSave(self) -> None:
@@ -178,7 +173,6 @@ class GuiWordList(NDialog):
self.newWordListReady.emit()
QApplication.processEvents()
self.close()
- return
@pyqtSlot()
def _importWords(self) -> None:
@@ -213,7 +207,6 @@ class GuiWordList(NDialog):
fo.write("\n".join(self._listWords()))
except Exception as exc:
SHARED.error("Could not write file.", exc=exc)
- return
##
# Internal Functions
@@ -226,7 +219,6 @@ class GuiWordList(NDialog):
self.listBox.clear()
for word in userDict:
self.listBox.addItem(word)
- return
def _saveGuiSettings(self) -> None:
"""Save GUI settings."""
@@ -234,14 +226,12 @@ class GuiWordList(NDialog):
pOptions = SHARED.project.options
pOptions.setValue("GuiWordList", "winWidth", self.width())
pOptions.setValue("GuiWordList", "winHeight", self.height())
- return
def _addWord(self, word: str) -> None:
"""Add a single word to the list."""
if word and not self.listBox.findItems(word, Qt.MatchFlag.MatchExactly):
self.listBox.addItem(word)
self._changed = True
- return
def _listWords(self) -> list[str]:
"""List all words in the list box."""
diff --git a/novelwriter/enum.py b/novelwriter/enum.py
index a837d824..7ea60294 100644
--- a/novelwriter/enum.py
+++ b/novelwriter/enum.py
@@ -20,12 +20,13 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from enum import Enum
class nwItemType(Enum):
+ """Enum: Project Item Types."""
NO_TYPE = 0
ROOT = 1
@@ -34,6 +35,7 @@ class nwItemType(Enum):
class nwItemClass(Enum):
+ """Enum: Project Item Classes."""
NO_CLASS = 0
NOVEL = 1
@@ -50,6 +52,7 @@ class nwItemClass(Enum):
class nwItemLayout(Enum):
+ """A project item's layout."""
NO_LAYOUT = 0
DOCUMENT = 1
@@ -57,6 +60,7 @@ class nwItemLayout(Enum):
class nwComment(Enum):
+ """Types of text comments."""
PLAIN = 0
IGNORE = 1
@@ -69,6 +73,7 @@ class nwComment(Enum):
class nwChange(Enum):
+ """Change request modes."""
CREATE = 0
UPDATE = 1
@@ -76,12 +81,14 @@ class nwChange(Enum):
class nwDocMode(Enum):
+ """Document open modes."""
VIEW = 0
EDIT = 1
class nwDocAction(Enum):
+ """Document actions."""
NO_ACTION = 0
UNDO = 1
@@ -92,38 +99,40 @@ class nwDocAction(Enum):
MD_ITALIC = 6
MD_BOLD = 7
MD_STRIKE = 8
- S_QUOTE = 9
- D_QUOTE = 10
- SEL_ALL = 11
- SEL_PARA = 12
- BLOCK_H1 = 13
- BLOCK_H2 = 14
- BLOCK_H3 = 15
- BLOCK_H4 = 16
- BLOCK_COM = 17
- BLOCK_IGN = 18
- BLOCK_TXT = 19
- BLOCK_TTL = 20
- BLOCK_UNN = 21
- BLOCK_HSC = 22
- REPL_SNG = 23
- REPL_DBL = 24
- RM_BREAKS = 25
- ALIGN_L = 26
- ALIGN_C = 27
- ALIGN_R = 28
- INDENT_L = 29
- INDENT_R = 30
- SC_ITALIC = 31
- SC_BOLD = 32
- SC_STRIKE = 33
- SC_ULINE = 34
- SC_MARK = 35
- SC_SUP = 36
- SC_SUB = 37
+ MD_MARK = 9
+ S_QUOTE = 10
+ D_QUOTE = 11
+ SEL_ALL = 12
+ SEL_PARA = 13
+ BLOCK_H1 = 14
+ BLOCK_H2 = 15
+ BLOCK_H3 = 16
+ BLOCK_H4 = 17
+ BLOCK_COM = 18
+ BLOCK_IGN = 19
+ BLOCK_TXT = 20
+ BLOCK_TTL = 21
+ BLOCK_UNN = 22
+ BLOCK_HSC = 23
+ REPL_SNG = 24
+ REPL_DBL = 25
+ RM_BREAKS = 26
+ ALIGN_L = 27
+ ALIGN_C = 28
+ ALIGN_R = 29
+ INDENT_L = 30
+ INDENT_R = 31
+ SC_ITALIC = 32
+ SC_BOLD = 33
+ SC_STRIKE = 34
+ SC_ULINE = 35
+ SC_MARK = 36
+ SC_SUP = 37
+ SC_SUB = 38
class nwDocInsert(Enum):
+ """Document insert actions."""
NO_INSERT = 0
QUOTE_LS = 1
@@ -141,6 +150,7 @@ class nwDocInsert(Enum):
class nwView(Enum):
+ """Main GUI view modes."""
EDITOR = 0
PROJECT = 1
@@ -150,13 +160,23 @@ class nwView(Enum):
class nwFocus(Enum):
+ """Main GUI panel focus."""
TREE = 1
DOCUMENT = 2
OUTLINE = 3
+class nwTheme(Enum):
+ """GUI theme colour modes."""
+
+ AUTO = 0
+ LIGHT = 1
+ DARK = 2
+
+
class nwOutline(Enum):
+ """Enum: Project Outline Columns."""
TITLE = 0
LEVEL = 1
@@ -181,6 +201,7 @@ class nwOutline(Enum):
class nwNovelExtra(Enum):
+ """Enum: Novel View Extra Columns."""
HIDDEN = 0
POV = 1
@@ -189,6 +210,7 @@ class nwNovelExtra(Enum):
class nwBuildFmt(Enum):
+ """Enum: Manuscript Document Formats."""
ODT = 0
FODT = 1
@@ -203,6 +225,7 @@ class nwBuildFmt(Enum):
class nwStatusShape(Enum):
+ """Enum: Status/Importance Icon Shapes."""
SQUARE = 0
TRIANGLE = 1
diff --git a/novelwriter/error.py b/novelwriter/error.py
index 03f5d07a..59fafdff 100644
--- a/novelwriter/error.py
+++ b/novelwriter/error.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -47,7 +47,6 @@ def logException() -> None:
exType, exValue, _ = sys.exc_info()
if exType is not None:
logger.error(f"{exType.__name__}: {exValue!s}", stacklevel=2)
- return
def formatException(exc: BaseException) -> str:
@@ -58,6 +57,7 @@ def formatException(exc: BaseException) -> str:
class NWErrorMessage(QDialog):
+ """GUI: Error Dialog."""
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -110,8 +110,6 @@ class NWErrorMessage(QDialog):
self.setSizeGripEnabled(True)
self.resize(800, 400)
- return
-
def setMessage(self, exType: type, exValue: BaseException, exTrace: TracebackType) -> None:
"""Generate a message and append session data, error info and
error traceback.
@@ -143,7 +141,7 @@ class NWErrorMessage(QDialog):
enchantVersion = "Unknown"
try:
- txtTrace = "\n".join(format_tb(exTrace))
+ trace = "\n".join(format_tb(exTrace))
self.msgBody.setPlainText(
"Environment:\n"
f"novelWriter Version: {__version__}\n"
@@ -152,13 +150,11 @@ class NWErrorMessage(QDialog):
f"Qt: {QT_VERSION_STR}, PyQt: {PYQT_VERSION_STR}\n"
f"enchant: {enchantVersion}\n\n"
f"{exType.__name__}:\n{exValue!s}\n\n"
- f"Traceback:\n{txtTrace}\n"
+ f"Traceback:\n{trace}\n"
)
except Exception:
self.msgBody.setPlainText("Failed to generate error report ...")
- return
-
##
# Slots
##
@@ -167,11 +163,10 @@ class NWErrorMessage(QDialog):
def _doClose(self) -> None:
"""Close the dialog."""
self.close()
- return
def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackType) -> None:
- """Function to catch unhandled global exceptions."""
+ """Catch unhandled global exceptions."""
from traceback import print_tb
from PyQt6.QtWidgets import QApplication
diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py
index b82a0997..14800a5c 100644
--- a/novelwriter/extensions/configlayout.py
+++ b/novelwriter/extensions/configlayout.py
@@ -24,7 +24,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
from PyQt6.QtGui import QColor, QFont, QPalette, QPixmap
@@ -39,7 +39,7 @@ DEFAULT_SCALE = 0.9
class NFixedPage(QFrame):
- """Extension: Fixed Page Widget
+ """Extension: Fixed Page Widget.
A custom widget that holds a layout. This is just a wrapper around a
QFrame that sets the same frame style as the other Page widgets.
@@ -49,23 +49,20 @@ class NFixedPage(QFrame):
super().__init__(parent=parent)
self.setFrameShadow(QFrame.Shadow.Sunken)
self.setFrameShape(QFrame.Shape.StyledPanel)
- return
def setCentralLayout(self, layout: QLayout) -> None:
"""Set a layout as the central object."""
self.setLayout(layout)
- return
def setCentralWidget(self, widget: QWidget) -> None:
"""Set a layout as the central object."""
layout = QHBoxLayout()
layout.addWidget(widget)
self.setLayout(layout)
- return
class NScrollablePage(QScrollArea):
- """Extension: Scrollable Page Widget
+ """Extension: Scrollable Page Widget.
A custom widget that holds a layout within a scrollable area.
"""
@@ -79,16 +76,14 @@ class NScrollablePage(QScrollArea):
self.setVerticalScrollBarPolicy(QtScrollAsNeeded)
self.setFrameShadow(QFrame.Shadow.Sunken)
self.setFrameShape(QFrame.Shape.StyledPanel)
- return
def setCentralLayout(self, layout: QLayout) -> None:
"""Set the central layout of the scroll page."""
self._widget.setLayout(layout)
- return
class NScrollableForm(QScrollArea):
- """Extension: Scrollable Form Widget
+ """Extension: Scrollable Form Widget.
A custom widget that creates a form within a scrollable area.
"""
@@ -117,8 +112,6 @@ class NScrollableForm(QScrollArea):
self.setFrameShadow(QFrame.Shadow.Sunken)
self.setFrameShape(QFrame.Shape.StyledPanel)
- return
-
##
# Properties
##
@@ -135,18 +128,15 @@ class NScrollableForm(QScrollArea):
"""Set the text color for the help text."""
self._helpCol = color
self._fontScale = scale
- return
def setHelpText(self, key: str, text: str) -> None:
"""Set the text for the help label."""
if qHelp := self._editable.get(key):
qHelp.setText(text)
- return
def setRowIndent(self, indent: int) -> None:
"""Set the indentation of each row."""
self._indent = max(indent, 0)
- return
##
# Methods
@@ -158,7 +148,6 @@ class NScrollableForm(QScrollArea):
yPos = self._sections[identifier].pos().y() - 8
if vBar := self.verticalScrollBar():
vBar.setValue(yPos)
- return
def scrollToLabel(self, label: str) -> None:
"""Scroll to the requested label."""
@@ -166,7 +155,6 @@ class NScrollableForm(QScrollArea):
yPos = self._index[label].pos().y() - 8
if vBar := self.verticalScrollBar():
vBar.setValue(yPos)
- return
def addGroupLabel(self, label: str, identifier: int | None = None) -> None:
"""Add a text label to separate groups of settings."""
@@ -178,7 +166,6 @@ class NScrollableForm(QScrollArea):
self._first = False
if identifier is not None:
self._sections[identifier] = qLabel
- return
def addRow(
self,
@@ -252,17 +239,14 @@ class NScrollableForm(QScrollArea):
self._index[label.strip()] = qWidget
qLabel.setAccessibleName(text)
- return
-
def finalise(self) -> None:
"""Finalise the layout when the form is built."""
self._layout.addSpacing(20)
self._layout.addStretch(1)
- return
class NColorLabel(QLabel):
- """Extension: A Coloured Label
+ """Extension: A Coloured Label.
A custom widget that draws a label in a specific colour, and
optionally at a specific size, and word wrapped.
@@ -298,21 +282,17 @@ class NColorLabel(QLabel):
self.setWordWrap(wrap)
self.setColorState(True)
- return
-
def setTextColors(self, *, color: QColor | None = None, faded: QColor | None = None) -> None:
"""Set or update the text colours."""
self._color = color or self._color
self._faded = faded or self._faded
self._refeshTextColor()
- return
def setColorState(self, state: bool) -> None:
"""Change the colour state."""
if self._state is not state:
self._state = state
self._refeshTextColor()
- return
def _refeshTextColor(self) -> None:
"""Refresh the colour of the text on the label."""
@@ -322,11 +302,10 @@ class NColorLabel(QLabel):
self._color if self._state else self._faded,
)
self.setPalette(palette)
- return
class NWrappedWidgetBox(QHBoxLayout):
- """Extension: A Text-Wrapped Widget Box
+ """Extension: A Text-Wrapped Widget Box.
A custom layout box where a widget is wrapped in text labels on
either side within a layout box. The widget is inserted at the {0}
@@ -341,4 +320,3 @@ class NWrappedWidgetBox(QHBoxLayout):
self.addWidget(widget)
if after:
self.addWidget(QLabel(after.lstrip()))
- return
diff --git a/novelwriter/extensions/eventfilters.py b/novelwriter/extensions/eventfilters.py
index 09366590..844aacd6 100644
--- a/novelwriter/extensions/eventfilters.py
+++ b/novelwriter/extensions/eventfilters.py
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
from typing import TYPE_CHECKING
@@ -34,7 +34,7 @@ if TYPE_CHECKING:
class WheelEventFilter(QObject):
- """Extensions: Wheel Event Filter
+ """Extensions: Wheel Event Filter.
An event filter that filters mouse wheel events for a widget and
forward them to the root widget. This solves the lack of mouse wheel
@@ -50,7 +50,6 @@ class WheelEventFilter(QObject):
super().__init__(parent=parent)
self._parent = parent
self._locked = False
- return
def eventFilter(self, obj: QObject, event: QEvent) -> bool:
"""Filter events of type QWheelEvent and forward them to the
@@ -69,6 +68,7 @@ class WheelEventFilter(QObject):
class StatusTipFilter(QObject):
+ """Filter: Remove StatusBar ToolTips."""
def eventFilter(self, obj: QObject, event: QEvent) -> bool:
"""Filter out status tip events on menus."""
diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py
index 9bea4c78..bb256ff1 100644
--- a/novelwriter/extensions/modified.py
+++ b/novelwriter/extensions/modified.py
@@ -24,7 +24,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
from typing import TYPE_CHECKING
@@ -47,6 +47,7 @@ if TYPE_CHECKING:
class NDialog(QDialog):
+ """Custom: Modified QDialog."""
def softDelete(self) -> None:
"""Since calling deleteLater is sometimes not safe from Python,
@@ -55,53 +56,54 @@ class NDialog(QDialog):
so that it gets garbage collected when it runs out of scope.
"""
self.setParent(None) # type: ignore
- return
@pyqtSlot()
def reject(self) -> None:
"""Overload the reject slot and also call close."""
super().reject()
self.close()
- return
class NToolDialog(NDialog):
+ """Custom: Modified QDialog for Tools."""
def __init__(self, parent: GuiMain) -> None:
super().__init__(parent=parent)
self.setModal(False)
if CONFIG.osDarwin:
self.setWindowFlag(Qt.WindowType.Tool)
- return
def activateDialog(self) -> None:
- """Helper function to activate dialog on various systems."""
+ """Activate dialog on various operating systems."""
self.show()
if CONFIG.osWindows:
self.activateWindow()
self.raise_()
QApplication.processEvents()
- return
class NNonBlockingDialog(NDialog):
+ """Custom: Modified Non-Blocking QDialog."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent=parent)
self.setModal(True)
- return
def activateDialog(self) -> None:
- """Helper function to activate dialog on various systems."""
+ """Activate dialog on various operating systems."""
self.show()
if CONFIG.osWindows:
self.activateWindow()
self.raise_()
QApplication.processEvents()
- return
class NTreeView(QTreeView):
+ """Custom: Modified QTreeView.
+
+ The main purpose is to provide the middleClicked signal that matches
+ clicked and doubleCLicked.
+ """
middleClicked = pyqtSignal(QModelIndex)
@@ -116,42 +118,62 @@ class NTreeView(QTreeView):
class NComboBox(QComboBox):
+ """Custom: Modified QComboBox.
- def __init__(self, parent: QWidget | None = None) -> None:
+ The main purpose is to provide a combo box that doesn't scroll when
+ the mousewheel is active on it while scrolling through a scrollable
+ window of many widgets.
+ """
+
+ def __init__(self, parent: QWidget | None = None, maxItems: int = 15) -> None:
super().__init__(parent=parent)
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
- return
+ self.setMaxVisibleItems(maxItems)
+
+ # The style sheet disables Fusion style pop-up mode on some platforms
+ # and allows for scrolling of long lists of items
+ self.setStyleSheet("QComboBox {combobox-popup: 0;}")
def wheelEvent(self, event: QWheelEvent) -> None:
+ """Only capture the mouse wheel if the widget has focus."""
if self.hasFocus():
super().wheelEvent(event)
else:
event.ignore()
- return
def setCurrentData(self, data: str | int | Enum, default: str | int | Enum) -> None:
"""Set the current index from data, with a fallback."""
idx = self.findData(data)
self.setCurrentIndex(self.findData(default) if idx < 0 else idx)
- return
class NSpinBox(QSpinBox):
+ """Custom: Modified QSpinBox.
+
+ The main purpose is to provide a spin box that doesn't scroll when
+ the mousewheel is active on it while scrolling through a scrollable
+ window of many widgets.
+ """
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent=parent)
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
- return
def wheelEvent(self, event: QWheelEvent) -> None:
+ """Only capture the mouse wheel if the widget has focus."""
if self.hasFocus():
super().wheelEvent(event)
else:
event.ignore()
- return
class NDoubleSpinBox(QDoubleSpinBox):
+ """Custom: Modified QDoubleSpinBox.
+
+ The main purpose is to provide a float spin box that doesn't scroll
+ when the mousewheel is active on it while scrolling through a
+ scrollable window of many widgets.
+ """
def __init__(
self,
@@ -168,17 +190,20 @@ class NDoubleSpinBox(QDoubleSpinBox):
self.setMaximum(maxVal)
self.setSingleStep(step)
self.setDecimals(prec)
- return
def wheelEvent(self, event: QWheelEvent) -> None:
+ """Only capture the mouse wheel if the widget has focus."""
if self.hasFocus():
super().wheelEvent(event)
else:
event.ignore()
- return
class NIconToolButton(QToolButton):
+ """Custom: Modified QToolButton.
+
+ A quicker way to create a tool button using the app theme.
+ """
def __init__(
self, parent: QWidget, iconSize: QSize,
@@ -190,15 +215,17 @@ class NIconToolButton(QToolButton):
self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
if icon:
self.setThemeIcon(icon, color)
- return
def setThemeIcon(self, iconKey: str, color: str | None = None) -> None:
"""Set an icon from the current theme."""
self.setIcon(SHARED.theme.getIcon(iconKey, color))
- return
class NIconToggleButton(QToolButton):
+ """Custom: Modified QToolButton.
+
+ A quicker way to create a toggle button using the app theme.
+ """
def __init__(self, parent: QWidget, iconSize: QSize, icon: str | None = None) -> None:
super().__init__(parent=parent)
@@ -209,16 +236,15 @@ class NIconToggleButton(QToolButton):
self.setStyleSheet("border: none; background: transparent;")
if icon:
self.setThemeIcon(icon)
- return
def setThemeIcon(self, iconKey: str) -> None:
"""Set an icon from the current theme."""
iconSize = self.iconSize()
self.setIcon(SHARED.theme.getToggleIcon(iconKey, (iconSize.width(), iconSize.height())))
- return
class NClickableLabel(QLabel):
+ """Custom: Clickable QLabel."""
mouseClicked = pyqtSignal()
diff --git a/novelwriter/extensions/novelselector.py b/novelwriter/extensions/novelselector.py
index ee585a0d..27d217c6 100644
--- a/novelwriter/extensions/novelselector.py
+++ b/novelwriter/extensions/novelselector.py
@@ -20,23 +20,29 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
+from typing import TYPE_CHECKING
+
from PyQt6.QtCore import pyqtSignal, pyqtSlot
from PyQt6.QtGui import QPalette
-from PyQt6.QtWidgets import QComboBox, QWidget
+from PyQt6.QtWidgets import QComboBox
from novelwriter import SHARED
from novelwriter.constants import nwLabels
from novelwriter.enum import nwItemClass
+if TYPE_CHECKING:
+ from PyQt6.QtWidgets import QWidget
+
logger = logging.getLogger(__name__)
class NovelSelector(QComboBox):
+ """Custom: Novel Root Folder Selector."""
novelSelectionChanged = pyqtSignal(str)
@@ -48,7 +54,6 @@ class NovelSelector(QComboBox):
self._listFormat = None
self.currentIndexChanged.connect(self._indexChanged)
self.updateTheme()
- return
##
# Properties
@@ -75,18 +80,15 @@ class NovelSelector(QComboBox):
self._blockSignal = blockSignal
self.setCurrentIndex(index)
self._blockSignal = False
- return
def setIncludeAll(self, value: bool) -> None:
"""Set flag to add an "All Novel Folders" option."""
self._includeAll = value
- return
def setListFormat(self, value: str | None) -> None:
"""Set a format string for the list entries."""
if value is None or "{0}" in value:
self._listFormat = value
- return
def updateTheme(self) -> None:
"""Update theme colours."""
@@ -94,7 +96,6 @@ class NovelSelector(QComboBox):
palette.setBrush(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, palette.text())
self.setPalette(palette)
self.refreshNovelList()
- return
##
# Public Slots
@@ -128,8 +129,6 @@ class NovelSelector(QComboBox):
self.setEnabled(self.count() > 1)
self._blockSignal = False
- return
-
##
# Private Slots
##
@@ -139,4 +138,3 @@ class NovelSelector(QComboBox):
"""Re-emit the change of selection signal, unless blocked."""
if not self._blockSignal:
self.novelSelectionChanged.emit(self.currentData())
- return
diff --git a/novelwriter/extensions/pagedsidebar.py b/novelwriter/extensions/pagedsidebar.py
index b1745816..fd332495 100644
--- a/novelwriter/extensions/pagedsidebar.py
+++ b/novelwriter/extensions/pagedsidebar.py
@@ -22,7 +22,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
from PyQt6.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot
@@ -39,7 +39,7 @@ from novelwriter.types import (
class NPagedSideBar(QToolBar):
- """Extensions: Paged Side Bar
+ """Extensions: Paged Side Bar.
A side bar widget that holds buttons that mimic tabs. It is designed
to be used in combination with a QStackedWidget for options panels.
@@ -65,8 +65,6 @@ class NPagedSideBar(QToolBar):
stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
self._stretchAction = self.addWidget(stretch)
- return
-
def button(self, buttonId: int) -> _PagedToolButton:
"""Return a specific button."""
return self._buttons[buttonId]
@@ -74,14 +72,12 @@ class NPagedSideBar(QToolBar):
def setLabelColor(self, color: QColor) -> None:
"""Set the text color for the labels."""
self._labelCol = color
- return
def addLabel(self, text: str) -> None:
"""Add a new label to the toolbar."""
label = _NPagedToolLabel(self, self._labelCol)
label.setText(text)
self.insertWidget(self._stretchAction, label)
- return
def addButton(self, text: str, buttonId: int = -1) -> None:
"""Add a new button to the toolbar."""
@@ -90,13 +86,11 @@ class NPagedSideBar(QToolBar):
self.insertWidget(self._stretchAction, button)
self._group.addButton(button, id=buttonId)
self._buttons[buttonId] = button
- return
def setSelected(self, buttonId: int) -> None:
"""Set the selected button."""
if button := self._group.button(buttonId):
button.setChecked(True)
- return
##
# Private Slots
@@ -104,11 +98,10 @@ class NPagedSideBar(QToolBar):
@pyqtSlot("QAbstractButton*")
def _buttonClicked(self, button: QAbstractButton) -> None:
- """A button was clicked in the group, emit its id."""
+ """Handle a button click in the group and emit its id."""
buttonId = self._group.id(button)
if buttonId != -1:
self.buttonClicked.emit(buttonId)
- return
class _PagedToolButton(QToolButton):
@@ -127,8 +120,6 @@ class _PagedToolButton(QToolButton):
self._aH = 2*fH//7
self.setFixedHeight(self._bH)
- return
-
def sizeHint(self) -> QSize:
"""Return a size hint that includes the arrow."""
return super().sizeHint() + QSize(4*self._aH, 0)
@@ -156,7 +147,7 @@ class _PagedToolButton(QToolButton):
if self.isChecked():
painter.setBrush(palette.highlight())
- painter.setOpacity(0.35)
+ painter.setOpacity(0.8)
painter.drawRoundedRect(0, 0, width, height, 4, 4)
textCol = palette.highlightedText().color()
else:
@@ -180,8 +171,6 @@ class _PagedToolButton(QToolButton):
]))
painter.end()
- return
-
class _NPagedToolLabel(QLabel):
@@ -199,8 +188,6 @@ class _NPagedToolLabel(QLabel):
self._textCol = textColor or self.palette().text().color()
- return
-
def paintEvent(self, event: QPaintEvent) -> None:
"""Overload the paint event to draw a simple, left aligned text
label that matches the button style.
@@ -215,5 +202,3 @@ class _NPagedToolLabel(QLabel):
painter.setOpacity(1.0)
painter.drawText(QRectF(4, self._tM, tW, tH), QtAlignLeft, self.text())
painter.end()
-
- return
diff --git a/novelwriter/extensions/progressbars.py b/novelwriter/extensions/progressbars.py
index 0c92564e..8c0b1d1d 100644
--- a/novelwriter/extensions/progressbars.py
+++ b/novelwriter/extensions/progressbars.py
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
from math import ceil
@@ -37,7 +37,7 @@ from novelwriter.types import (
class NProgressCircle(QProgressBar):
- """Extension: Circular Progress Widget
+ """Extension: Circular Progress Widget.
A custom widget that paints a circular progress indicator instead of
a straight bar. It is also possible to set custom text for iṫ.
@@ -64,7 +64,6 @@ class NProgressCircle(QProgressBar):
self.setSizePolicy(QtSizeFixed, QtSizeFixed)
self.setFixedWidth(size)
self.setFixedHeight(size)
- return
def setColors(
self, back: QColor | None = None, track: QColor | None = None,
@@ -80,16 +79,14 @@ class NProgressCircle(QProgressBar):
self._bPen = QPen(QBrush(track), self._point, QtSolidLine, QtRoundCap)
if isinstance(text, QColor):
self._tColor = text
- return
def setCentreText(self, text: str | None) -> None:
"""Replace the progress text with a custom string."""
self._text = text
self.setValue(self.value()) # Triggers a redraw
- return
def paintEvent(self, event: QPaintEvent) -> None:
- """Custom painter for the progress bar."""
+ """Paint the progress bar."""
progress = 100.0*self.value()/self.maximum()
angle = ceil(16*3.6*progress)
painter = QPainter(self)
@@ -103,21 +100,19 @@ class NProgressCircle(QProgressBar):
painter.drawArc(self._cRect, 90*16, -angle)
painter.setPen(self._tColor)
painter.drawText(self._cRect, QtAlignCenter, self._text or f"{progress:.1f} %")
- return
class NProgressSimple(QProgressBar):
- """Extension: Simple Progress Widget
+ """Extension: Simple Progress Widget.
A custom widget that paints a plain bar with no other styling.
"""
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
- return
def paintEvent(self, event: QPaintEvent) -> None:
- """Custom painter for the progress bar."""
+ """Paint the progress bar."""
if (value := self.value()) > 0:
progress = ceil(self.width()*float(value)/self.maximum())
painter = QPainter(self)
@@ -125,4 +120,3 @@ class NProgressSimple(QProgressBar):
painter.setPen(self.palette().highlight().color())
painter.setBrush(self.palette().highlight())
painter.drawRect(0, 0, progress, self.height())
- return
diff --git a/novelwriter/extensions/statusled.py b/novelwriter/extensions/statusled.py
index 5547a664..9152abb2 100644
--- a/novelwriter/extensions/statusled.py
+++ b/novelwriter/extensions/statusled.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -34,6 +34,7 @@ logger = logging.getLogger(__name__)
class StatusLED(QAbstractButton):
+ """Custom: LED Style Indicator."""
__slots__ = ("_color", "_negative", "_neutral", "_postitve", "_state")
@@ -46,7 +47,6 @@ class StatusLED(QAbstractButton):
self._state = None
self.setFixedWidth(sW)
self.setFixedHeight(sH)
- return
@property
def state(self) -> bool | None:
@@ -59,7 +59,6 @@ class StatusLED(QAbstractButton):
self._postitve = positive
self._negative = negative
self.setState(self._state)
- return
def setState(self, state: bool | None) -> None:
"""Set the colour state."""
@@ -71,7 +70,6 @@ class StatusLED(QAbstractButton):
self._color = self._neutral
self._state = state
self.update()
- return
def paintEvent(self, event: QPaintEvent) -> None:
"""Draw the LED."""
@@ -82,4 +80,3 @@ class StatusLED(QAbstractButton):
painter.setOpacity(1.0)
painter.drawEllipse(1, 1, self.width() - 2, self.height() - 2)
painter.end()
- return
diff --git a/novelwriter/extensions/switch.py b/novelwriter/extensions/switch.py
index 4699d50a..cc0d43a9 100644
--- a/novelwriter/extensions/switch.py
+++ b/novelwriter/extensions/switch.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
from PyQt6.QtCore import QPropertyAnimation, Qt, pyqtProperty, pyqtSlot # pyright: ignore
@@ -32,8 +32,9 @@ from novelwriter.types import QtNoPen, QtPaintAntiAlias, QtSizeFixed
class NSwitch(QAbstractButton):
+ """Custom: Toggle Switch."""
- __slots__ = ("_offset", "_rH", "_rR", "_xH", "_xR", "_xW")
+ __slots__ = ("_cOff", "_cOn", "_offset", "_rH", "_rR", "_xH", "_xR", "_xW")
def __init__(self, parent: QWidget, height: int = 0) -> None:
super().__init__(parent=parent)
@@ -44,6 +45,9 @@ class NSwitch(QAbstractButton):
self._rH = self._xH - 4
self._rR = self._xR - 2
+ self._cOn = SHARED.theme.accentCol
+ self._cOff = self.palette().alternateBase()
+
self.setCheckable(True)
self.setSizePolicy(QtSizeFixed, QtSizeFixed)
self.setFixedWidth(self._xW)
@@ -52,8 +56,6 @@ class NSwitch(QAbstractButton):
self.clicked.connect(self._onClick)
- return
-
##
# Properties
##
@@ -66,7 +68,6 @@ class NSwitch(QAbstractButton):
def offset(self, offset: int) -> None:
self._offset = offset
self.update()
- return
##
# Getters and Setters
@@ -76,7 +77,6 @@ class NSwitch(QAbstractButton):
"""Overload setChecked to also alter the offset."""
super().setChecked(checked)
self._offset = (self._xW - self._xR) if checked else self._xR
- return
##
# Events
@@ -86,7 +86,6 @@ class NSwitch(QAbstractButton):
"""Overload resize to ensure correct offset."""
super().resizeEvent(event)
self._offset = (self._xW - self._xR) if self.isChecked() else self._xR
- return
def paintEvent(self, event: QPaintEvent) -> None:
"""Drawing the switch itself."""
@@ -97,7 +96,7 @@ class NSwitch(QAbstractButton):
painter.setOpacity(1.0 if self.isEnabled() else 0.5)
painter.setPen(palette.highlight().color() if self.hasFocus() else palette.mid().color())
- painter.setBrush(palette.highlight() if self.isChecked() else palette.alternateBase())
+ painter.setBrush(self._cOn if self.isChecked() else self._cOff)
painter.drawRoundedRect(0, 0, self._xW, self._xH, self._xR, self._xR)
painter.setPen(QtNoPen)
@@ -106,13 +105,10 @@ class NSwitch(QAbstractButton):
painter.end()
- return
-
def enterEvent(self, event: QEnterEvent) -> None:
"""Change the cursor when hovering the button."""
self.setCursor(Qt.CursorShape.PointingHandCursor)
super().enterEvent(event)
- return
@pyqtSlot(bool)
def _onClick(self, checked: bool) -> None:
@@ -122,4 +118,3 @@ class NSwitch(QAbstractButton):
anim.setStartValue(self._offset)
anim.setEndValue((self._xW - self._xR) if checked else self._xR)
anim.start()
- return
diff --git a/novelwriter/extensions/switchbox.py b/novelwriter/extensions/switchbox.py
index e4732f97..e287cf25 100644
--- a/novelwriter/extensions/switchbox.py
+++ b/novelwriter/extensions/switchbox.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
from typing import TYPE_CHECKING
@@ -39,7 +39,7 @@ if TYPE_CHECKING:
class NSwitchBox(QScrollArea):
- """Extension: Switch Box Widget
+ """Extension: Switch Box Widget.
A widget that can hold a list of switches with labels and optional
icons. The switch toggles emits a common signal with a switch key.
@@ -55,7 +55,6 @@ class NSwitchBox(QScrollArea):
self._sIcon = baseSize
self._widgets = []
self.clear()
- return
def clear(self) -> None:
"""Rebuild the content of the core widget."""
@@ -72,8 +71,6 @@ class NSwitchBox(QScrollArea):
self.setWidgetResizable(True)
self.setWidget(self._widget)
- return
-
def addLabel(self, text: str) -> None:
"""Add a header label to the content box."""
label = QLabel(text, self)
@@ -83,7 +80,6 @@ class NSwitchBox(QScrollArea):
self._content.addWidget(label, self._index, 0, 1, 3, QtAlignLeft)
self._widgets.append(label)
self._bumpIndex()
- return
def addItem(self, qIcon: QIcon, text: str, identifier: str, default: bool = False) -> None:
"""Add an item to the content box."""
@@ -104,8 +100,6 @@ class NSwitchBox(QScrollArea):
self._widgets.append(switch)
self._bumpIndex()
- return
-
def addSeparator(self) -> None:
"""Add a blank entry in the content box."""
spacer = QWidget(self)
@@ -113,7 +107,6 @@ class NSwitchBox(QScrollArea):
self._content.addWidget(spacer, self._index, 0, 1, 3, QtAlignLeft)
self._widgets.append(spacer)
self._bumpIndex()
- return
##
# Internal Functions
@@ -122,7 +115,6 @@ class NSwitchBox(QScrollArea):
def _emitSwitchSignal(self, identifier: str, state: bool) -> None:
"""Emit a signal for a switch toggle."""
self.switchToggled.emit(identifier, state)
- return
def _bumpIndex(self) -> None:
"""Increase the index counter and make sure only the last
@@ -131,4 +123,3 @@ class NSwitchBox(QScrollArea):
self._content.setRowStretch(self._index, 0)
self._content.setRowStretch(self._index + 1, 1)
self._index += 1
- return
diff --git a/novelwriter/extensions/versioninfo.py b/novelwriter/extensions/versioninfo.py
index 2f13afaa..d92a71c8 100644
--- a/novelwriter/extensions/versioninfo.py
+++ b/novelwriter/extensions/versioninfo.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import json
@@ -45,6 +45,11 @@ API_URL = "https://api.github.com/repos/vkbo/novelwriter/releases/latest"
class VersionInfoWidget(QWidget):
+ """Custom: version Info Label.
+
+ A custom widget that will show a clickable area for contacting
+ GitHub and pulling the latest release version info.
+ """
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -75,8 +80,6 @@ class VersionInfoWidget(QWidget):
self.setLayout(self._layout)
- return
-
##
# Private Slots
##
@@ -93,7 +96,6 @@ class VersionInfoWidget(QWidget):
lookup = _Retriever()
lookup.signals.dataReady.connect(self._updateReleaseInfo)
SHARED.runInThreadPool(lookup)
- return
##
# Private Slots
@@ -109,7 +111,6 @@ class VersionInfoWidget(QWidget):
))
else:
self._lblRelease.setText(self._trLatest.format(reason or self.tr("Failed")))
- return
class _Retriever(QRunnable):
@@ -117,7 +118,6 @@ class _Retriever(QRunnable):
def __init__(self) -> None:
super().__init__()
self.signals = _RetrieverSignal()
- return
@pyqtSlot()
def run(self) -> None:
@@ -140,7 +140,6 @@ class _Retriever(QRunnable):
except Exception as e:
logger.error("Failed to retrieve release info")
self.signals.dataReady.emit("", str(e))
- return
class _RetrieverSignal(QObject):
diff --git a/novelwriter/formats/shared.py b/novelwriter/formats/shared.py
index 8a2ab5e6..f6468125 100644
--- a/novelwriter/formats/shared.py
+++ b/novelwriter/formats/shared.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import re
@@ -29,7 +29,15 @@ from enum import Flag, IntEnum
from PyQt6.QtGui import QColor
-ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""}
+ESCAPES = {
+ r"\*": "*",
+ r"\~": "~",
+ r"\=": "=",
+ r"\_": "_",
+ r"\[": "[",
+ r"\]": "]",
+ r"\ ": "",
+}
RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL)
diff --git a/novelwriter/formats/todocx.py b/novelwriter/formats/todocx.py
index 366191ef..53d8884c 100644
--- a/novelwriter/formats/todocx.py
+++ b/novelwriter/formats/todocx.py
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import logging
@@ -38,7 +38,7 @@ from novelwriter import __version__
from novelwriter.common import firstFloat, xmlElement, xmlSubElem
from novelwriter.constants import nwHeadFmt, nwStyles
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
-from novelwriter.formats.tokenizer import Tokenizer
+from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer
from novelwriter.types import QtHexRgb
if TYPE_CHECKING:
@@ -51,7 +51,7 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
# RegEx
-RX_TEXT = re.compile(r"([\n\t])", re.UNICODE)
+RX_TEXT = re.compile(r"([\n\t])")
# Types and Relationships
OOXML_SCM = "http://schemas.openxmlformats.org"
@@ -100,7 +100,7 @@ def _wText(parent: ET.Element, text: str) -> ET.Element:
def _mmToSz(value: float) -> int:
- """Convert millimetres to internal margin size units"""
+ """Convert millimetres to internal margin size units."""
return int(value*20.0*72.0/25.4)
@@ -143,6 +143,7 @@ S_FNOTE = "FootnoteText"
class DocXXmlRel(NamedTuple):
+ """DocX XML Rel Data."""
rId: str
relType: str
@@ -150,6 +151,7 @@ class DocXXmlRel(NamedTuple):
class DocXXmlFile(NamedTuple):
+ """DocX XML File Data."""
xml: ET.Element
path: str
@@ -157,6 +159,7 @@ class DocXXmlFile(NamedTuple):
class DocXParStyle(NamedTuple):
+ """DocX XML Paragraph Style Data."""
name: str
styleId: str
@@ -176,7 +179,7 @@ class DocXParStyle(NamedTuple):
class ToDocX(Tokenizer):
- """Core: DocX Document Writer
+ """Core: DocX Document Writer.
Extend the Tokenizer class to writer DocX Document files.
"""
@@ -202,8 +205,6 @@ class ToDocX(Tokenizer):
self._usedNotes: dict[str, int] = {}
self._usedFields: list[tuple[ET.Element, str]] = []
- return
-
##
# Setters
##
@@ -214,25 +215,22 @@ class ToDocX(Tokenizer):
"""Set the document page size and margins in millimetres."""
self._pageSize = QSize(_mmToSz(width), _mmToSz(height))
self._pageMargins = QMargins(_mmToSz(left), _mmToSz(top), _mmToSz(right), _mmToSz(bottom))
- return
def setHeaderFormat(self, value: str, offset: int) -> None:
"""Set the document header format."""
self._headerFormat = value.strip()
self._pageOffset = offset
- return
##
# Class Methods
##
def initDocument(self) -> None:
- """Initialises the DocX document structure."""
+ """Initialise the DocX document structure."""
super().initDocument()
self._fontFamily = self._textFont.family()
self._fontSize = self._textFont.pointSizeF()
self._generateStyles()
- return
def doConvert(self) -> None:
"""Convert the list of text tokens into XML elements."""
@@ -296,14 +294,12 @@ class ToDocX(Tokenizer):
elif tType == BlockTyp.SKIP:
self._processFragments(par, S_NORM, "")
- elif tType == BlockTyp.COMMENT:
+ elif tType in COMMENT_BLOCKS:
self._processFragments(par, S_META, tText, tFormat)
elif tType == BlockTyp.KEYWORD:
self._processFragments(par, S_META, tText, tFormat)
- return
-
def closeDocument(self) -> None:
"""Generate all the XML."""
self._coreXml()
@@ -322,8 +318,6 @@ class ToDocX(Tokenizer):
if self._usedNotes:
self._footnotesXml()
- return
-
def saveDocument(self, path: Path) -> None:
"""Save the data to a .docx file."""
# Content Lists
@@ -373,8 +367,6 @@ class ToDocX(Tokenizer):
xmlToZip(f"{rel.path}/{name}", rel.xml, outZip)
xmlToZip("[Content_Types].xml", dTypes, outZip)
- return
-
##
# Internal Functions
##
@@ -454,8 +446,6 @@ class ToDocX(Tokenizer):
if temp := text[fStart:]:
par.addContent(self._textRunToXml(temp, xFmt, fClass, fLink))
- return
-
def _textRunToXml(self, text: str | None, fmt: int, fClass: str, fLink: str) -> ET.Element:
"""Encode the text run into XML."""
xR = xmlElement(_wTag("r"))
@@ -668,8 +658,6 @@ class ToDocX(Tokenizer):
for style in styles:
self._styles[style.styleId] = style
- return
-
def _nextRelId(self) -> str:
"""Generate the next unique rId."""
return f"rId{len(self._rels) + 1}"
@@ -1054,6 +1042,10 @@ class ToDocX(Tokenizer):
class DocXParagraph:
+ """DocX Text Paragraph.
+
+ This class holds a single paragraph of a DocX document.
+ """
__slots__ = (
"_bottomMargin", "_breakAfter", "_breakBefore", "_content",
@@ -1073,7 +1065,6 @@ class DocXParagraph:
self._breakBefore = False
self._breakAfter = False
self._footnoteRef = False
- return
##
# Properties
@@ -1091,53 +1082,43 @@ class DocXParagraph:
def setStyle(self, style: DocXParStyle | None) -> None:
"""Set the paragraph style."""
self._style = style
- return
def setAlignment(self, value: str) -> None:
"""Set paragraph alignment."""
if value in ("left", "center", "right", "both"):
self._textAlign = value
- return
def setMarginTop(self, value: float) -> None:
"""Set margin above in pt."""
self._topMargin = value
- return
def setMarginBottom(self, value: float) -> None:
"""Set margin below in pt."""
self._bottomMargin = value
- return
def setMarginLeft(self, value: float) -> None:
"""Set margin left in pt."""
self._leftMargin = value
- return
def setMarginRight(self, value: float) -> None:
"""Set margin right in pt."""
self._rightMargin = value
- return
def setIndentFirst(self, state: bool) -> None:
"""Set first line indent."""
self._indentFirst = state
- return
def setPageBreakBefore(self, state: bool) -> None:
"""Set page break before flag."""
self._breakBefore = state
- return
def setPageBreakAfter(self, state: bool) -> None:
"""Set page break after flag."""
self._breakAfter = state
- return
def setIsFootnote(self, state: bool) -> None:
"""Set is footnote flag."""
self._footnoteRef = state
- return
##
# Methods
@@ -1146,10 +1127,9 @@ class DocXParagraph:
def addContent(self, run: ET.Element) -> None:
"""Add a run segment to the paragraph."""
self._content.append(run)
- return
def toXml(self, body: ET.Element) -> None:
- """Called after all content is set."""
+ """Generate the XML. Call after all content is set."""
if style := self._style:
xP = xmlSubElem(body, _wTag("p"))
@@ -1191,5 +1171,3 @@ class DocXParagraph:
if self._breakAfter:
xR = xmlSubElem(xP, _wTag("r"))
xmlSubElem(xR, _wTag("br"), attrib={_wTag("type"): "page"})
-
- return
diff --git a/novelwriter/formats/tohtml.py b/novelwriter/formats/tohtml.py
index c7bf7b17..db9b0061 100644
--- a/novelwriter/formats/tohtml.py
+++ b/novelwriter/formats/tohtml.py
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
import json
@@ -32,7 +32,7 @@ from typing import TYPE_CHECKING
from novelwriter.common import formatTimeStamp
from novelwriter.constants import nwHtmlUnicode, nwStyles
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
-from novelwriter.formats.tokenizer import Tokenizer
+from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS, QtHexRgb
if TYPE_CHECKING:
@@ -77,7 +77,7 @@ HTML_NONE = (0, "")
class ToHtml(Tokenizer):
- """Core: HTML Document Writer
+ """Core: HTML Document Writer.
Extend the Tokenizer class to writer HTML output. This class is
also used by the Document Viewer, and Manuscript Build Preview.
@@ -90,7 +90,6 @@ class ToHtml(Tokenizer):
self._usedNotes: dict[str, int] = {}
self._usedFields: list[tuple[int, str]] = []
self.setReplaceUnicode(False)
- return
##
# Setters
@@ -101,7 +100,6 @@ class ToHtml(Tokenizer):
class tags.
"""
self._cssStyles = cssStyles
- return
def setReplaceUnicode(self, doReplace: bool) -> None:
"""Set the translation map to either minimal or full unicode for
@@ -114,7 +112,6 @@ class ToHtml(Tokenizer):
if doReplace:
# Extend to all relevant Unicode characters
self._trMap.update(str.maketrans(nwHtmlUnicode.U_TO_H))
- return
##
# Class Methods
@@ -130,7 +127,6 @@ class ToHtml(Tokenizer):
"""
super().doPreProcessing()
self._text = self._text.translate(self._trMap)
- return
def doConvert(self) -> None:
"""Convert the list of text tokens into an HTML document."""
@@ -159,34 +155,33 @@ class ToHtml(Tokenizer):
# If we don't have formatting, we can do a plain replace
tText = tText.replace("<", "<").replace(">", ">")
- # Styles
+ # Inline Styles
aStyle = []
- if self._cssStyles:
- if tStyle & BlockFmt.LEFT:
- aStyle.append("text-align: left;")
- elif tStyle & BlockFmt.RIGHT:
- aStyle.append("text-align: right;")
- elif tStyle & BlockFmt.CENTRE:
- aStyle.append("text-align: center;")
- elif tStyle & BlockFmt.JUSTIFY:
- aStyle.append("text-align: justify;")
+ if tStyle & BlockFmt.LEFT:
+ aStyle.append("text-align: left;")
+ elif tStyle & BlockFmt.RIGHT:
+ aStyle.append("text-align: right;")
+ elif tStyle & BlockFmt.CENTRE:
+ aStyle.append("text-align: center;")
+ elif tStyle & BlockFmt.JUSTIFY:
+ aStyle.append("text-align: justify;")
- if tStyle & BlockFmt.PBB:
- aStyle.append("page-break-before: always;")
- if tStyle & BlockFmt.PBA:
- aStyle.append("page-break-after: always;")
+ if tStyle & BlockFmt.PBB:
+ aStyle.append("page-break-before: always;")
+ if tStyle & BlockFmt.PBA:
+ aStyle.append("page-break-after: always;")
- if tStyle & BlockFmt.Z_BTM:
- aStyle.append("margin-bottom: 0;")
- if tStyle & BlockFmt.Z_TOP:
- aStyle.append("margin-top: 0;")
+ if tStyle & BlockFmt.Z_BTM:
+ aStyle.append("margin-bottom: 0;")
+ if tStyle & BlockFmt.Z_TOP:
+ aStyle.append("margin-top: 0;")
- if tStyle & BlockFmt.IND_L:
- aStyle.append(f"margin-left: {self._blockIndent:.2f}em;")
- if tStyle & BlockFmt.IND_R:
- aStyle.append(f"margin-right: {self._blockIndent:.2f}em;")
- if tStyle & BlockFmt.IND_T:
- aStyle.append(f"text-indent: {self._firstWidth:.2f}em;")
+ if tStyle & BlockFmt.IND_L:
+ aStyle.append(f"margin-left: {self._blockIndent:.2f}em;")
+ if tStyle & BlockFmt.IND_R:
+ aStyle.append(f"margin-right: {self._blockIndent:.2f}em;")
+ if tStyle & BlockFmt.IND_T:
+ aStyle.append(f"text-indent: {self._firstWidth:.2f}em;")
if aStyle:
stVals = " ".join(aStyle)
@@ -229,7 +224,7 @@ class ToHtml(Tokenizer):
elif tType == BlockTyp.SKIP:
lines.append(f"