diff --git a/CREDITS.md b/CREDITS.md index 9ac3b34a..4c08b186 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -55,19 +55,33 @@ 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 following icon themes are bundles with novelWriter: * **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) + +## Colour Themes + +Some colour themes included with novelWriter were adapted from the following sources: + +* **Dracula** theme by Zeno Rocha +* **Espresso** theme by Sainnhe Park and Antoine Cotten +* **Noctis** themes by Liviu Schera, and contributors +* **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: + +* **Blue Streak** themes by Veronica Berglyd Olsen +* **Cyberpunk Night** theme by Anders Lemvigh +* **LCARS** theme by Veronica Berglyd Olsen ## Fonts 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..dea685b5 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,130 @@ 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 + description = The novelWriter standard light theme + 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 = blue + note = yellow:D125 + hidden = faded + shortcode = green + keyword = red + tag = green + value = blue + optional = green + spellcheckline = red + errorline = green + replacetag = green + modifier = green + 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 +172,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`` and ``licenseurl`` settings. The ``description`` and ``author`` fields + are 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 = + icon:alert_info = + icon:alert_question = + icon:alert_warn = + icon:cls_archive = + icon:cls_character = + icon:cls_custom = + icon:cls_entity = + icon:cls_none = + icon:cls_novel = + icon:cls_object = + icon:cls_plot = + icon:cls_template = + icon:cls_timeline = + icon:cls_trash = + icon:cls_world = -In the Main section, you must define at least the ``name`` setting. The Syntax colour values are -RGB(A) numbers of the format ``r, g, b, a`` where each is an integer from ``0`` to ``255``. The -fourth value is the alpha channel, which can be omitted. +The icon keys are associated with icon placement locations inside novelWriter, and the template for +them is defined in the script that generates the default icon themes. -Omitted syntax colours default to black, except ``background`` which defaults to white, and -``texthighlight`` which defaults to white with half transparency. +The script can be found under +`utils/icon_themes.py `__ +in the source code. -.. versionadded:: 2.2 - The ``shortcode`` syntax colour entry was added. +This file includes all the code needed to generate the themes that are included in novelWriter. The +icon keys are mapped to icon keys from the specific themes in JSON files in the ``icon_themes`` +folder next to the script. This is the recommended way to generate these themes. Doing it manually +is not advisable. -.. versionadded:: 2.3 - The ``optional`` syntax colour entry was added. -.. versionadded:: 2.4 - The ``texthighlight`` syntax colour entry was added. +Icon Value Format +----------------- -.. versionadded:: 2.5 - The ``dialog``, ``altdialog``, ``note`` and ``tag`` syntax colour entries were added. - ``straightquotes``, ``doublequotes`` and ``singlequotes`` were removed. +As can be seen from the example, an icon is defined in the ``icon`` section with a key and an +in-line SVG XML block. The XML must fit on one line and obey the following rules: + +#. It must be single colour, that is, the fill colour attribute must be able to colourise the + entire icon. +#. The fill colour attribute *must* be defined and must be set to: ``fill="#000000"``. This value + is replaced by the relevant theme colour when the icon is processed in novelWriter. + +.. versionadded:: 2.7 + The icon theme files were added. Previously, icons were stored as individual SVG files with a + config file mapping the file names to the internal icon keys. diff --git a/docs/source/more/dictionaries.rst b/docs/source/more/dictionaries.rst new file mode 100644 index 00000000..1ca896d5 --- /dev/null +++ b/docs/source/more/dictionaries.rst @@ -0,0 +1,55 @@ +.. _docs_more_dictionaries: + +************************ +Spell Check Dictionaries +************************ + +.. _Enchant: https://rrthomas.github.io/enchant/ +.. _Free Desktop: https://cgit.freedesktop.org/libreoffice/dictionaries/tree/ + +Spell checking is provided by the Enchant_ library. 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. diff --git a/docs/source/user_interface/editor_viewer.rst b/docs/source/user_interface/editor_viewer.rst index 841be0b0..ee0c1eb3 100644 --- a/docs/source/user_interface/editor_viewer.rst +++ b/docs/source/user_interface/editor_viewer.rst @@ -55,7 +55,7 @@ edited from the **Project Word List** tool available from the **Tools** menu. .. note:: Generally, spell checking dictionaries are collected from your operating system, but on Windows - they are not. See :ref:`docs_more_custom_dict` for how to add spell checking dictionaries on + they are not. See :ref:`docs_more_dictionaries` for how to add spell checking dictionaries on Windows. diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index df5cafa8..3f2bc134 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -49,9 +49,9 @@ __license__ = "GPLv3" __author__ = "Veronica Berglyd Olsen" __maintainer__ = "Veronica Berglyd Olsen" __email__ = "code@vkbo.net" -__version__ = "2.7.3" -__hexversion__ = "0x020703f0" -__date__ = "2025-07-07" +__version__ = "2.8a1" +__hexversion__ = "0x020800a1" +__date__ = "2025-06-13" __status__ = "Stable" __domain__ = "novelwriter.io" @@ -296,7 +296,8 @@ def _createApp(style: str) -> QApplication: """Create the app. This is done in a function to make it easier to block app creation during testing. """ - app = QApplication([CONFIG.appName, f"-style={style}"]) + app = QApplication([CONFIG.appName]) + app.setStyle(style) app.setApplicationName(CONFIG.appName) app.setApplicationVersion(__version__) app.setOrganizationDomain(__domain__) diff --git a/novelwriter/assets/icons/font_awesome.icons b/novelwriter/assets/icons/font_awesome.icons index 550fd857..75204942 100644 --- a/novelwriter/assets/icons/font_awesome.icons +++ b/novelwriter/assets/icons/font_awesome.icons @@ -56,6 +56,9 @@ icon:sb_outline = icon:sb_search = icon:sb_stats = +icon:theme_light = +icon:theme_dark = +icon:theme_auto = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/material_filled_bold.icons b/novelwriter/assets/icons/material_filled_bold.icons index bc902698..584a04bb 100644 --- a/novelwriter/assets/icons/material_filled_bold.icons +++ b/novelwriter/assets/icons/material_filled_bold.icons @@ -56,6 +56,9 @@ icon:sb_outline = icon:sb_search = icon:sb_stats = +icon:theme_light = +icon:theme_dark = +icon:theme_auto = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/material_filled_normal.icons b/novelwriter/assets/icons/material_filled_normal.icons index ce666d14..a6d7933e 100644 --- a/novelwriter/assets/icons/material_filled_normal.icons +++ b/novelwriter/assets/icons/material_filled_normal.icons @@ -56,6 +56,9 @@ icon:sb_outline = icon:sb_search = icon:sb_stats = +icon:theme_light = +icon:theme_dark = +icon:theme_auto = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/material_filled_thin.icons b/novelwriter/assets/icons/material_filled_thin.icons index 70c4f364..a405ea3e 100644 --- a/novelwriter/assets/icons/material_filled_thin.icons +++ b/novelwriter/assets/icons/material_filled_thin.icons @@ -56,6 +56,9 @@ icon:sb_outline = icon:sb_search = icon:sb_stats = +icon:theme_light = +icon:theme_dark = +icon:theme_auto = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/material_rounded_bold.icons b/novelwriter/assets/icons/material_rounded_bold.icons index 08564be2..02d83821 100644 --- a/novelwriter/assets/icons/material_rounded_bold.icons +++ b/novelwriter/assets/icons/material_rounded_bold.icons @@ -56,6 +56,9 @@ icon:sb_outline = icon:sb_search = icon:sb_stats = +icon:theme_light = +icon:theme_dark = +icon:theme_auto = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/material_rounded_normal.icons b/novelwriter/assets/icons/material_rounded_normal.icons index 3da2739e..06887aa7 100644 --- a/novelwriter/assets/icons/material_rounded_normal.icons +++ b/novelwriter/assets/icons/material_rounded_normal.icons @@ -56,6 +56,9 @@ icon:sb_outline = icon:sb_search = icon:sb_stats = +icon:theme_light = +icon:theme_dark = +icon:theme_auto = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/material_rounded_thin.icons b/novelwriter/assets/icons/material_rounded_thin.icons index 61fcd5fa..1002fcbf 100644 --- a/novelwriter/assets/icons/material_rounded_thin.icons +++ b/novelwriter/assets/icons/material_rounded_thin.icons @@ -56,6 +56,9 @@ icon:sb_outline = icon:sb_search = icon:sb_stats = +icon:theme_light = +icon:theme_dark = +icon:theme_auto = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/remix_filled.icons b/novelwriter/assets/icons/remix_filled.icons index b2305187..51e73a78 100644 --- a/novelwriter/assets/icons/remix_filled.icons +++ b/novelwriter/assets/icons/remix_filled.icons @@ -56,6 +56,9 @@ icon:sb_outline = icon:sb_search = icon:sb_stats = +icon:theme_light = +icon:theme_dark = +icon:theme_auto = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/remix_outline.icons b/novelwriter/assets/icons/remix_outline.icons index 0fae7c91..02ce0243 100644 --- a/novelwriter/assets/icons/remix_outline.icons +++ b/novelwriter/assets/icons/remix_outline.icons @@ -56,6 +56,9 @@ icon:sb_outline = icon:sb_search = icon:sb_stats = +icon:theme_light = +icon:theme_dark = +icon:theme_auto = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/images/welcome-dark.jpg b/novelwriter/assets/images/welcome-dark.jpg deleted file mode 100644 index 1b434912..00000000 Binary files a/novelwriter/assets/images/welcome-dark.jpg and /dev/null differ diff --git a/novelwriter/assets/images/welcome-light.jpg b/novelwriter/assets/images/welcome-light.jpg deleted file mode 100644 index 1a1b9f6a..00000000 Binary files a/novelwriter/assets/images/welcome-light.jpg and /dev/null differ diff --git a/novelwriter/assets/images/welcome.webp b/novelwriter/assets/images/welcome.webp new file mode 100644 index 00000000..3831aafe Binary files /dev/null and b/novelwriter/assets/images/welcome.webp differ diff --git a/novelwriter/assets/syntax/cyberpunk_night.conf b/novelwriter/assets/syntax/cyberpunk_night.conf deleted file mode 100644 index 57a14536..00000000 --- a/novelwriter/assets/syntax/cyberpunk_night.conf +++ /dev/null @@ -1,28 +0,0 @@ -[Main] -name = Cyberpunk Night -author = Anders Lemvigh -url = https://github.com/alemvigh -license = CC BY-SA 4.0 -licenseurl = https://creativecommons.org/licenses/by-sa/4.0/ - -[Syntax] -background = 0, 0, 0 -text = 150, 150, 150 -link = 77, 77, 255 -headertext = 255, 255, 255 -headertag = 50, 0, 180 -emphasis = 0, 255, 255 -dialog = 0, 255, 0 -altdialog = 0, 140, 255 -note = 150, 150, 150 -hidden = 77, 77, 100 -shortcode = 255, 255, 0 -keyword = 255, 100, 255 -tag = 255, 150, 10 -value = 255, 150, 10 -spellcheckline = 242, 72, 23 -errorline = 186, 218, 4 -replacetag = 0, 0, 180 -modifier = 144, 142, 176 -optional = 180, 180, 180 -texthighlight = 255, 255, 132, 96 diff --git a/novelwriter/assets/syntax/default_dark.conf b/novelwriter/assets/syntax/default_dark.conf deleted file mode 100644 index 8e467189..00000000 --- a/novelwriter/assets/syntax/default_dark.conf +++ /dev/null @@ -1,42 +0,0 @@ -[Main] -name = Default Dark -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/ - -## -# Colours: -# Background = 2a2a2a : 42, 42, 42 -# Foreground = cccccc : 204, 204, 204 -# Comment = 969696 : 150, 150, 150 -# Red = e64250 : 230, 66, 80 -# Orange = f99157 : 249, 145, 87 -# Yellow = ffff84 : 255, 255, 132 -# Green = 99cc99 : 153, 204, 153 -# Blue = 288ed7 : 102, 153, 204 -# Purple = 8c56d7 : 140, 86, 215 -## - -[Syntax] -background = 42, 42, 42 -text = 204, 204, 204 -link = 102, 153, 204 -headertext = 153, 204, 153 -headertag = 153, 204, 153, 160 -emphasis = 249, 145, 87 -dialog = 102, 153, 204 -altdialog = 102, 153, 204 -note = 255, 255, 216 -hidden = 150, 150, 150 -shortcode = 153, 204, 153 -keyword = 230, 66, 80 -tag = 153, 204, 153 -value = 102, 153, 204 -optional = 153, 204, 153 -spellcheckline = 230, 66, 80 -errorline = 153, 204, 153 -replacetag = 153, 204, 153 -modifier = 153, 204, 153 -texthighlight = 255, 255, 132, 96 diff --git a/novelwriter/assets/syntax/default_light.conf b/novelwriter/assets/syntax/default_light.conf deleted file mode 100644 index 2e5ae404..00000000 --- a/novelwriter/assets/syntax/default_light.conf +++ /dev/null @@ -1,42 +0,0 @@ -[Main] -name = Default Light -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/ - -## -# Colours: -# Background = ffffff : 255, 255, 255 -# Foreground = 000000 : 0, 0, 0 -# Comment = 646464 : 100, 100, 100 -# Red = c83232 : 200, 50, 50 -# Orange = ae6e1e : 174, 110, 30 -# Yellow = 969600 : 150, 150, 0 -# Green = 006400 : 0, 100, 0 -# Blue = 0000c8 : 0, 0, 200 -# Purple = 6400ae : 100, 0, 174 -## - -[Syntax] -background = 255, 255, 255 -text = 0, 0, 0 -link = 0, 0, 200 -headertext = 0, 100, 0 -headertag = 0, 100, 0, 160 -emphasis = 174, 110, 30 -dialog = 0, 0, 200 -altdialog = 0, 0, 200 -note = 100, 100, 0 -hidden = 100, 100, 100 -shortcode = 0, 100, 0 -keyword = 200, 50, 50 -tag = 0, 100, 0 -value = 0, 0, 200 -optional = 0, 100, 0 -spellcheckline = 200, 50, 50 -errorline = 0, 100, 0 -replacetag = 0, 100, 0 -modifier = 0, 100, 0 -texthighlight = 150, 150, 0, 96 diff --git a/novelwriter/assets/syntax/dracula.conf b/novelwriter/assets/syntax/dracula.conf deleted file mode 100644 index fad01ea8..00000000 --- a/novelwriter/assets/syntax/dracula.conf +++ /dev/null @@ -1,44 +0,0 @@ -[Main] -name = Dracula -author = Veronica Berglyd Olsen (adaptation) -credit = Zeno Rocha -url = https://draculatheme.com -license = MIT -licenseurl = https://github.com/dracula/dracula-theme/blob/main/LICENSE - -## -# Colours: -# Background = 282a36 : 40, 42, 54 -# Foreground = f8f8f2 : 248, 248, 242 -# Comment = 6272a4 : 98, 114, 164 -# Cyan = 8be9fd : 139, 233, 253 -# Green = 50fa7b : 80, 250, 123 -# Orange = ffb86c : 255, 184, 108 -# Pink = ff79c6 : 255, 121, 198 -# Purple = bd93f9 : 189, 147, 249 -# Red = ff5555 : 255, 85, 85 -# Yellow = f1fa8c : 241, 250, 140 -## - -[Syntax] -# See: https://spec.draculatheme.com/ -background = 40, 42, 54 -text = 248, 248, 242 -link = 255, 121, 198 -headertext = 189, 147, 249 -headertag = 189, 147, 249, 160 -emphasis = 255, 184, 108 -dialog = 80, 250, 123 -altdialog = 241, 250, 140 -note = 255, 204, 233 -hidden = 98, 114, 164 -shortcode = 139, 233, 253 -keyword = 255, 121, 198 -tag = 255, 184, 108 -value = 255, 184, 108 -optional = 80, 250, 123 -spellcheckline = 255, 85, 85 -errorline = 80, 250, 123 -replacetag = 241, 250, 140 -modifier = 139, 233, 253 -texthighlight = 241, 250, 140, 96 diff --git a/novelwriter/assets/syntax/grey_dark.conf b/novelwriter/assets/syntax/grey_dark.conf deleted file mode 100644 index cc36478c..00000000 --- a/novelwriter/assets/syntax/grey_dark.conf +++ /dev/null @@ -1,29 +0,0 @@ -[Main] -name = Grey Dark -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/ - -[Syntax] -background = 54, 54, 54 -text = 200, 200, 200 -link = 200, 200, 200 -headertext = 225, 225, 225 -headertag = 225, 225, 225, 160 -emphasis = 200, 200, 200 -dialog = 200, 200, 200 -altdialog = 200, 200, 200 -note = 200, 200, 200 -hidden = 150, 150, 150 -shortcode = 225, 225, 225 -keyword = 225, 225, 225 -tag = 200, 200, 200 -value = 200, 200, 200 -optional = 225, 225, 225 -spellcheckline = 200, 46, 0 -errorline = 46, 200, 0 -replacetag = 225, 225, 225 -modifier = 225, 225, 225 -texthighlight = 255, 255, 255, 64 diff --git a/novelwriter/assets/syntax/grey_light.conf b/novelwriter/assets/syntax/grey_light.conf deleted file mode 100644 index 80a8a01c..00000000 --- a/novelwriter/assets/syntax/grey_light.conf +++ /dev/null @@ -1,29 +0,0 @@ -[Main] -name = Grey Light -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/ - -[Syntax] -background = 255, 255, 255 -text = 20, 20, 20 -link = 20, 20, 20 -headertext = 0, 0, 0 -headertag = 0, 0, 0, 160 -emphasis = 20, 20, 20 -dialog = 20, 20, 20 -altdialog = 20, 20, 20 -note = 20, 20, 20 -hidden = 100, 100, 100 -shortcode = 0, 0, 0 -keyword = 0, 0, 0 -tag = 20, 20, 20 -value = 20, 20, 20 -optional = 0, 0, 0 -spellcheckline = 200, 0, 0 -errorline = 0, 150, 0 -replacetag = 0, 0, 0 -modifier = 0, 0, 0 -texthighlight = 0, 0, 0, 64 diff --git a/novelwriter/assets/syntax/light_owl.conf b/novelwriter/assets/syntax/light_owl.conf deleted file mode 100644 index ce65e704..00000000 --- a/novelwriter/assets/syntax/light_owl.conf +++ /dev/null @@ -1,49 +0,0 @@ -## -# Theme: Light Owl -# Source: https://github.com/sdras/night-owl-vscode-theme -# Credit: Sarah Drasner -## -# Colours: -# Background = fbfbfb : 251, 251, 251 -# Current Line = e0e0e0 : 224, 224, 224 -# Selection = e0e0e0 : 224, 224, 224 -# Foreground = 403f52 : 64, 63, 82 -# Comment = 989fb1 : 152, 159, 177 -# Red = de3d3a : 222, 61, 58 -# Orange = e0af05 : 224, 175, 5 -# Yellow = daaa01 : 218, 170, 1 -# Green = 08916a : 8, 145, 106 -# Aqua = 2aa298 : 42, 162, 152 -# Blue = 288ed7 : 40, 142, 215 -# Purple = 964ac1 : 150, 74, 193 -## - -[Main] -name = Light Owl -author = Veronica Berglyd Olsen (adaptation) -credit = Sarah Drasner (color theme) -url = https://github.com/sdras/night-owl-vscode-theme -license = MIT License -licenseurl = https://github.com/sdras/night-owl-vscode-theme/blob/master/LICENSE - -[Syntax] -background = 251, 251, 251 -text = 64, 63, 82 -link = 40, 142, 215 -headertext = 40, 142, 215 -headertag = 40, 142, 215, 160 -emphasis = 224, 175, 5 -dialog = 8, 145, 106 -altdialog = 218, 170, 1 -note = 128, 118, 75 -hidden = 152, 159, 177 -shortcode = 40, 142, 215 -keyword = 222, 61, 58 -tag = 150, 74, 193 -value = 224, 175, 5 -optional = 40, 142, 215 -spellcheckline = 222, 61, 58 -errorline = 8, 145, 106 -replacetag = 42, 162, 152 -modifier = 8, 145, 106 -texthighlight = 218, 170, 1, 96 diff --git a/novelwriter/assets/syntax/night_owl.conf b/novelwriter/assets/syntax/night_owl.conf deleted file mode 100644 index 337cdfb8..00000000 --- a/novelwriter/assets/syntax/night_owl.conf +++ /dev/null @@ -1,49 +0,0 @@ -## -# Theme: Night Owl -# Source: https://github.com/sdras/night-owl-vscode-theme -# Credit: Sarah Drasner -## -# Colours: -# Background = 011627 : 1, 22, 39 -# Current Line = 32374d : 50, 55, 77 -# Selection = 32374d : 50, 55, 77 -# Foreground = d6deeb : 214, 222, 235 -# Comment = 637777 : 99, 119, 119 -# Red = f78c6c : 247, 140, 108 -# Orange = ecc48d : 236, 196, 141 -# Yellow = ffeb95 : 255, 235, 149 -# Green = addb67 : 173, 219, 103 -# Aqua = 7fdbca : 127, 219, 202 -# Blue = 82aaff : 130, 170, 255 -# Purple = c792ea : 199, 146, 234 -## - -[Main] -name = Night Owl -author = Veronica Berglyd Olsen (adaptation) -credit = Sarah Drasner (color theme) -url = https://github.com/sdras/night-owl-vscode-theme -license = MIT License -licenseurl = https://github.com/sdras/night-owl-vscode-theme/blob/master/LICENSE - -[Syntax] -background = 1, 22, 39 -text = 214, 222, 235 -link = 130, 170, 255 -headertext = 130, 170, 255 -headertag = 130, 170, 255, 160 -emphasis = 236, 196, 141 -dialog = 173, 219, 103 -altdialog = 255, 235, 149 -note = 255, 249, 202 -hidden = 99, 119, 119 -shortcode = 130, 170, 255 -keyword = 247, 140, 108 -tag = 199, 146, 234 -value = 255, 235, 149 -optional = 130, 170, 255 -spellcheckline = 247, 140, 108 -errorline = 173, 219, 103 -replacetag = 127, 219, 202 -modifier = 173, 219, 103 -texthighlight = 255, 235, 149, 96 diff --git a/novelwriter/assets/syntax/snazzy.conf b/novelwriter/assets/syntax/snazzy.conf deleted file mode 100644 index d948b770..00000000 --- a/novelwriter/assets/syntax/snazzy.conf +++ /dev/null @@ -1,42 +0,0 @@ -[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 - -## -# 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 -## - -[Syntax] -background = 250, 251, 252 -text = 86, 88, 105 -link = 9, 161, 237 -headertext = 45, 174, 88 -headertag = 45, 174, 88, 160 -emphasis = 247, 103, 187 -dialog = 9, 161, 237 -altdialog = 207, 156, 0 -note = 120, 187, 185 -hidden = 145, 148, 162 -shortcode = 247, 103, 187 -keyword = 9, 161, 237 -tag = 45, 174, 88 -value = 207, 156, 0 -optional = 207, 156, 0 -spellcheckline = 255, 92, 87 -errorline = 45, 174, 88 -replacetag = 19, 187, 183 -modifier = 247, 103, 187 -texthighlight = 207, 156, 0, 96 diff --git a/novelwriter/assets/syntax/solarized_dark.conf b/novelwriter/assets/syntax/solarized_dark.conf deleted file mode 100644 index 0fbacdb7..00000000 --- a/novelwriter/assets/syntax/solarized_dark.conf +++ /dev/null @@ -1,29 +0,0 @@ -[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 - -[Syntax] -background = 7, 54, 66 -text = 253, 246, 227 -link = 38, 139, 210 -headertext = 147, 161, 161 -headertag = 42, 161, 152 -emphasis = 38, 139, 210 -dialog = 42, 161, 152 -altdialog = 42, 161, 152 -note = 101, 161, 156 -hidden = 147, 161, 161 -shortcode = 147, 161, 161 -keyword = 133, 153, 0 -tag = 203, 75, 22 -value = 203, 75, 22 -optional = 147, 161, 161 -spellcheckline = 203, 75, 22 -errorline = 220, 50, 47 -replacetag = 133, 153, 0 -modifier = 181, 137, 0 -texthighlight = 181, 137, 0, 96 diff --git a/novelwriter/assets/syntax/solarized_light.conf b/novelwriter/assets/syntax/solarized_light.conf deleted file mode 100644 index 525a59e5..00000000 --- a/novelwriter/assets/syntax/solarized_light.conf +++ /dev/null @@ -1,29 +0,0 @@ -[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 - -[Syntax] -background = 253, 246, 227 -text = 0, 43, 54 -link = 38, 139, 210 -headertext = 88, 110, 117 -headertag = 42, 161, 152 -emphasis = 38, 139, 210 -dialog = 42, 161, 152 -altdialog = 42, 161, 152 -note = 27, 102, 96 -hidden = 88, 110, 117 -shortcode = 88, 110, 117 -keyword = 133, 153, 0 -tag = 203, 75, 22 -value = 203, 75, 22 -optional = 88, 110, 117 -spellcheckline = 203, 75, 22 -errorline = 220, 50, 47 -replacetag = 133, 153, 0 -modifier = 181, 137, 0 -texthighlight = 181, 137, 0, 96 diff --git a/novelwriter/assets/syntax/tango.conf b/novelwriter/assets/syntax/tango.conf deleted file mode 100644 index 7c5e9daa..00000000 --- a/novelwriter/assets/syntax/tango.conf +++ /dev/null @@ -1,39 +0,0 @@ -[Main] -name = Tango -author = Veronica Berglyd Olsen (adaptation) - -## -# Colours: -# Background = 30302f : 48, 48, 47 -# Foreground = eeeeec : 238, 238, 236 -# Comment = 8f8f8d : 143, 143, 141 -# Red = ef2929 : 239, 41, 41 -# Orange = c4a000 : 196, 160, 0 -# Yellow = fce94f : 252, 233, 79 -# Green = 8ae234 : 138, 226, 52 -# Cyan = 34e2e2 : 52, 226, 226 -# Blue = 3465a4 : 114, 159, 207 -# Purple = ad7fa8 : 173, 127, 168 -## - -[Syntax] -background = 48, 48, 47 -text = 238, 238, 236 -link = 114, 159, 207 -headertext = 114, 159, 207 -headertag = 114, 159, 207, 160 -emphasis = 196, 160, 0 -dialog = 138, 226, 52 -altdialog = 252, 233, 79 -note = 252, 242, 164 -hidden = 143, 143, 141 -shortcode = 114, 159, 207 -keyword = 239, 41, 41 -tag = 173, 127, 168 -value = 239, 41, 41 -optional = 114, 159, 207 -spellcheckline = 239, 41, 41 -errorline = 138, 226, 52 -replacetag = 52, 226, 226 -modifier = 114, 159, 207 -texthighlight = 252, 233, 79, 96 diff --git a/novelwriter/assets/syntax/tomorrow.conf b/novelwriter/assets/syntax/tomorrow.conf deleted file mode 100644 index d3d2b8e4..00000000 --- a/novelwriter/assets/syntax/tomorrow.conf +++ /dev/null @@ -1,49 +0,0 @@ -## -# Theme: Tomorrow -# Source: https://github.com/chriskempson/tomorrow-theme -# Credit: Chris Kempson -## -# Colours: -# Background = ffffff : 255, 255, 255 -# Current Line = efefef : 239, 239, 239 -# Selection = d6d6d6 : 214, 214, 214 -# Foreground = 4d4d4c : 77, 77, 76 -# Comment = 8e908c : 142, 144, 140 -# Red = c82829 : 240, 40, 41 -# Orange = f5871f : 245, 135, 31 -# Yellow = eab700 : 234, 183, 0 -# Green = 718c00 : 113, 140, 0 -# Aqua = 3e999f : 62, 153, 159 -# Blue = 4271ae : 66, 113, 174 -# Purple = 8959a8 : 137, 89, 168 -## - -[Main] -name = Tomorrow -author = Veronica Berglyd Olsen (adaptation) -credit = Chris Kempson (color theme) -url = https://github.com/chriskempson/tomorrow-theme -license = MIT License -licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md - -[Syntax] -background = 255, 255, 255 -text = 77, 77, 76 -link = 66, 113, 174 -headertext = 66, 113, 174 -headertag = 66, 113, 174, 160 -emphasis = 245, 135, 31 -dialog = 113, 140, 0 -altdialog = 234, 183, 0 -note = 115, 90, 0 -hidden = 142, 144, 140 -shortcode = 66, 113, 174 -keyword = 240, 40, 41 -tag = 137, 89, 168 -value = 234, 183, 0 -optional = 66, 113, 174 -spellcheckline = 240, 40, 41 -errorline = 113, 140, 0 -replacetag = 62, 153, 159 -modifier = 245, 135, 31 -texthighlight = 234, 183, 0, 96 diff --git a/novelwriter/assets/syntax/tomorrow_night.conf b/novelwriter/assets/syntax/tomorrow_night.conf deleted file mode 100644 index 15257945..00000000 --- a/novelwriter/assets/syntax/tomorrow_night.conf +++ /dev/null @@ -1,49 +0,0 @@ -## -# Theme: Tomorrow Night -# Source: https://github.com/chriskempson/tomorrow-theme -# Credit: Chris Kempson -## -# Colours: -# Background = 1d1f21 : 29, 31, 33 -# Current Line = 282a2e : 40, 42, 46 -# Selection = 373b41 : 55, 59. 65 -# Foreground = c5c8c6 : 197, 200, 198 -# Comment = 969896 : 150, 152, 150 -# Red = cc6666 : 204, 102, 102 -# Orange = de935f : 222, 147, 95 -# Yellow = f0c674 : 240, 198, 116 -# Green = b5bd68 : 181, 189, 104 -# Aqua = 8abeb7 : 138, 190, 183 -# Blue = 81a2be : 129, 162, 190 -# Purple = b294bb : 178, 148, 187 -## - -[Main] -name = Tomorrow Night -author = Veronica Berglyd Olsen (adaptation) -credit = Chris Kempson (color theme) -url = https://github.com/chriskempson/tomorrow-theme -license = MIT License -licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md - -[Syntax] -background = 29, 31, 33 -text = 197, 200, 198 -link = 197, 200, 198, 160 -headertext = 129, 162, 190 -headertag = 94, 119, 139 -emphasis = 222, 147, 95 -dialog = 181, 189, 104 -altdialog = 240, 198, 116 -note = 240, 219, 178 -hidden = 150, 152, 150 -shortcode = 129, 162, 190 -keyword = 204, 102, 102 -tag = 178, 148, 187 -value = 240, 198, 116 -optional = 129, 162, 190 -spellcheckline = 204, 102, 102 -errorline = 181, 189, 104 -replacetag = 138, 190, 183 -modifier = 222, 147, 95 -texthighlight = 240, 198, 116, 96 diff --git a/novelwriter/assets/syntax/tomorrow_night_blue.conf b/novelwriter/assets/syntax/tomorrow_night_blue.conf deleted file mode 100644 index 89067e4f..00000000 --- a/novelwriter/assets/syntax/tomorrow_night_blue.conf +++ /dev/null @@ -1,49 +0,0 @@ -## -# Theme: Tomorrow Night Blue -# Source: https://github.com/chriskempson/tomorrow-theme -# Credit: Chris Kempson -## -# Colours: -# Background = 002451 : 0, 36, 81 -# Current Line = 00346e : 0, 52, 110 -# Selection = 003f8e : 0, 63, 142 -# Foreground = ffffff : 255, 255, 255 -# Comment = 7285b7 : 114, 133, 183 -# Red = ff9da4 : 255, 157, 164 -# Orange = ffc58f : 255, 197, 143 -# Yellow = ffeead : 255, 238, 173 -# Green = d1f1a9 : 209, 241, 169 -# Aqua = 99ffff : 153, 255, 255 -# Blue = bbdaff : 187, 218, 255 -# Purple = ebbbff : 235, 187, 255 -## - -[Main] -name = Tomorrow Night Blue -author = Veronica Berglyd Olsen (adaptation) -credit = Chris Kempson (color theme) -url = https://github.com/chriskempson/tomorrow-theme -license = MIT License -licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md - -[Syntax] -background = 0, 36, 81 -text = 255, 255, 255 -link = 187, 218, 255 -headertext = 187, 218, 255 -headertag = 187, 218, 255, 160 -emphasis = 255, 197, 143 -dialog = 209, 241, 169 -altdialog = 255, 238, 173 -note = 255, 247, 214 -hidden = 114, 133, 183 -shortcode = 187, 218, 255 -keyword = 255, 157, 164 -tag = 235, 187, 255 -value = 255, 238, 173 -optional = 187, 218, 255 -spellcheckline = 255, 157, 164 -errorline = 209, 241, 169 -replacetag = 153, 255, 255 -modifier = 255, 197, 143 -texthighlight = 255, 238, 173, 96 diff --git a/novelwriter/assets/syntax/tomorrow_night_bright.conf b/novelwriter/assets/syntax/tomorrow_night_bright.conf deleted file mode 100644 index 0f1fd055..00000000 --- a/novelwriter/assets/syntax/tomorrow_night_bright.conf +++ /dev/null @@ -1,49 +0,0 @@ -## -# Theme: Tomorrow Night Bright -# Source: https://github.com/chriskempson/tomorrow-theme -# Credit: Chris Kempson -## -# Colours: -# Background = 000000 : 0, 0, 0 -# Current Line = 2a2a2a : 42, 42, 42 -# Selection = 424242 : 66, 66, 66 -# Foreground = eaeaea : 234, 234, 234 -# Comment = 969896 : 150, 152, 150 -# Red = d54e53 : 213, 78, 83 -# Orange = e78c45 : 231, 140, 69 -# Yellow = e7c547 : 231, 197, 71 -# Green = b9ca4a : 185, 202, 74 -# Aqua = 70c0b1 : 112, 192, 177 -# Blue = 7aa6da : 122, 166, 218 -# Purple = c397d8 : 195, 151, 216 -## - -[Main] -name = Tomorrow Night Bright -author = Veronica Berglyd Olsen (adaptation) -credit = Chris Kempson (color theme) -url = https://github.com/chriskempson/tomorrow-theme -license = MIT License -licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md - -[Syntax] -background = 0, 0, 0 -text = 234, 234, 234 -link = 122, 166, 218 -headertext = 122, 166, 218 -headertag = 122, 166, 218, 160 -emphasis = 231, 140, 69 -dialog = 185, 202, 74 -altdialog = 231, 197, 71 -note = 231, 214, 150 -hidden = 150, 152, 150 -shortcode = 122, 166, 218 -keyword = 213, 78, 83 -tag = 195, 151, 216 -value = 231, 197, 71 -optional = 122, 166, 218 -spellcheckline = 213, 78, 83 -errorline = 185, 202, 74 -replacetag = 112, 192, 177 -modifier = 231, 140, 69 -texthighlight = 231, 197, 71, 128 diff --git a/novelwriter/assets/syntax/tomorrow_night_eighties.conf b/novelwriter/assets/syntax/tomorrow_night_eighties.conf deleted file mode 100644 index a638a871..00000000 --- a/novelwriter/assets/syntax/tomorrow_night_eighties.conf +++ /dev/null @@ -1,49 +0,0 @@ -## -# Theme: Tomorrow Night Eighties -# Source: https://github.com/chriskempson/tomorrow-theme -# Credit: Chris Kempson -## -# Colours: -# Background = 2d2d2d : 45, 45, 45 -# Current Line = 393939 : 57, 57, 57 -# Selection = 515151 : 81, 81, 81 -# Foreground = cccccc : 204, 204, 204 -# Comment = 999999 : 153, 153, 153 -# Red = f2777a : 242, 119, 122 -# Orange = f99157 : 249, 145, 57 -# Yellow = ffcc66 : 255, 204, 102 -# Green = 99cc99 : 153, 204, 153 -# Aqua = 66cccc : 102, 204, 204 -# Blue = 6699cc : 102, 153, 204 -# Purple = cc99cc : 204, 153, 204 -## - -[Main] -name = Tomorrow Night Eighties -author = Veronica Berglyd Olsen (adaptation) -credit = Chris Kempson (color theme) -url = https://github.com/chriskempson/tomorrow-theme -license = MIT License -licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md - -[Syntax] -background = 45, 45, 45 -text = 204, 204, 204 -link = 102, 153, 204 -headertext = 102, 153, 204 -headertag = 102, 153, 204, 160 -emphasis = 249, 145, 57 -dialog = 153, 204, 153 -altdialog = 255, 204, 102 -note = 255, 230, 179 -hidden = 153, 153, 153 -shortcode = 102, 153, 204 -keyword = 242, 119, 122 -tag = 204, 153, 204 -value = 255, 204, 102 -optional = 102, 153, 204 -spellcheckline = 242, 119, 122 -errorline = 153, 204, 153 -replacetag = 102, 204, 204 -modifier = 249, 145, 57 -texthighlight = 255, 204, 102, 96 diff --git a/novelwriter/assets/text/credits_en.htm b/novelwriter/assets/text/credits_en.htm index 88b3887d..c8d868a9 100644 --- a/novelwriter/assets/text/credits_en.htm +++ b/novelwriter/assets/text/credits_en.htm @@ -67,20 +67,38 @@ more contributions are listed on the project's Members page.

  • PyEnchant by Dimitri Merejkowsky
  • -

    Assets

    +

    Icon Themes

    -

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

    +

    The following icon themes are bundles with novelWriter:

    • 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)
    • +
    + +

    Colour Themes

    + +

    Some colour themes included with novelWriter were adapted from the following sources:

    + +
      +
    • Dracula theme by Zeno Rocha
    • +
    • Espresso theme by Sainnhe Park and Antoine Cotten
    • +
    • Noctis themes by Liviu Schera, and contributors
    • +
    • 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:

    + +
      +
    • Blue Streak themes by Veronica Berglyd Olsen
    • +
    • Cyberpunk Night theme by Anders Lemvigh
    • +
    • LCARS theme by Veronica Berglyd Olsen

    Fonts

    diff --git a/novelwriter/assets/themes/aura.conf b/novelwriter/assets/themes/aura.conf new file mode 100644 index 00000000..b6d8749e --- /dev/null +++ b/novelwriter/assets/themes/aura.conf @@ -0,0 +1,76 @@ +[Main] +name = Aura +mode = dark +description = Aura +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:L110 +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..65f41056 --- /dev/null +++ b/novelwriter/assets/themes/aura_bright.conf @@ -0,0 +1,74 @@ +[Main] +name = Aura (Bright) +mode = light +description = Aura +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 = default +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..82ddbc11 --- /dev/null +++ b/novelwriter/assets/themes/aura_soft.conf @@ -0,0 +1,76 @@ +[Main] +name = Aura (Soft) +mode = dark +description = Aura +author = Myian +credit = Dalton Menezes +url = https://github.com/daltonmenezes/aura-theme + +[Base] +base = #15141b +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 = #0c0c11 +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 = purple:L110 +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..a6dcf87a --- /dev/null +++ b/novelwriter/assets/themes/b2t_garden_dark.conf @@ -0,0 +1,76 @@ +[Main] +name = B2T Garden Dark +mode = dark +description = Garden 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 = #1c8217 +title = default +chapter = #90d98c +scene = #4cb946 +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 \ No newline at end of file diff --git a/novelwriter/assets/themes/b2t_garden_light.conf b/novelwriter/assets/themes/b2t_garden_light.conf new file mode 100644 index 00000000..6d3172bd --- /dev/null +++ b/novelwriter/assets/themes/b2t_garden_light.conf @@ -0,0 +1,76 @@ +[Main] +name = B2T Garden Light +mode = light +description = Garden 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 = #6bcc66 +title = default +chapter = #1c8217 +scene = #3fac39 +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 \ No newline at end of file diff --git a/novelwriter/assets/themes/b2t_suburb_dark.conf b/novelwriter/assets/themes/b2t_suburb_dark.conf new file mode 100644 index 00000000..a088ecef --- /dev/null +++ b/novelwriter/assets/themes/b2t_suburb_dark.conf @@ -0,0 +1,76 @@ +[Main] +name = B2T Suburb Dark +mode = dark +description = Suburb 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 \ No newline at end of file diff --git a/novelwriter/assets/themes/b2t_suburb_light.conf b/novelwriter/assets/themes/b2t_suburb_light.conf new file mode 100644 index 00000000..429994df --- /dev/null +++ b/novelwriter/assets/themes/b2t_suburb_light.conf @@ -0,0 +1,76 @@ +[Main] +name = B2T Suburb Light +mode = light +description = Suburb 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 \ No newline at end of file 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..5c664a5b --- /dev/null +++ b/novelwriter/assets/themes/b4t_classic_o_dark.conf @@ -0,0 +1,76 @@ +[Main] +name = B4T Classic-O Dark +mode = dark +description = Classic-O 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 = #9cc1fc +purple = #d19af4 + +[Project] +root = default +folder = #649efb +file = default +title = #039582 +chapter = #1abba6 +scene = #5ee3d1 +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 = #0c9c89 +highlightedtext = base +link = blue +linkvisited = blue +accent = #0c9c89 + +[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 = #18ae9a +keyword = #877af0 +tag = #a095f3 +value = faded +optional = faded +spellcheckline = #ff688d +errorline = #5fe2d1 +replacetag = blue +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..b7d7643d --- /dev/null +++ b/novelwriter/assets/themes/b4t_classic_o_light.conf @@ -0,0 +1,76 @@ +[Main] +name = B4T Classic-O Light +mode = light +description = Classic-O 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 = #5996F6 +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 = purple +hidden = faded +shortcode = #17a592 +keyword = #9488F2 +tag = #6555e0 +value = faded +optional = faded +spellcheckline = #ff2259 +errorline = blue +replacetag = blue +modifier = #c17eed +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..4300da41 --- /dev/null +++ b/novelwriter/assets/themes/b4t_modern_c_dark.conf @@ -0,0 +1,76 @@ +[Main] +name = B4T Modern-C Dark +mode = dark +description = Modern-C 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 = #fad6e4 +title = #e64d87 +chapter = #eb75a2 +scene = #f5a3c2 +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 = #9fabf8 +note = #929ff7 +hidden = faded +shortcode = cyan +keyword = #18ae9a +tag = #93ece0 +value = #c5cdfb +optional = faded +spellcheckline = #e64d87 +errorline = blue +replacetag = blue +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..ef4c23f5 --- /dev/null +++ b/novelwriter/assets/themes/b4t_modern_c_light.conf @@ -0,0 +1,76 @@ +[Main] +name = B4T Modern-C Light +mode = light +description = Modern-C 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 = #eb75a2 +title = #931f4c +chapter = #d22d6c +scene = #e64d87 +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 = #354ddd +note = blue +hidden = faded +shortcode = cyan +keyword = #007a6a +tag = #18ae9a +value = #8493F6 +optional = faded +spellcheckline = red +errorline = blue +replacetag = blue +modifier = #354ddd +texthighlight = blue:64 \ No newline at end of file diff --git a/novelwriter/assets/themes/blue_streak_dark.conf b/novelwriter/assets/themes/blue_streak_dark.conf new file mode 100644 index 00000000..c8dbf177 --- /dev/null +++ b/novelwriter/assets/themes/blue_streak_dark.conf @@ -0,0 +1,76 @@ +[Main] +name = Blue Streak Dark +mode = dark +description = A theme mainly using shades of blue +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..906ab71e --- /dev/null +++ b/novelwriter/assets/themes/blue_streak_light.conf @@ -0,0 +1,76 @@ +[Main] +name = Blue Streak Light +mode = light +description = A theme mainly using shades of blue +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..a7948592 --- /dev/null +++ b/novelwriter/assets/themes/castle_day.conf @@ -0,0 +1,74 @@ +[Main] +name = Castle by Day +mode = light +description = Castle Day +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:D115 +text = default +tooltipbase = yellow:L230 +tooltiptext = default +button = base +buttontext = default +brighttext = base +highlight = #cf7c6d +highlightedtext = base:L110 +link = #5068b6 +linkvisited = #5068b6 +accent = red:192 + +[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..8fd8a963 --- /dev/null +++ b/novelwriter/assets/themes/castle_night.conf @@ -0,0 +1,74 @@ +[Main] +name = Castle by Night +mode = dark +description = Castle Night +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..19b8dc6a --- /dev/null +++ b/novelwriter/assets/themes/chalky_soil.conf @@ -0,0 +1,74 @@ +[Main] +name = Chalky Soil +mode = light +description = Chalky +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 = yellow +note = faded +hidden = faded +shortcode = #967044 +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..10847dd3 --- /dev/null +++ b/novelwriter/assets/themes/chernozem.conf @@ -0,0 +1,74 @@ +[Main] +name = Chernozem +mode = dark +description = Chernozem +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 = #5a8d55 +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 = #af8973 +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..3643888f 100644 --- a/novelwriter/assets/themes/cyberpunk_night.conf +++ b/novelwriter/assets/themes/cyberpunk_night.conf @@ -1,48 +1,75 @@ [Main] name = Cyberpunk Night +mode = dark 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/ -[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..75b2843f 100644 --- a/novelwriter/assets/themes/default_dark.conf +++ b/novelwriter/assets/themes/default_dark.conf @@ -1,49 +1,76 @@ [Main] name = Default Dark Theme +mode = dark 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/ -[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 = green +keyword = red +tag = green +value = green +optional = green +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..744f90e3 100644 --- a/novelwriter/assets/themes/default_light.conf +++ b/novelwriter/assets/themes/default_light.conf @@ -1,49 +1,76 @@ [Main] name = Default Light Theme +mode = light 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/ -[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 = green +keyword = red +tag = green +value = green +optional = green +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..25fe36ca 100644 --- a/novelwriter/assets/themes/dracula.conf +++ b/novelwriter/assets/themes/dracula.conf @@ -1,17 +1,17 @@ [Main] name = Dracula +mode = dark 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 ## # 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 +22,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..74ff4b33 --- /dev/null +++ b/novelwriter/assets/themes/espresso.conf @@ -0,0 +1,76 @@ +[Main] +name = Espresso +mode = dark +description = High contrast & vivid color scheme based on Monokai Pro +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..8de92d38 --- /dev/null +++ b/novelwriter/assets/themes/everforest_dark.conf @@ -0,0 +1,76 @@ +[Main] +name = Everforest Dark +mode = dark +description = Everforest +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..f6219327 --- /dev/null +++ b/novelwriter/assets/themes/everforest_light.conf @@ -0,0 +1,76 @@ +[Main] +name = Everforest Light +mode = light +description = Everforest +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..97c71ec3 --- /dev/null +++ b/novelwriter/assets/themes/floral_daydream.conf @@ -0,0 +1,74 @@ +[Main] +name = Floral Daydream +mode = light +description = Floral +author = Myian + +[Base] +base = #fff4f7 +default = #ac3b6f +faded = #c285aa +red = #e76b6d +orange = #e08b46 +yellow = #bea224 +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 = blue +headertext = blue +headertag = blue:128 +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..8dc48dbe --- /dev/null +++ b/novelwriter/assets/themes/floral_midnight.conf @@ -0,0 +1,74 @@ +[Main] +name = Floral Midnight +mode = dark +description = Floral +author = Myian + +[Base] +base = #1e1e31 +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 = base:D130 +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 = #c29df3 +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..25e9678c --- /dev/null +++ b/novelwriter/assets/themes/full_moon.conf @@ -0,0 +1,75 @@ +[Main] +name = Full Moon +mode = light +description = New Moon Light +author = Myian + +[Base] +base = #ffffff +default = #454c55 +faded = #9197a0 +red = #d84f52 +orange = #db7a34 +yellow = #c5931f +green = #479e47 +cyan = #219fa3 +blue = #3e8acc +purple = #bd70cc + + +[Project] +root = default +folder = orange +file = green +title = blue +chapter = red +scene = purple +note = yellow +active = green +inactive = red +disabled = faded + +[Palette] +window = #eceef1 +windowtext = default +base = base +alternatebase = #e2eaf0 +text = default +tooltipbase = #ffeea6 +tooltiptext = default +button = #ffffff +buttontext = default +brighttext = base +highlight = #549adb +highlightedtext = base +link = blue +linkvisited = blue +accent = #549adb + +[GUI] +helptext = faded:D125 +fadedtext = faded:L135 +errortext = red + +[Syntax] +background = base +text = #565c63 +line = #eceff5 +link = blue +headertext = purple +headertag = purple:128 +emphasis = blue +dialog = #2d2d2d +altdialog = orange +note = yellow +hidden = faded +shortcode = cyan +keyword = red +tag = yellow +value = orange +optional = yellow +spellcheckline = #f73c3f +errorline = cyan +replacetag = purple +modifier = green +texthighlight = blue:80 \ No newline at end of file diff --git a/novelwriter/assets/themes/grey_dark.conf b/novelwriter/assets/themes/grey_dark.conf new file mode 100644 index 00000000..44f94e14 --- /dev/null +++ b/novelwriter/assets/themes/grey_dark.conf @@ -0,0 +1,76 @@ +[Main] +name = Grey Dark +mode = dark +description = A theme with minimal colours +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..3e4d3e86 --- /dev/null +++ b/novelwriter/assets/themes/grey_light.conf @@ -0,0 +1,76 @@ +[Main] +name = Grey Light +mode = light +description = A theme with minimal colours +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/lcars.conf b/novelwriter/assets/themes/lcars.conf new file mode 100644 index 00000000..94255255 --- /dev/null +++ b/novelwriter/assets/themes/lcars.conf @@ -0,0 +1,76 @@ +[Main] +name = LCARS +mode = dark +description = The Next Generation of Star Trek colours +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..24b676e1 --- /dev/null +++ b/novelwriter/assets/themes/light_owl.conf @@ -0,0 +1,96 @@ +[Main] +name = Light Owl +mode = light +description = A theme for the night owls out there, but for daylight +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..39656db7 --- /dev/null +++ b/novelwriter/assets/themes/new_moon.conf @@ -0,0 +1,76 @@ +[Main] +name = New Moon +mode = dark +description = New moon +author = Myian +credit = Tania Rascia +url = https://github.com/taniarascia/new-moon + +[Base] +base = #2d2d2d +default = #ffffff +faded = #777c85 +red = #f2777a +orange = #fca369 +yellow = #ffd479 +green = #92d192 +cyan = #76d4d6 +blue = #6ab0f3 +purple = #e1a6f2 + +[Project] +root = default +folder = orange +file = green +title = blue +chapter = red +scene = purple +note = yellow +active = green +inactive = red +disabled = faded + +[Palette] +window = #252525 +windowtext = default +base = base +alternatebase = #444444 +text = default +tooltipbase = #ffeea6 +tooltiptext = base +button = #333333 +buttontext = default +brighttext = base +highlight = #4386c5 +highlightedtext = default +link = blue +linkvisited = blue +accent = #4386c5 + +[GUI] +helptext = #a4a4a4 +fadedtext = #949494 +errortext = red + +[Syntax] +background = base +text = #bcc2cc +line = #444444 +link = blue +headertext = purple +headertag = purple:128 +emphasis = blue +dialog = default +altdialog = #ffeea6 +note = yellow +hidden = faded +shortcode = cyan +keyword = red +tag = yellow +value = orange +optional = #ffeea6 +spellcheckline = #ff6265 +errorline = green +replacetag = purple +modifier = green +texthighlight = blue:80 \ No newline at end of file diff --git a/novelwriter/assets/themes/night_owl.conf b/novelwriter/assets/themes/night_owl.conf new file mode 100644 index 00000000..b42ea489 --- /dev/null +++ b/novelwriter/assets/themes/night_owl.conf @@ -0,0 +1,96 @@ +[Main] +name = Night Owl +mode = dark +description = A theme for the night owls out there +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..35820118 --- /dev/null +++ b/novelwriter/assets/themes/noctis.conf @@ -0,0 +1,108 @@ +[Main] +name = Noctis +mode = dark +description = A dark theme with a well balanced blend of warm and cold medium contrast colors +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..ddf34244 --- /dev/null +++ b/novelwriter/assets/themes/noctis_lux.conf @@ -0,0 +1,108 @@ +[Main] +name = Noctis Lux +mode = light +description = A light theme with a well balanced blend of warm and cold medium contrast colors +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..be9d956a --- /dev/null +++ b/novelwriter/assets/themes/nord.conf @@ -0,0 +1,76 @@ +[Main] +name = Nord +mode = dark +description = Nord +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..2d78a103 --- /dev/null +++ b/novelwriter/assets/themes/nordlicht.conf @@ -0,0 +1,74 @@ +[Main] +name = Nordlicht +mode = light +description = Nord +author = Myian + +[Base] +base = #ffffff +default = #4c566a +faded = #7b88a1 +red = #bf616a +orange = #d08770 +yellow = #daba7a +green = #7cac78 +cyan = #6bacbe +blue = #6b8ab8 +purple = #aa7aa1 + +[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 = #5e81ac +altdialog = #619b5b +note = orange +hidden = faded +shortcode = orange +keyword = purple +tag = #68a593 +value = #68a593 +optional = faded +spellcheckline = #ff3b4f +errorline = #58aa51 +replacetag = orange +modifier = #a16e98 +texthighlight = cyan:64 \ No newline at end of file diff --git a/novelwriter/assets/themes/primer_light.conf b/novelwriter/assets/themes/primer_light.conf new file mode 100644 index 00000000..ecd1d34a --- /dev/null +++ b/novelwriter/assets/themes/primer_light.conf @@ -0,0 +1,76 @@ +[Main] +name = Primer Light +mode = light +description = Primer +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..2496f10d --- /dev/null +++ b/novelwriter/assets/themes/primer_night.conf @@ -0,0 +1,76 @@ +[Main] +name = Primer Night +mode = dark +description = Primer +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..20891da0 --- /dev/null +++ b/novelwriter/assets/themes/ruby_day.conf @@ -0,0 +1,74 @@ +[Main] +name = Ruby Day +mode = light +description = Ruby +author = Myian + +[Base] +base = #ffeeee +default = #444252 +faded = #8e8c9c +red = #e46363 +orange = #df9a61 +yellow = #e5c280 +green = #72ac79 +cyan = #6aa2a7 +blue = #7b80c2 +purple = #b781c2 + +[Project] +root = default +folder = orange +file = #7d7a8d +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 = #9f58ad +linkvisited = #9f58ad +accent = #c43939 + +[GUI] +helptext = #756777 +fadedtext = faded +errortext = red + +[Syntax] +background = base +text = default +line = #f3dbdd +link = #5159cc +headertext = default +headertag = default:128 +emphasis = #c05858 +dialog = #c53030 +altdialog = #c45522 +note = #9f58ad +hidden = faded +shortcode = #9f58ad +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..b9fe7486 --- /dev/null +++ b/novelwriter/assets/themes/ruby_night.conf @@ -0,0 +1,74 @@ +[Main] +name = Ruby Night +mode = dark +description = Ruby +author = Myian + +[Base] +base = #18181C +default = #dcd9e6 +faded = #817b94 +red = #ec6262 +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 = purple +linkvisited = purple +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 = #ff6969 +altdialog = orange +note = purple +hidden = faded +shortcode = purple +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/snazzy.conf b/novelwriter/assets/themes/snazzy.conf index 39ec3c87..3838eb26 100644 --- a/novelwriter/assets/themes/snazzy.conf +++ b/novelwriter/assets/themes/snazzy.conf @@ -1,48 +1,89 @@ [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 +description = A vivid light color theme +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..c328d12b 100644 --- a/novelwriter/assets/themes/solarized_dark.conf +++ b/novelwriter/assets/themes/solarized_dark.conf @@ -1,48 +1,95 @@ [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 +description = Precision colors for machines and people +author = nullbasis, 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..dbc20986 100644 --- a/novelwriter/assets/themes/solarized_light.conf +++ b/novelwriter/assets/themes/solarized_light.conf @@ -1,48 +1,95 @@ [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 +description = Precision colors for machines and people +author = nullbasis, 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/tango_dark.conf b/novelwriter/assets/themes/tango_dark.conf new file mode 100644 index 00000000..40f733c4 --- /dev/null +++ b/novelwriter/assets/themes/tango_dark.conf @@ -0,0 +1,90 @@ +[Main] +name = Tango Dark +mode = dark +description = Colors for everyone +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..81c34c11 --- /dev/null +++ b/novelwriter/assets/themes/tango_light.conf @@ -0,0 +1,90 @@ +[Main] +name = Tango Light +mode = light +description = Colors for everyone +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..97872b2b --- /dev/null +++ b/novelwriter/assets/themes/tomorrow.conf @@ -0,0 +1,96 @@ +[Main] +name = Tomorrow +mode = light +description = A colourful theme with muted colours and sensible highlighting +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..8424a1a3 --- /dev/null +++ b/novelwriter/assets/themes/tomorrow_night.conf @@ -0,0 +1,96 @@ +[Main] +name = Tomorrow Night +mode = dark +description = A colourful theme with muted colours and sensible highlighting +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..ab1692c7 --- /dev/null +++ b/novelwriter/assets/themes/tomorrow_night_blue.conf @@ -0,0 +1,96 @@ +[Main] +name = Tomorrow Night Blue +mode = dark +description = A colourful theme with muted colours and sensible highlighting +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..3fd7d44a --- /dev/null +++ b/novelwriter/assets/themes/tomorrow_night_bright.conf @@ -0,0 +1,96 @@ +[Main] +name = Tomorrow Night Bright +mode = dark +description = A colourful theme with muted colours and sensible highlighting +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..4a72bcf5 --- /dev/null +++ b/novelwriter/assets/themes/tomorrow_night_eighties.conf @@ -0,0 +1,96 @@ +[Main] +name = Tomorrow Night Eighties +mode = dark +description = A colourful theme with muted colours and sensible highlighting +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/common.py b/novelwriter/common.py index 2ad42935..f44ce08e 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -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 @@ -676,6 +677,9 @@ def openExternalPath(path: Path) -> bool: # Classes ## +_T_Enum = TypeVar("_T_Enum", bound=Enum) + + class NWConfigParser(ConfigParser): """Common: Adapted Config Parser @@ -737,3 +741,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..93d8fb02 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -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,8 +56,8 @@ 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" @@ -69,22 +70,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 +155,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 +181,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 +192,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 @@ -206,7 +210,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 +249,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 @@ -403,7 +408,7 @@ 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 @@ -551,11 +556,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() @@ -626,8 +630,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 +670,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 +714,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 +728,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 +758,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 +798,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 +842,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), diff --git a/novelwriter/constants.py b/novelwriter/constants.py index 191bcc1c..e6aaa360 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -28,7 +28,8 @@ 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 ) @@ -76,6 +77,7 @@ class nwRegEx: FMT_EI = r"(? 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) + 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) return def setProjectLang(self, language: str | None) -> None: @@ -547,6 +547,12 @@ class NWProject: self._tree.refreshAllItems() return + def updateTheme(self) -> None: + """Update theme elements.""" + self._data.itemStatus.refreshIcons() + self._data.itemImport.refreshIcons() + return + def localLookup(self, word: str | int) -> str: """Look up a word or number in the translation map for the project and return it. The variable is cast to a string before diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py index 1e0e146b..47ea748e 100644 --- a/novelwriter/core/projectxml.py +++ b/novelwriter/core/projectxml.py @@ -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 = { @@ -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: @@ -439,12 +441,15 @@ class ProjectXMLReader: 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) # Deprecated in 1.5 R6 + green = checkInt(xEntry.attrib.get("green", 0), 0) # Deprecated in 1.5 R6 + blue = checkInt(xEntry.attrib.get("blue", 0), 0) # Deprecated 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) + if color is None: + color = f"{red}, {green}, {blue}" + sObject.add(key, xEntry.text or "", color, shape, count) return def _parseDictKeyText(self, xItem: ET.Element) -> dict: diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index f7d9b584..c3e58b76 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -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,15 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) +CUSTOM_COL = "custom" + @dataclasses.dataclass class StatusEntry: name: str color: QColor + theme: str shape: nwStatusShape icon: QIcon count: int = 0 @@ -62,7 +66,7 @@ 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"] @@ -97,15 +101,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 +116,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 @@ -157,12 +158,14 @@ class NWStatus: 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 +182,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(): + entry.color = SHARED.theme.parseColor(entry.theme) + entry.icon = NWStatus.createIcon(self._height, entry.color, entry.shape) + return + @staticmethod def createIcon(height: int, color: QColor, shape: nwStatusShape) -> QIcon: """Generate an icon for a status label.""" diff --git a/novelwriter/dialogs/docsplit.py b/novelwriter/dialogs/docsplit.py index a67aafa7..a2acd53f 100644 --- a/novelwriter/dialogs/docsplit.py +++ b/novelwriter/dialogs/docsplit.py @@ -28,13 +28,13 @@ 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 @@ -79,7 +79,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) diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index fbeb0523..f13efb40 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -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 @@ -165,22 +165,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 +254,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 +294,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 +544,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) @@ -993,18 +1001,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 +1026,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 +1066,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() diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index 623be345..071664fc 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -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 @@ -326,6 +326,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 +385,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 +441,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 +454,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) @@ -496,11 +520,20 @@ class _StatusPage(NFixedPage): 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() + return + @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 @@ -511,7 +544,8 @@ 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 @@ -537,22 +571,26 @@ 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 @@ -608,10 +646,11 @@ class _StatusPage(NFixedPage): 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 @@ -658,11 +697,18 @@ 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): diff --git a/novelwriter/enum.py b/novelwriter/enum.py index a837d824..9e50620e 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -92,35 +92,36 @@ 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): @@ -156,6 +157,13 @@ class nwFocus(Enum): OUTLINE = 3 +class nwTheme(Enum): + + AUTO = 0 + LIGHT = 1 + DARK = 2 + + class nwOutline(Enum): TITLE = 0 diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index 9bea4c78..4b2f663e 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -117,12 +117,19 @@ class NTreeView(QTreeView): class NComboBox(QComboBox): - def __init__(self, parent: QWidget | None = None) -> None: + def __init__(self, parent: QWidget | None = None, maxItems: int = 15) -> None: super().__init__(parent=parent) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + 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;}") + return def wheelEvent(self, event: QWheelEvent) -> None: + """Only capture the mouse wheel if the widget has focus.""" if self.hasFocus(): super().wheelEvent(event) else: diff --git a/novelwriter/extensions/novelselector.py b/novelwriter/extensions/novelselector.py index ee585a0d..e3b1e93f 100644 --- a/novelwriter/extensions/novelselector.py +++ b/novelwriter/extensions/novelselector.py @@ -25,14 +25,19 @@ 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__) diff --git a/novelwriter/extensions/pagedsidebar.py b/novelwriter/extensions/pagedsidebar.py index b1745816..89df0c7f 100644 --- a/novelwriter/extensions/pagedsidebar.py +++ b/novelwriter/extensions/pagedsidebar.py @@ -156,7 +156,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: diff --git a/novelwriter/extensions/switch.py b/novelwriter/extensions/switch.py index 4699d50a..0a11f52f 100644 --- a/novelwriter/extensions/switch.py +++ b/novelwriter/extensions/switch.py @@ -33,7 +33,7 @@ from novelwriter.types import QtNoPen, QtPaintAntiAlias, QtSizeFixed class NSwitch(QAbstractButton): - __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 +44,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) @@ -97,7 +100,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) diff --git a/novelwriter/formats/shared.py b/novelwriter/formats/shared.py index 8a2ab5e6..3f7b3a42 100644 --- a/novelwriter/formats/shared.py +++ b/novelwriter/formats/shared.py @@ -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..1c7a2a1f 100644 --- a/novelwriter/formats/todocx.py +++ b/novelwriter/formats/todocx.py @@ -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: @@ -296,7 +296,7 @@ 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: diff --git a/novelwriter/formats/tohtml.py b/novelwriter/formats/tohtml.py index c7bf7b17..4614050a 100644 --- a/novelwriter/formats/tohtml.py +++ b/novelwriter/formats/tohtml.py @@ -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: @@ -159,34 +159,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 +228,7 @@ class ToHtml(Tokenizer): elif tType == BlockTyp.SKIP: lines.append(f" 

    \n") - elif tType == BlockTyp.COMMENT: + elif tType in COMMENT_BLOCKS: lines.append(f"

    {self._formatText(tText, tFmt)}

    \n") elif tType == BlockTyp.KEYWORD: @@ -288,26 +287,25 @@ class ToHtml(Tokenizer): json.dump(data, fObj, indent=2) else: + html = [] + html.append("") + html.append("") + html.append("") + html.append(f"{self._project.data.name:s}") + html.append("") + if self._cssStyles: + html.append("") + html.append("") + html.append("") + html.append("") + html.append(("".join(self._pages)).replace("\t", " ").rstrip()) + html.append("") + html.append("\n") + with open(path, mode="w", encoding="utf-8") as fObj: - fObj.write(( - "\n" - "\n" - "\n" - "\n" - "{title:s}\n" - "\n" - "\n" - "\n" - "{body:s}\n" - "\n" - "\n" - ).format( - title=self._project.data.name, - style="\n".join(self.getStyleSheet()), - body=("".join(self._pages)).replace("\t", " ").rstrip(), - )) + fObj.write("\n".join(html)) logger.info("Wrote file: %s", path) @@ -410,7 +408,11 @@ class ToHtml(Tokenizer): # isn't already open, and only closed if it has previously been opened. tags: list[tuple[int, str]] = [] state = dict.fromkeys(HTML_OPENER, False) + plain = not self._cssStyles for pos, fmt, data in tFmt: + if plain and fmt in (TextFmt.COL_B, TextFmt.COL_E): + # We ignore colour tags if CSS is off + continue if m := HTML_OPENER.get(fmt): if not state.get(fmt, True): if fmt == TextFmt.COL_B and (color := self._classes.get(data)): diff --git a/novelwriter/formats/tokenizer.py b/novelwriter/formats/tokenizer.py index e0587002..b4cce890 100644 --- a/novelwriter/formats/tokenizer.py +++ b/novelwriter/formats/tokenizer.py @@ -71,11 +71,23 @@ COMMENT_STYLE = { nwComment.COMMENT: ComStyle(), nwComment.STORY: ComStyle("Story Structure", "modifier", "note"), } -HEADINGS = [ +COMMENT_TYPE = { + nwComment.PLAIN: BlockTyp.COMMENT, + nwComment.IGNORE: BlockTyp.COMMENT, + nwComment.SYNOPSIS: BlockTyp.SUMMARY, + nwComment.SHORT: BlockTyp.SUMMARY, + nwComment.NOTE: BlockTyp.NOTE, + nwComment.FOOTNOTE: BlockTyp.COMMENT, + nwComment.COMMENT: BlockTyp.COMMENT, + nwComment.STORY: BlockTyp.NOTE, +} +HEADING_BLOCKS = [ BlockTyp.TITLE, BlockTyp.PART, BlockTyp.HEAD1, BlockTyp.HEAD2, BlockTyp.HEAD3, BlockTyp.HEAD4, ] -SKIP_INDENT = [*HEADINGS, BlockTyp.SEP, BlockTyp.SKIP] +COMMENT_BLOCKS = (BlockTyp.COMMENT, BlockTyp.SUMMARY, BlockTyp.NOTE) +META_BLOCKS = (BlockTyp.COMMENT, BlockTyp.SUMMARY, BlockTyp.NOTE, BlockTyp.KEYWORD) +SKIP_INDENT = [*HEADING_BLOCKS, BlockTyp.SEP, BlockTyp.SKIP] B_EMPTY: T_Block = (BlockTyp.EMPTY, "", "", [], BlockFmt.NONE) @@ -189,6 +201,7 @@ class Tokenizer(ABC): (REGEX_PATTERNS.markdownItalic, [0, TextFmt.I_B, 0, TextFmt.I_E]), (REGEX_PATTERNS.markdownBold, [0, TextFmt.B_B, 0, TextFmt.B_E]), (REGEX_PATTERNS.markdownStrike, [0, TextFmt.D_B, 0, TextFmt.D_E]), + (REGEX_PATTERNS.markdownMark, [0, TextFmt.M_B, 0, TextFmt.M_E]), ] self._shortCodeFmt = { @@ -618,7 +631,7 @@ class Tokenizer(ABC): bStyle = COMMENT_STYLE[cStyle] tLine, tFmt = self._formatComment(bStyle, cKey, cText) tBlocks.append(( - BlockTyp.COMMENT, "", tLine, tFmt, tStyle + COMMENT_TYPE[cStyle], "", tLine, tFmt, tStyle )) elif cStyle == nwComment.FOOTNOTE: @@ -862,12 +875,12 @@ class Tokenizer(ABC): # We don't need to keep the empty lines after this pass pass - elif cBlock[0] == BlockTyp.KEYWORD: - # Adjust margins for lines in a list of keyword lines + elif cBlock[0] in (BlockTyp.KEYWORD, BlockTyp.NOTE): + # Adjust margins for lines in repeated meta blocks aStyle = cBlock[4] - if pBlock[0] == BlockTyp.KEYWORD: + if pBlock[0] == cBlock[0]: aStyle |= BlockFmt.Z_TOP - if nBlock[0] == BlockTyp.KEYWORD: + if nBlock[0] == cBlock[0]: aStyle |= BlockFmt.Z_BTM sBlocks.append(( cBlock[0], cBlock[1], cBlock[2], cBlock[3], aStyle @@ -991,7 +1004,7 @@ class Tokenizer(ABC): allWordChars += nPWChars textWordChars += nPWChars - elif tType in HEADINGS: + elif tType in HEADING_BLOCKS: titleCount += 1 allWords += nWords titleWords += nWords @@ -1005,7 +1018,7 @@ class Tokenizer(ABC): allChars += nChars allWordChars += nWChars - elif tType in (BlockTyp.COMMENT, BlockTyp.KEYWORD): + elif tType in META_BLOCKS: words = tText.split() allWords += len(words) allChars += len(tText) diff --git a/novelwriter/formats/tomarkdown.py b/novelwriter/formats/tomarkdown.py index b87d0325..0b477302 100644 --- a/novelwriter/formats/tomarkdown.py +++ b/novelwriter/formats/tomarkdown.py @@ -29,7 +29,7 @@ from typing import TYPE_CHECKING from novelwriter.constants import nwUnicode from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt -from novelwriter.formats.tokenizer import Tokenizer +from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer if TYPE_CHECKING: from pathlib import Path @@ -144,7 +144,7 @@ class ToMarkdown(Tokenizer): elif tType == BlockTyp.SKIP: lines.append(f"{cSkip}\n\n") - elif tType == BlockTyp.COMMENT: + elif tType in COMMENT_BLOCKS: lines.append(f"{self._formatText(tText, tFormat, mTags)}\n\n") elif tType == BlockTyp.KEYWORD: diff --git a/novelwriter/formats/toodt.py b/novelwriter/formats/toodt.py index 15622845..ddd554ba 100644 --- a/novelwriter/formats/toodt.py +++ b/novelwriter/formats/toodt.py @@ -40,7 +40,7 @@ from novelwriter import __version__ from novelwriter.common import xmlElement, xmlIndent, xmlSubElem from novelwriter.constants import nwHeadFmt, nwStyles from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt, stripEscape -from novelwriter.formats.tokenizer import Tokenizer +from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer from novelwriter.types import FONT_STYLE, QtHexRgb if TYPE_CHECKING: @@ -389,7 +389,7 @@ class ToOdt(Tokenizer): elif tType == BlockTyp.SKIP: self._addTextPar(xText, S_TEXT, oStyle, "") - elif tType == BlockTyp.COMMENT: + elif tType in COMMENT_BLOCKS: self._addTextPar(xText, S_META, oStyle, tText, tFmt=tFormat) elif tType == BlockTyp.KEYWORD: diff --git a/novelwriter/formats/toqdoc.py b/novelwriter/formats/toqdoc.py index 69c25fa4..28186b69 100644 --- a/novelwriter/formats/toqdoc.py +++ b/novelwriter/formats/toqdoc.py @@ -37,7 +37,7 @@ from PyQt6.QtPrintSupport import QPrinter from novelwriter import __version__ from novelwriter.constants import nwStyles, nwUnicode from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape -from novelwriter.formats.tokenizer import HEADINGS, Tokenizer +from novelwriter.formats.tokenizer import HEADING_BLOCKS, META_BLOCKS, Tokenizer from novelwriter.types import ( QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight, QtKeepAnchor, QtMoveAnchor, QtPageBreakAfter, QtPageBreakAuto, @@ -214,7 +214,7 @@ class ToQTextDocument(Tokenizer): for tType, tMeta, tText, tFormat, tStyle in self._blocks: bFmt = QTextBlockFormat(self._blockFmt) - if tType in (BlockTyp.COMMENT, BlockTyp.KEYWORD): + if tType in META_BLOCKS: bFmt.setTopMargin(self._mMeta[0]) bFmt.setBottomMargin(self._mMeta[1]) elif tType == BlockTyp.SEP: @@ -248,17 +248,21 @@ class ToQTextDocument(Tokenizer): if tStyle & BlockFmt.IND_T: bFmt.setTextIndent(self._tIndent) - if tType in (BlockTyp.TEXT, BlockTyp.COMMENT, BlockTyp.KEYWORD): + if tType == BlockTyp.TEXT: newBlock(cursor, bFmt) self._insertFragments(tText, tFormat, cursor, self._charFmt) - elif tType in HEADINGS: + elif tType in HEADING_BLOCKS: bFmt, cFmt = self._genHeadStyle(tType, tMeta, bFmt) for tPart in tText.split("\n"): newBlock(cursor, bFmt) cursor.insertText(tPart, cFmt) bFmt.setPageBreakPolicy(QtPageBreakAuto) + elif tType in META_BLOCKS: + newBlock(cursor, bFmt) + self._insertFragments(tText, tFormat, cursor, self._charFmt) + elif tType == BlockTyp.SEP: newBlock(cursor, bFmt) cursor.insertText(tText, self._charFmt) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 846b6317..9f0f7bfd 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -45,11 +45,11 @@ from PyQt6.QtCore import ( from PyQt6.QtGui import ( QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent, QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QShortcut, - QTextBlock, QTextCursor, QTextDocument, QTextOption + QTextBlock, QTextCursor, QTextDocument, QTextFormat, QTextOption ) from PyQt6.QtWidgets import ( QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu, - QPlainTextEdit, QToolBar, QVBoxLayout, QWidget + QPlainTextEdit, QTextEdit, QToolBar, QVBoxLayout, QWidget ) from novelwriter import CONFIG, SHARED @@ -75,7 +75,8 @@ from novelwriter.tools.lipsum import GuiLipsum from novelwriter.types import ( QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop, QtAlignRight, QtKeepAnchor, QtModCtrl, QtModNone, QtModShift, QtMouseLeft, - QtMoveAnchor, QtMoveLeft, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded + QtMoveAnchor, QtMoveLeft, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded, + QtTransparent ) logger = logging.getLogger(__name__) @@ -145,6 +146,8 @@ class GuiDocEditor(QPlainTextEdit): self._lastActive = 0.0 # Timestamp of last activity self._lastFind = None # Position of the last found search word self._doReplace = False # Switch to temporarily disable auto-replace + self._lineColor = QtTransparent + self._selection = QTextEdit.ExtraSelection() # Auto-Replace self._autoReplace = TextAutoReplace() @@ -283,6 +286,7 @@ class GuiDocEditor(QPlainTextEdit): self.docHeader.clearHeader() self.docFooter.setHandle(self._docHandle) self.docToolBar.setVisible(False) + self.setExtraSelections([]) self.itemHandleChanged.emit("") @@ -315,6 +319,10 @@ class GuiDocEditor(QPlainTextEdit): self.docHeader.matchColors() self.docFooter.matchColors() + self._lineColor = syntax.line + self._selection.format.setBackground(self._lineColor) + self._selection.format.setProperty(QTextFormat.Property.FullWidthSelection, True) + return def initEditor(self) -> None: @@ -372,6 +380,8 @@ class GuiDocEditor(QPlainTextEdit): # Refresh sizes self.setTabStopDistance(CONFIG.tabWidth) self.setCursorWidth(CONFIG.cursorWidth) + self.setExtraSelections([]) + self._cursorMoved() # If we have a document open, we should refresh it in case the # font changed, otherwise we just clear the editor entirely, @@ -733,6 +743,8 @@ class GuiDocEditor(QPlainTextEdit): self._toggleFormat(2, "*") elif action == nwDocAction.MD_STRIKE: self._toggleFormat(2, "~") + elif action == nwDocAction.MD_MARK: + self._toggleFormat(2, "=") elif action == nwDocAction.S_QUOTE: self._wrapSelection(CONFIG.fmtSQuoteOpen, CONFIG.fmtSQuoteClose) elif action == nwDocAction.D_QUOTE: @@ -1110,6 +1122,10 @@ class GuiDocEditor(QPlainTextEdit): def _cursorMoved(self) -> None: """Triggered when the cursor moved in the editor.""" self.docFooter.updateLineCount(self.textCursor()) + if CONFIG.lineHighlight: + self._selection.cursor = self.textCursor() + self._selection.cursor.clearSelection() + self.setExtraSelections([self._selection]) return @pyqtSlot(int, int, str) @@ -2405,6 +2421,12 @@ class GuiDocToolBar(QWidget): qtLambda(self.requestDocAction.emit, nwDocAction.MD_STRIKE) ) + self.tbMarkMD = NIconToolButton(self, iSz) + self.tbMarkMD.setToolTip(self.tr("Markdown Highlight")) + self.tbMarkMD.clicked.connect( + qtLambda(self.requestDocAction.emit, nwDocAction.MD_MARK) + ) + self.tbBold = NIconToolButton(self, iSz) self.tbBold.setToolTip(self.tr("Shortcode Bold")) self.tbBold.clicked.connect( @@ -2454,6 +2476,7 @@ class GuiDocToolBar(QWidget): self.outerBox.addWidget(self.tbBoldMD) self.outerBox.addWidget(self.tbItalicMD) self.outerBox.addWidget(self.tbStrikeMD) + self.outerBox.addWidget(self.tbMarkMD) self.outerBox.addSpacing(4) self.outerBox.addWidget(self.tbBold) self.outerBox.addWidget(self.tbItalic) @@ -2488,6 +2511,7 @@ class GuiDocToolBar(QWidget): self.tbBoldMD.setThemeIcon("fmt_bold", "orange") self.tbItalicMD.setThemeIcon("fmt_italic", "orange") self.tbStrikeMD.setThemeIcon("fmt_strike", "orange") + self.tbMarkMD.setThemeIcon("fmt_mark", "orange") self.tbBold.setThemeIcon("fmt_bold") self.tbItalic.setThemeIcon("fmt_italic") self.tbStrike.setThemeIcon("fmt_strike") diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py index 8103fd79..d013b807 100644 --- a/novelwriter/gui/dochighlight.py +++ b/novelwriter/gui/dochighlight.py @@ -110,6 +110,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): self._addCharFormat("bold", colEmph, "b") self._addCharFormat("italic", colEmph, "i") self._addCharFormat("strike", syntax.hidden, "s") + self._addCharFormat("mark", syntax.mark, "bg") self._addCharFormat("mspaces", syntax.error, "err") self._addCharFormat("nobreak", colBreak, "bg") self._addCharFormat("altdialog", syntax.dialA) @@ -196,6 +197,17 @@ class GuiDocHighlighter(QSyntaxHighlighter): self._txtRules.append((rxRule, hlRule)) self._cmnRules.append((rxRule, hlRule)) + # Markdown Highlight + rxRule = REGEX_PATTERNS.markdownMark + hlRule = { + 1: self._hStyles["markup"], + 2: self._hStyles["mark"], + 3: self._hStyles["markup"], + } + self._minRules.append((rxRule, hlRule)) + self._txtRules.append((rxRule, hlRule)) + self._cmnRules.append((rxRule, hlRule)) + # Shortcodes rxRule = REGEX_PATTERNS.shortcodePlain hlRule = { @@ -437,9 +449,6 @@ class GuiDocHighlighter(QSyntaxHighlighter): charFormat = QTextCharFormat() charFormat.setFontStyleName(name) - if color: - charFormat.setForeground(color) - if style: styles = style.split(",") if "b" in styles: @@ -455,6 +464,10 @@ class GuiDocHighlighter(QSyntaxHighlighter): charFormat.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SpellCheckUnderline) if "bg" in styles and color is not None: charFormat.setBackground(QBrush(color, Qt.BrushStyle.SolidPattern)) + color = None + + if color: + charFormat.setForeground(color) if size: charFormat.setFontPointSize(round(size*CONFIG.textFont.pointSize())) diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 4b43937b..d5445536 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -228,11 +228,11 @@ class GuiDocViewer(QTextBrowser): qDoc.setTheme(self._docTheme) qDoc.initDocument() qDoc.setKeywords(True) - qDoc.setCommentType(nwComment.NOTE, CONFIG.viewComments) - qDoc.setCommentType(nwComment.STORY, CONFIG.viewComments) qDoc.setCommentType(nwComment.PLAIN, CONFIG.viewComments) qDoc.setCommentType(nwComment.SYNOPSIS, CONFIG.viewSynopsis) qDoc.setCommentType(nwComment.SHORT, CONFIG.viewSynopsis) + qDoc.setCommentType(nwComment.STORY, CONFIG.viewNotes) + qDoc.setCommentType(nwComment.NOTE, CONFIG.viewNotes) # Be extra careful here to prevent crashes when first opening a # project as a crash here leaves no way of recovering. @@ -913,12 +913,23 @@ class GuiDocViewFooter(QWidget): self.showSynopsis.toggled.connect(self._doToggleSynopsis) self.showSynopsis.setToolTip(self.tr("Show Synopsis Comments")) + # Show Notes + self.showNotes = QToolButton(self) + self.showNotes.setText(self.tr("Notes")) + self.showNotes.setCheckable(True) + self.showNotes.setChecked(CONFIG.viewNotes) + self.showNotes.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) + self.showNotes.setIconSize(iSz) + self.showNotes.toggled.connect(self._doToggleNotes) + self.showNotes.setToolTip(self.tr("Show Notes")) + # Assemble Layout self.outerBox = QHBoxLayout() self.outerBox.addWidget(self.showHide, 0) self.outerBox.addStretch(1) self.outerBox.addWidget(self.showComments, 0) self.outerBox.addWidget(self.showSynopsis, 0) + self.outerBox.addWidget(self.showNotes, 0) self.outerBox.setSpacing(4) self.setLayout(self.outerBox) @@ -944,6 +955,7 @@ class GuiDocViewFooter(QWidget): self.setFont(SHARED.theme.guiFont) self.showComments.setFont(SHARED.theme.guiFontSmall) self.showSynopsis.setFont(SHARED.theme.guiFontSmall) + self.showNotes.setFont(SHARED.theme.guiFontSmall) return def updateTheme(self) -> None: @@ -955,11 +967,13 @@ class GuiDocViewFooter(QWidget): self.showHide.setThemeIcon("panel") self.showComments.setIcon(bulletIcon) self.showSynopsis.setIcon(bulletIcon) + self.showNotes.setIcon(bulletIcon) buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) self.showHide.setStyleSheet(buttonStyle) self.showComments.setStyleSheet(buttonStyle) self.showSynopsis.setStyleSheet(buttonStyle) + self.showNotes.setStyleSheet(buttonStyle) self.matchColors() @@ -994,3 +1008,10 @@ class GuiDocViewFooter(QWidget): CONFIG.viewSynopsis = state self.docViewer.reloadText() return + + @pyqtSlot(bool) + def _doToggleNotes(self, state: bool) -> None: + """Toggle the view notes button and reload the document.""" + CONFIG.viewNotes = state + self.docViewer.reloadText() + return diff --git a/novelwriter/gui/itemdetails.py b/novelwriter/gui/itemdetails.py index 9bece183..7bffb923 100644 --- a/novelwriter/gui/itemdetails.py +++ b/novelwriter/gui/itemdetails.py @@ -233,14 +233,8 @@ class GuiItemDetails(QWidget): # Label # ===== - if nwItem.isFileType(): - if nwItem.isActive: - self.labelIcon.setPixmap(SHARED.theme.getPixmap("checked", (iPx, iPx), "green")) - else: - self.labelIcon.setPixmap(SHARED.theme.getPixmap("unchecked", (iPx, iPx), "red")) - else: - self.labelIcon.setPixmap(SHARED.theme.getPixmap("noncheckable", (iPx, iPx), "faded")) - + _, icon = nwItem.getActiveStatus() + self.labelIcon.setPixmap(icon.pixmap(iPx, iPx)) self.labelData.setText(elide(nwItem.itemName, 100)) # Status diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index 33f97dde..3d0ab1c5 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -675,6 +675,14 @@ class GuiMainMenu(QMenuBar): ) self.mainGui.addAction(self.aFmtStrike) + # Format > Highlight + self.aFmtMark = qtAddAction(self.fmtMenu, self.tr("Highlight")) + self.aFmtMark.setShortcut("Ctrl+M") + self.aFmtMark.triggered.connect( + lambda: self.requestDocAction.emit(nwDocAction.MD_MARK) + ) + self.mainGui.addAction(self.aFmtStrike) + # Edit > Separator self.fmtMenu.addSeparator() diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py index f784623f..e0ccb9e5 100644 --- a/novelwriter/gui/sidebar.py +++ b/novelwriter/gui/sidebar.py @@ -27,12 +27,13 @@ import logging from typing import TYPE_CHECKING -from PyQt6.QtCore import QEvent, QPoint, QSize, pyqtSignal +from PyQt6.QtCore import QEvent, QPoint, QSize, pyqtSignal, pyqtSlot from PyQt6.QtWidgets import QMenu, QVBoxLayout, QWidget -from novelwriter import SHARED +from novelwriter import CONFIG, SHARED from novelwriter.common import qtLambda -from novelwriter.enum import nwView +from novelwriter.constants import nwLabels, trConst +from novelwriter.enum import nwTheme, nwView from novelwriter.extensions.eventfilters import StatusTipFilter from novelwriter.extensions.modified import NIconToolButton from novelwriter.gui.theme import STYLES_BIG_TOOLBUTTON @@ -77,9 +78,9 @@ class GuiSideBar(QWidget): self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View"))) self.tbOutline.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.OUTLINE)) - self.tbBuild = NIconToolButton(self, iSz) - self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript"))) - self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog) + self.tbTheme = NIconToolButton(self, iSz) + self.tbTheme.setToolTip(self.tr("Switch Colour Theme")) + self.tbTheme.clicked.connect(self._cycleColurTheme) self.tbDetails = NIconToolButton(self, iSz) self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Novel Details"))) @@ -89,6 +90,10 @@ class GuiSideBar(QWidget): self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics"))) self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog) + self.tbBuild = NIconToolButton(self, iSz) + self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript"))) + self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog) + # Settings Menu self.tbSettings = NIconToolButton(self, iSz) self.tbSettings.setToolTip(self.tr("Settings")) @@ -111,6 +116,7 @@ class GuiSideBar(QWidget): self.outerBox.addStretch(1) self.outerBox.addWidget(self.tbDetails) self.outerBox.addWidget(self.tbStats) + self.outerBox.addWidget(self.tbTheme) self.outerBox.addWidget(self.tbSettings) self.outerBox.setContentsMargins(0, 0, 0, 0) self.outerBox.setSpacing(6) @@ -133,6 +139,7 @@ class GuiSideBar(QWidget): self.tbBuild.setStyleSheet(buttonStyle) self.tbDetails.setStyleSheet(buttonStyle) self.tbStats.setStyleSheet(buttonStyle) + self.tbTheme.setStyleSheet(buttonStyle) self.tbSettings.setStyleSheet(buttonStyle) self.tbProject.setThemeIcon("sb_project") @@ -144,6 +151,36 @@ class GuiSideBar(QWidget): self.tbStats.setThemeIcon("sb_stats") self.tbSettings.setThemeIcon("settings") + self._setThemeModeIcon() + + return + + ## + # Private Slots + ## + + @pyqtSlot() + def _cycleColurTheme(self) -> None: + """Go to nex colour theme.""" + match CONFIG.themeMode: + case nwTheme.AUTO: + CONFIG.themeMode = nwTheme.LIGHT + case nwTheme.LIGHT: + CONFIG.themeMode = nwTheme.DARK + case nwTheme.DARK: + CONFIG.themeMode = nwTheme.AUTO + self.mainGui.checkThemeUpdate() + self._setThemeModeIcon() + return + + ## + # Internal Functions + ## + + def _setThemeModeIcon(self) -> None: + """Set the theme button icon.""" + self.tbTheme.setThemeIcon(nwLabels.THEME_MODE_ICON[CONFIG.themeMode]) + self.tbTheme.setToolTip(trConst(nwLabels.THEME_MODE_LABEL[CONFIG.themeMode])) return diff --git a/novelwriter/gui/statusbar.py b/novelwriter/gui/statusbar.py index 3239fd4a..315633da 100644 --- a/novelwriter/gui/statusbar.py +++ b/novelwriter/gui/statusbar.py @@ -143,9 +143,9 @@ class GuiMainStatus(QStatusBar): self.idlePixmap = SHARED.theme.getPixmap("timer_off", (iPx, iPx)) self.timeIcon.setPixmap(self.timePixmap) - colNone = SHARED.theme.getIconColor("default").darker(150) - colSaved = SHARED.theme.getIconColor("green").darker(150) - colUnsaved = SHARED.theme.getIconColor("red").darker(150) + colNone = SHARED.theme.getBaseColor("default").darker(150) + colSaved = SHARED.theme.getBaseColor("green").darker(150) + colUnsaved = SHARED.theme.getBaseColor("red").darker(150) self.docIcon.setColors(colNone, colSaved, colUnsaved) self.projIcon.setColors(colNone, colSaved, colUnsaved) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 1eb2e487..cbd4166d 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -26,21 +26,23 @@ from __future__ import annotations import logging +from configparser import ConfigParser +from dataclasses import dataclass from math import ceil from typing import TYPE_CHECKING, Final from PyQt6.QtCore import QSize, Qt from PyQt6.QtGui import ( - QColor, QFont, QFontDatabase, QFontMetrics, QIcon, QPainter, QPainterPath, - QPalette, QPixmap + QColor, QFont, QFontDatabase, QFontMetrics, QGuiApplication, QIcon, + QPainter, QPainterPath, QPalette, QPixmap ) from PyQt6.QtWidgets import QApplication from novelwriter import CONFIG -from novelwriter.common import NWConfigParser, minmax -from novelwriter.config import DEF_GUI, DEF_ICONS, DEF_SYNTAX +from novelwriter.common import checkInt, minmax +from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS, DEF_TREECOL from novelwriter.constants import nwLabels -from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType +from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwTheme from novelwriter.error import logException from novelwriter.types import QtBlack, QtHexArgb, QtPaintAntiAlias, QtTransparent @@ -54,21 +56,36 @@ STYLES_MIN_TOOLBUTTON = "minimalToolButton" STYLES_BIG_TOOLBUTTON = "bigToolButton" +@dataclass +class ThemeEntry: + + name: str + dark: bool + path: Path + + class ThemeMeta: name: str = "" + mode: str = "" description: str = "" author: str = "" credit: str = "" url: str = "" - license: str = "" - licenseUrl: str = "" + + +class IconsMeta: + + name: str = "" + author: str = "" + license: str = "" class SyntaxColors: back: QColor = QColor(255, 255, 255) text: QColor = QColor(0, 0, 0) + line: QColor = QColor(0, 0, 0) link: QColor = QColor(0, 0, 0) head: QColor = QColor(0, 0, 0) headH: QColor = QColor(0, 0, 0) @@ -96,45 +113,41 @@ class GuiTheme: """ __slots__ = ( - "_availSyntax", "_availThemes", "_guiPalette", "_styleSheets", "_syntaxList", "_themeList", - "baseButtonHeight", "baseIconHeight", "baseIconSize", "buttonIconSize", "errorText", - "fadedText", "fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration", - "getHeaderDecorationNarrow", "getIcon", "getIconColor", "getItemIcon", "getPixmap", - "getToggleIcon", "guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", - "helpText", "iconCache", "isDarkTheme", "syntaxMeta", "syntaxTheme", "textNHeight", - "textNWidth", "themeMeta", + "_allThemes", "_currentTheme", "_darkThemes", "_guiPalette", "_lightThemes", "_meta", + "_qColors", "_styleSheets", "_svgColors", "_syntaxList", "accentCol", "baseButtonHeight", + "baseIconHeight", "baseIconSize", "buttonIconSize", "errorText", "fadedText", + "fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration", + "getHeaderDecorationNarrow", "getIcon", "getItemIcon", "getPixmap", "getToggleIcon", + "guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", "helpText", + "iconCache", "isDarkTheme", "syntaxTheme", "textNHeight", "textNWidth", ) def __init__(self) -> None: - self.iconCache = GuiIcons(self) - - # GUI Theme - self.themeMeta = ThemeMeta() + # Theme Objects + self.iconCache = GuiIcons(self) + self.syntaxTheme = SyntaxColors() self.isDarkTheme = False - # Special Text Colours + # Special Colours self.helpText = QColor(0, 0, 0) self.fadedText = QColor(0, 0, 0) self.errorText = QColor(255, 0, 0) + self.accentCol = QColor(255, 0, 255) # Needed until we move to Qt 6.6 - # Syntax Theme - self.syntaxMeta = ThemeMeta() - self.syntaxTheme = SyntaxColors() - - # Load Themes + # Theme Data + self._meta = ThemeMeta() + self._currentTheme = "" self._guiPalette = QPalette() - self._themeList: list[tuple[str, str]] = [] - self._syntaxList: list[tuple[str, str]] = [] - self._availThemes: dict[str, Path] = {} - self._availSyntax: dict[str, Path] = {} + self._allThemes: dict[str, ThemeEntry] = {} self._styleSheets: dict[str, str] = {} + self._svgColors: dict[str, bytes] = {} + self._qColors: dict[str, QColor] = {} # Icon Functions self.getIcon = self.iconCache.getIcon self.getPixmap = self.iconCache.getPixmap self.getItemIcon = self.iconCache.getItemIcon - self.getIconColor = self.iconCache.getIconColor self.getToggleIcon = self.iconCache.getToggleIcon self.getDecoration = self.iconCache.getDecoration self.getHeaderDecoration = self.iconCache.getHeaderDecoration @@ -178,19 +191,19 @@ class GuiTheme: logger.debug("Text 'N' Height: %d", self.textNHeight) logger.debug("Text 'N' Width: %d", self.textNWidth) - # Process Themes - _listConf(self._availSyntax, CONFIG.assetPath("syntax"), ".conf") - _listConf(self._availThemes, CONFIG.assetPath("themes"), ".conf") - _listConf(self._availSyntax, CONFIG.dataPath("syntax"), ".conf") - _listConf(self._availThemes, CONFIG.dataPath("themes"), ".conf") - - self.loadTheme() - self.loadSyntax() - return ## - # Methods + # Properties + ## + + @property + def colourThemes(self) -> dict[str, ThemeEntry]: + """Return a dictionary of all themes.""" + return self._allThemes + + ## + # Getters ## def getTextWidth(self, text: str, font: QFont | None = None) -> int: @@ -203,30 +216,79 @@ class GuiTheme: qMetrics = QFontMetrics(self.guiFont) return ceil(qMetrics.boundingRect(text).width()) + def getBaseColor(self, name: str) -> QColor: + """Return a base color.""" + return QColor(self._qColors.get(name) or QtBlack) + + def getRawBaseColor(self, name: str) -> bytes: + """Return a base color.""" + return self._svgColors.get(name, self._svgColors.get("default", b"#000000")) + ## # Theme Methods ## - def loadTheme(self) -> bool: - """Load the currently specified GUI theme.""" - theme = CONFIG.guiTheme - if theme not in self._availThemes: - logger.error("Could not find GUI theme '%s'", theme) - theme = DEF_GUI - CONFIG.guiTheme = theme + def initThemes(self) -> None: + """Initialise themes.""" + CONFIG.splashMessage("Scanning for colour themes ...") + themes: list[Path] = [] + _listContent(themes, CONFIG.assetPath("themes"), ".conf") + _listContent(themes, CONFIG.dataPath("themes"), ".conf") + self._scanThemes(themes) - if not (file := self._availThemes.get(theme)): + self.iconCache.initIcons() + self.loadTheme() + + return + + def isDesktopDarkMode(self) -> bool: + """Check if the desktop is in dark mode.""" + if CONFIG.verQtValue >= 0x060500 and (hint := QGuiApplication.styleHints()): + return hint.colorScheme() == Qt.ColorScheme.Dark + + palette = QPalette() + text = palette.windowText().color() + window = palette.window().color() + return text.lightnessF() > window.lightnessF() + + def loadTheme(self, force: bool = False) -> bool: + """Load the currently specified GUI theme. The boolean return + can be used to determine if the GUI needs refreshing. + """ + match CONFIG.themeMode: + case nwTheme.LIGHT: + darkMode = False + case nwTheme.DARK: + darkMode = True + case _: + darkMode = self.isDesktopDarkMode() + + theme = CONFIG.darkTheme if darkMode else CONFIG.lightTheme + if theme not in self._allThemes: + logger.error("Could not find theme '%s'", theme) + if darkMode: + theme = DEF_GUI_DARK + CONFIG.darkTheme = DEF_GUI_DARK + else: + theme = DEF_GUI_LIGHT + CONFIG.lightTheme = DEF_GUI_LIGHT + + if theme == self._currentTheme and not force: + logger.info("Theme '%s' is already loaded", theme) + return False + + entry = self._allThemes.get(theme) + if not entry: logger.error("Could not load GUI theme") return False - CONFIG.splashMessage("Loading GUI theme ...") + CONFIG.splashMessage(f"Loading colour theme: {entry.name}") logger.info("Loading GUI theme '%s'", theme) - parser = NWConfigParser() + parser = ConfigParser() try: - with open(file, mode="r", encoding="utf-8") as fo: - parser.read_file(fo) + parser.read(entry.path, encoding="utf-8") except Exception: - logger.error("Could not read file: %s", file) + logger.error("Could not read file: %s", entry.path) logException() return False @@ -237,39 +299,42 @@ class GuiTheme: sec = "Main" meta = ThemeMeta() if parser.has_section(sec): - meta.name = parser.rdStr(sec, "name", "") - meta.description = parser.rdStr(sec, "description", "N/A") - meta.author = parser.rdStr(sec, "author", "N/A") - meta.credit = parser.rdStr(sec, "credit", "N/A") - meta.url = parser.rdStr(sec, "url", "") - meta.license = parser.rdStr(sec, "license", "N/A") - meta.licenseUrl = parser.rdStr(sec, "licenseurl", "") + meta.name = parser.get(sec, "name", fallback="") + meta.mode = parser.get(sec, "mode", fallback="light") + meta.description = parser.get(sec, "description", fallback="") + meta.author = parser.get(sec, "author", fallback="") + meta.credit = parser.get(sec, "credit", fallback="") + meta.url = parser.get(sec, "url", fallback="") - self.themeMeta = meta + self._meta = meta - # Icons - sec = "Icons" + # Base + sec = "Base" if parser.has_section(sec): - self.iconCache.setIconColor("default", self._parseColor(parser, sec, "default")) - self.iconCache.setIconColor("faded", self._parseColor(parser, sec, "faded")) - self.iconCache.setIconColor("red", self._parseColor(parser, sec, "red")) - self.iconCache.setIconColor("orange", self._parseColor(parser, sec, "orange")) - self.iconCache.setIconColor("yellow", self._parseColor(parser, sec, "yellow")) - self.iconCache.setIconColor("green", self._parseColor(parser, sec, "green")) - self.iconCache.setIconColor("aqua", self._parseColor(parser, sec, "aqua")) - self.iconCache.setIconColor("blue", self._parseColor(parser, sec, "blue")) - self.iconCache.setIconColor("purple", self._parseColor(parser, sec, "purple")) + self._setBaseColor("base", self._readColor(parser, sec, "base")) + self._setBaseColor("default", self._readColor(parser, sec, "default")) + self._setBaseColor("faded", self._readColor(parser, sec, "faded")) + self._setBaseColor("red", self._readColor(parser, sec, "red")) + self._setBaseColor("orange", self._readColor(parser, sec, "orange")) + self._setBaseColor("yellow", self._readColor(parser, sec, "yellow")) + self._setBaseColor("green", self._readColor(parser, sec, "green")) + self._setBaseColor("cyan", self._readColor(parser, sec, "cyan")) + self._setBaseColor("blue", self._readColor(parser, sec, "blue")) + self._setBaseColor("purple", self._readColor(parser, sec, "purple")) # Project sec = "Project" if parser.has_section(sec): - self.iconCache.setIconColor("root", self._parseColor(parser, sec, "root")) - self.iconCache.setIconColor("folder", self._parseColor(parser, sec, "folder")) - self.iconCache.setIconColor("file", self._parseColor(parser, sec, "file")) - self.iconCache.setIconColor("title", self._parseColor(parser, sec, "title")) - self.iconCache.setIconColor("chapter", self._parseColor(parser, sec, "chapter")) - self.iconCache.setIconColor("scene", self._parseColor(parser, sec, "scene")) - self.iconCache.setIconColor("note", self._parseColor(parser, sec, "note")) + self._setBaseColor("root", self._readColor(parser, sec, "root")) + self._setBaseColor("folder", self._readColor(parser, sec, "folder")) + self._setBaseColor("file", self._readColor(parser, sec, "file")) + self._setBaseColor("title", self._readColor(parser, sec, "title")) + self._setBaseColor("chapter", self._readColor(parser, sec, "chapter")) + self._setBaseColor("scene", self._readColor(parser, sec, "scene")) + self._setBaseColor("note", self._readColor(parser, sec, "note")) + self._setBaseColor("active", self._readColor(parser, sec, "active")) + self._setBaseColor("inactive", self._readColor(parser, sec, "inactive")) + self._setBaseColor("disabled", self._readColor(parser, sec, "disabled")) # Palette sec = "Palette" @@ -288,20 +353,46 @@ class GuiTheme: self._setPalette(parser, sec, "highlightedtext", QPalette.ColorRole.HighlightedText) self._setPalette(parser, sec, "link", QPalette.ColorRole.Link) self._setPalette(parser, sec, "linkvisited", QPalette.ColorRole.LinkVisited) + self.accentCol = self._readColor(parser, sec, "accent") # Special handling 'til Qt 6.6 # GUI sec = "GUI" if parser.has_section(sec): - self.helpText = self._parseColor(parser, sec, "helptext") - self.fadedText = self._parseColor(parser, sec, "fadedtext") - self.errorText = self._parseColor(parser, sec, "errortext") + self.helpText = self._readColor(parser, sec, "helptext") + self.fadedText = self._readColor(parser, sec, "fadedtext") + self.errorText = self._readColor(parser, sec, "errortext") + + # Syntax + sec = "Syntax" + self.syntaxTheme = SyntaxColors() + if parser.has_section(sec): + self.syntaxTheme.back = self._readColor(parser, sec, "background") + self.syntaxTheme.text = self._readColor(parser, sec, "text") + self.syntaxTheme.line = self._readColor(parser, sec, "line") + self.syntaxTheme.link = self._readColor(parser, sec, "link") + self.syntaxTheme.head = self._readColor(parser, sec, "headertext") + self.syntaxTheme.headH = self._readColor(parser, sec, "headertag") + self.syntaxTheme.emph = self._readColor(parser, sec, "emphasis") + self.syntaxTheme.dialN = self._readColor(parser, sec, "dialog") + self.syntaxTheme.dialA = self._readColor(parser, sec, "altdialog") + self.syntaxTheme.hidden = self._readColor(parser, sec, "hidden") + self.syntaxTheme.note = self._readColor(parser, sec, "note") + self.syntaxTheme.code = self._readColor(parser, sec, "shortcode") + self.syntaxTheme.key = self._readColor(parser, sec, "keyword") + self.syntaxTheme.tag = self._readColor(parser, sec, "tag") + self.syntaxTheme.val = self._readColor(parser, sec, "value") + self.syntaxTheme.opt = self._readColor(parser, sec, "optional") + self.syntaxTheme.spell = self._readColor(parser, sec, "spellcheckline") + self.syntaxTheme.error = self._readColor(parser, sec, "errorline") + self.syntaxTheme.repTag = self._readColor(parser, sec, "replacetag") + self.syntaxTheme.mod = self._readColor(parser, sec, "modifier") + self.syntaxTheme.mark = self._readColor(parser, sec, "texthighlight") # Update Dependant Colours # Based on: https://github.com/qt/qtbase/blob/dev/src/gui/kernel/qplatformtheme.cpp text = self._guiPalette.text().color() window = self._guiPalette.window().color() highlight = self._guiPalette.highlight().color() - isDark = text.lightnessF() > window.lightnessF() QtColActive = QPalette.ColorGroup.Active QtColInactive = QPalette.ColorGroup.Inactive @@ -321,8 +412,8 @@ class GuiTheme: darkOff = dark.darker(150) shadowOff = ref.darker(150) - grey = QColor(120, 120, 120) if isDark else QColor(140, 140, 140) - dimmed = QColor(130, 130, 130) if isDark else QColor(190, 190, 190) + grey = QColor(120, 120, 120) if darkMode else QColor(140, 140, 140) + dimmed = QColor(130, 130, 130) if darkMode else QColor(190, 190, 190) placeholder = QColor(text) placeholder.setAlpha(128) @@ -347,145 +438,94 @@ class GuiTheme: self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.Highlight, grey) if CONFIG.verQtValue >= 0x060600: - self._guiPalette.setBrush(QtColActive, QPalette.ColorRole.Accent, highlight) - self._guiPalette.setBrush(QtColInactive, QPalette.ColorRole.Accent, highlight) + self._guiPalette.setBrush(QtColActive, QPalette.ColorRole.Accent, self.accentCol) + self._guiPalette.setBrush(QtColInactive, QPalette.ColorRole.Accent, self.accentCol) self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.Accent, grey) + # Set project override colours + if (override := CONFIG.iconColTree) != DEF_TREECOL: + color = self._qColors.get(override, QtBlack) + self._setBaseColor("root", color) + self._setBaseColor("folder", color) + if not CONFIG.iconColDocs: + self._setBaseColor("file", color) + self._setBaseColor("title", color) + self._setBaseColor("chapter", color) + self._setBaseColor("scene", color) + self._setBaseColor("note", color) + + self.isDarkTheme = darkMode + self._currentTheme = theme + # Load icons after the theme is parsed self.iconCache.loadTheme(CONFIG.iconTheme) # Finalise - self.isDarkTheme = isDark QApplication.setPalette(self._guiPalette) self._buildStyleSheets(self._guiPalette) - CONFIG.splashMessage(f"Loaded GUI theme: {meta.name}") - return True - def loadSyntax(self) -> bool: - """Load the currently specified syntax highlighter theme.""" - theme = CONFIG.guiSyntax - if theme not in self._availSyntax: - logger.error("Could not find syntax theme '%s'", theme) - theme = DEF_SYNTAX - CONFIG.guiSyntax = theme - - if not (file := self._availSyntax.get(theme)): - logger.error("Could not load syntax theme") - return False - - CONFIG.splashMessage("Loading syntax theme ...") - logger.info("Loading syntax theme '%s'", theme) - parser = NWConfigParser() - try: - with open(file, mode="r", encoding="utf-8") as fo: - parser.read_file(fo) - except Exception: - logger.error("Could not read file: %s", file) - logException() - return False - - # Main - sec = "Main" - meta = ThemeMeta() - if parser.has_section(sec): - meta.name = parser.rdStr(sec, "name", "") - meta.description = parser.rdStr(sec, "description", "N/A") - meta.author = parser.rdStr(sec, "author", "N/A") - meta.credit = parser.rdStr(sec, "credit", "N/A") - meta.url = parser.rdStr(sec, "url", "") - meta.license = parser.rdStr(sec, "license", "N/A") - meta.licenseUrl = parser.rdStr(sec, "licenseurl", "") - - # Syntax - sec = "Syntax" - syntax = SyntaxColors() - if parser.has_section(sec): - syntax.back = self._parseColor(parser, sec, "background") - syntax.text = self._parseColor(parser, sec, "text") - syntax.link = self._parseColor(parser, sec, "link") - syntax.head = self._parseColor(parser, sec, "headertext") - syntax.headH = self._parseColor(parser, sec, "headertag") - syntax.emph = self._parseColor(parser, sec, "emphasis") - syntax.dialN = self._parseColor(parser, sec, "dialog") - syntax.dialA = self._parseColor(parser, sec, "altdialog") - syntax.hidden = self._parseColor(parser, sec, "hidden") - syntax.note = self._parseColor(parser, sec, "note") - syntax.code = self._parseColor(parser, sec, "shortcode") - syntax.key = self._parseColor(parser, sec, "keyword") - syntax.tag = self._parseColor(parser, sec, "tag") - syntax.val = self._parseColor(parser, sec, "value") - syntax.opt = self._parseColor(parser, sec, "optional") - syntax.spell = self._parseColor(parser, sec, "spellcheckline") - syntax.error = self._parseColor(parser, sec, "errorline") - syntax.repTag = self._parseColor(parser, sec, "replacetag") - syntax.mod = self._parseColor(parser, sec, "modifier") - syntax.mark = self._parseColor(parser, sec, "texthighlight") - - CONFIG.splashMessage(f"Loaded syntax theme: {meta.name}") - - self.syntaxMeta = meta - self.syntaxTheme = syntax - - return True - - def listThemes(self) -> list[tuple[str, str]]: - """Scan the GUI themes folder and list all themes.""" - if self._themeList: - return self._themeList - - themes = [] - parser = NWConfigParser() - for key, path in self._availThemes.items(): - logger.debug("Checking theme config '%s'", key) - if name := _loadInternalName(parser, path): - themes.append((key, name)) - - self._themeList = sorted(themes, key=_sortTheme) - - return self._themeList - - def listSyntax(self) -> list[tuple[str, str]]: - """Scan the syntax themes folder and list all themes.""" - if self._syntaxList: - return self._syntaxList - - themes = [] - parser = NWConfigParser() - for key, path in self._availSyntax.items(): - logger.debug("Checking theme syntax '%s'", key) - if name := _loadInternalName(parser, path): - themes.append((key, name)) - - self._syntaxList = sorted(themes, key=_sortTheme) - - return self._syntaxList - def getStyleSheet(self, name: str) -> str: """Load a standard style sheet.""" return self._styleSheets.get(name, "") + def parseColor(self, value: str, default: QColor = QtBlack) -> QColor: + """Parse a string as a colour value.""" + if value in self._qColors: + # Named colour + return self._qColors[value] + elif value.startswith("#") and len(value) == 7: + # Assume #RRGGBB + return QColor.fromString(value) + elif value.startswith("#") and len(value) == 9: + # Assume #RRGGBBAA and convert to #AARRGGBB + return QColor.fromString(f"#{value[7:9]}{value[1:7]}") + elif ":" in value: + # Colour name and lighter, darker or alpha + name, _, adjust = value.partition(":") + color = QColor(self._qColors.get(name.strip(), default)) + if adjust.startswith("L"): + color = color.lighter(checkInt(adjust[1:], 100)) + elif adjust.startswith("D"): + color = color.darker(checkInt(adjust[1:], 100)) + else: + color.setAlpha(checkInt(adjust, 255)) + return color + elif "," in value: + # Integer red, green, blue, alpha + data = value.split(",") + result = [0, 0, 0, 255] + for i in range(min(len(data), 4)): + result[i] = checkInt(data[i].strip(), result[i]) + return QColor(*result) + return default + ## # Internal Functions ## + def _setBaseColor(self, key: str, color: QColor) -> None: + """Set the colour for a named colour.""" + self._qColors[key] = QColor(color) + self._svgColors[key] = color.name(QColor.NameFormat.HexRgb).encode("utf-8") + return + def _resetTheme(self) -> None: """Reset GUI colours to default values.""" palette = QPalette() - - text = palette.color(QPalette.ColorRole.Text) - window = palette.color(QPalette.ColorRole.Window) - isDark = text.lightnessF() > window.lightnessF() + isDark = self.isDesktopDarkMode() # Reset GUI Palette + base = palette.color(QPalette.ColorRole.Base) + default = palette.color(QPalette.ColorRole.Text) faded = QColor(128, 128, 128) dimmed = QColor(130, 130, 130) if isDark else QColor(190, 190, 190) red = QColor(242, 119, 122) if isDark else QColor(240, 40, 41) orange = QColor(249, 145, 57) if isDark else QColor(245, 135, 31) yellow = QColor(255, 204, 102) if isDark else QColor(234, 183, 0) green = QColor(153, 204, 153) if isDark else QColor(113, 140, 0) - aqua = QColor(102, 204, 204) if isDark else QColor(62, 153, 159) + cyan = QColor(102, 204, 204) if isDark else QColor(62, 153, 159) blue = QColor(102, 153, 204) if isDark else QColor(66, 113, 174) purple = QColor(204, 153, 204) if isDark else QColor(137, 89, 168) @@ -496,37 +536,42 @@ class GuiTheme: self._guiPalette = palette - # Reset Icons - icons = self.iconCache - icons.clear() - icons.setIconColor("default", text) - icons.setIconColor("faded", faded) - icons.setIconColor("red", red) - icons.setIconColor("orange", orange) - icons.setIconColor("yellow", yellow) - icons.setIconColor("green", green) - icons.setIconColor("aqua", aqua) - icons.setIconColor("blue", blue) - icons.setIconColor("purple", purple) - icons.setIconColor("root", blue) - icons.setIconColor("folder", yellow) - icons.setIconColor("file", text) - icons.setIconColor("title", green) - icons.setIconColor("chapter", red) - icons.setIconColor("scene", blue) - icons.setIconColor("note", yellow) + # Reset Base Colours and Icons + self.iconCache.clear() + self._svgColors = {} + self._qColors = {} + self._setBaseColor("base", base) + self._setBaseColor("default", default) + self._setBaseColor("faded", faded) + self._setBaseColor("red", red) + self._setBaseColor("orange", orange) + self._setBaseColor("yellow", yellow) + self._setBaseColor("green", green) + self._setBaseColor("cyan", cyan) + self._setBaseColor("blue", blue) + self._setBaseColor("purple", purple) + self._setBaseColor("root", blue) + self._setBaseColor("folder", yellow) + self._setBaseColor("file", default) + self._setBaseColor("title", green) + self._setBaseColor("chapter", red) + self._setBaseColor("scene", blue) + self._setBaseColor("note", yellow) + self._setBaseColor("active", green) + self._setBaseColor("inactive", red) + self._setBaseColor("disabled", faded) return - def _parseColor(self, parser: NWConfigParser, section: str, name: str) -> QColor: + def _readColor(self, parser: ConfigParser, section: str, name: str) -> QColor: """Parse a colour value from a config string.""" - return QColor(*parser.rdIntList(section, name, [0, 0, 0, 255])) + return self.parseColor(parser.get(section, name, fallback="default")) def _setPalette( - self, parser: NWConfigParser, section: str, name: str, value: QPalette.ColorRole + self, parser: ConfigParser, section: str, name: str, value: QPalette.ColorRole ) -> None: """Set a palette colour value from a config string.""" - self._guiPalette.setBrush(value, self._parseColor(parser, section, name)) + self._guiPalette.setBrush(value, self._readColor(parser, section, name)) return def _buildStyleSheets(self, palette: QPalette) -> None: @@ -561,6 +606,35 @@ class GuiTheme: return + def _scanThemes(self, files: list[Path]) -> None: + """Scan the GUI themes folder and list all themes.""" + parser = ConfigParser() + data: dict[str, tuple[str, str, bool, Path]] = {} + keys = [] + for file in files: + try: + parser.clear() + parser.read(file, encoding="utf-8") + name = parser.get("Main", "name", fallback="") + mode = parser.get("Main", "mode", fallback="").lower() + if name and mode in ("light", "dark"): + key = file.stem + prefix = "*" if key.startswith("default") else "" + lookup = f"{prefix}{name} {key}" + keys.append(lookup) + data[lookup] = (file.stem, name, mode == "dark", file) + except Exception: # noqa: PERF203 + logger.error("Could not read file: %s", file) + logException() + + self._allThemes = {} + for lookup in sorted(keys): + key, name, dark, item = data[lookup] + logger.debug("Checking theme config '%s'", key) + self._allThemes[key] = ThemeEntry(name, dark, item) + + return + class GuiIcons: """The icon class manages the content of the assets/icons folder, @@ -572,9 +646,8 @@ class GuiIcons: """ __slots__ = ( - "_availThemes", "_headerDec", "_headerDecNarrow", "_noIcon", - "_qColors", "_qIcons", "_svgColors", "_svgData", "_themeList", - "mainTheme", "themeMeta", + "_allThemes", "_headerDec", "_headerDecNarrow", "_meta", + "_noIcon", "_qIcons", "_svgData", "_theme", ) TOGGLE_ICON_KEYS: Final[dict[str, tuple[str, str]]] = { @@ -582,69 +655,78 @@ class GuiIcons: "unfold": ("unfold-show", "unfold-hide"), } IMAGE_MAP: Final[dict[str, tuple[str, str]]] = { - "welcome": ("welcome-light.jpg", "welcome-dark.jpg"), + "welcome": ("welcome.webp", "welcome.webp"), "nw-text": ("novelwriter-text-light.svg", "novelwriter-text-dark.svg"), } def __init__(self, mainTheme: GuiTheme) -> None: - self.mainTheme = mainTheme - self.themeMeta = ThemeMeta() + self._theme = mainTheme + self._meta = IconsMeta() # Storage + self._allThemes: dict[str, ThemeEntry] = {} self._svgData: dict[str, bytes] = {} - self._svgColors: dict[str, bytes] = {} - self._qColors: dict[str, QColor] = {} self._qIcons: dict[str, QIcon] = {} self._headerDec: list[QPixmap] = [] self._headerDecNarrow: list[QPixmap] = [] - # Icon Theme Path - self._availThemes: dict[str, Path] = {} - self._themeList: list[tuple[str, str]] = [] - # None Icon self._noIcon = QIcon(str(CONFIG.assetPath("icons") / "none.svg")) - _listConf(self._availThemes, CONFIG.assetPath("icons"), ".icons") - _listConf(self._availThemes, CONFIG.dataPath("icons"), ".icons") - return def clear(self) -> None: """Clear the icon cache.""" self._svgData = {} - self._svgColors = {} - self._qColors = {} self._qIcons = {} self._headerDec = [] self._headerDecNarrow = [] - self.themeMeta = ThemeMeta() + self._meta = ThemeMeta() return + ## + # Properties + ## + + @property + def iconThemes(self) -> dict[str, ThemeEntry]: + """Return a dictionary of all icon themes.""" + return self._allThemes + ## # Actions ## - def loadTheme(self, theme: str) -> bool: + def initIcons(self) -> None: + """Initialise icons.""" + CONFIG.splashMessage("Scanning for icon themes ...") + icons: list[Path] = [] + _listContent(icons, CONFIG.assetPath("icons"), ".icons") + _listContent(icons, CONFIG.dataPath("icons"), ".icons") + self._scanThemes(icons) + return + + def loadTheme(self, theme: str) -> None: """Update the theme map. This is more of an init, since many of the GUI icons cannot really be replaced without writing specific update functions for the classes where they're used. """ - if theme not in self._availThemes: + if theme not in self._allThemes: logger.error("Could not find icon theme '%s'", theme) theme = DEF_ICONS CONFIG.iconTheme = theme - if not (file := self._availThemes.get(theme)): + entry = self._allThemes.get(theme) + if not entry: logger.error("Could not load icon theme") - return False + return - CONFIG.splashMessage("Loading icon theme ...") + CONFIG.splashMessage(f"Loading icon theme: {entry.name}") logger.info("Loading icon theme '%s'", theme) try: - meta = ThemeMeta() - with open(file, mode="r", encoding="utf-8") as icons: + meta = IconsMeta() + with open(entry.path, mode="r", encoding="utf-8") as icons: for icon in icons: bits = icon.partition("=") key = bits[0].strip() @@ -658,47 +740,23 @@ class GuiIcons: meta.author = value elif key == "meta:license": meta.license = value - self.themeMeta = meta + self._meta = meta except Exception: - logger.error("Could not read file: %s", file) + logger.error("Could not read file: %s", entry.path) logException() - return False - - CONFIG.splashMessage(f"Loaded icon theme: {meta.name}") - CONFIG.splashMessage("Generating additional icons ...") - - # Set colour overrides for project item icons - if (override := CONFIG.iconColTree) != "theme": - color = self._svgColors.get(override, b"#000000") - self._svgColors["root"] = color - self._svgColors["folder"] = color - if not CONFIG.iconColDocs: - self._svgColors["file"] = color - self._svgColors["title"] = color - self._svgColors["chapter"] = color - self._svgColors["scene"] = color - self._svgColors["note"] = color + return # Populate generated icons cache + CONFIG.splashMessage("Generating additional icons ...") self.getHeaderDecoration(0) self.getHeaderDecorationNarrow(0) - return True - - def setIconColor(self, key: str, color: QColor) -> None: - """Set an icon colour for a named colour.""" - self._qColors[key] = QColor(color) - self._svgColors[key] = color.name(QColor.NameFormat.HexRgb).encode("utf-8") return ## # Access Functions ## - def getIconColor(self, name: str) -> QColor: - """Return an icon color.""" - return QColor(self._qColors.get(name) or QtBlack) - def getIcon(self, name: str, color: str | None = None, w: int = 24, h: int = 24) -> QIcon: """Return an icon from the icon buffer, or load it.""" variant = f"{name}-{color}" if color else name @@ -769,7 +827,7 @@ class GuiIcons: map or the icon map. This function always returns a QPixmap. """ if name in self.IMAGE_MAP: - idx = int(self.mainTheme.isDarkTheme) + idx = int(self._theme.isDarkTheme) imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name][idx] else: logger.error("Decoration with name '%s' does not exist", name) @@ -793,7 +851,7 @@ class GuiIcons: def getHeaderDecoration(self, hLevel: int) -> QPixmap: """Get the decoration for a specific heading level.""" if not self._headerDec: - iPx = self.mainTheme.baseIconHeight + iPx = self._theme.baseIconHeight self._headerDec = [ self._generateDecoration("file", iPx, 0), self._generateDecoration("title", iPx, 0), @@ -806,7 +864,7 @@ class GuiIcons: def getHeaderDecorationNarrow(self, hLevel: int) -> QPixmap: """Get the narrow decoration for a specific heading level.""" if not self._headerDecNarrow: - iPx = self.mainTheme.baseIconHeight + iPx = self._theme.baseIconHeight self._headerDecNarrow = [ self._generateDecoration("file", iPx, 0), self._generateDecoration("title", iPx, 0), @@ -817,21 +875,6 @@ class GuiIcons: ] return self._headerDecNarrow[minmax(hLevel, 0, 5)] - def listThemes(self) -> list[tuple[str, str]]: - """Scan the GUI icons folder and list all themes.""" - if self._themeList: - return self._themeList - - themes = [] - for key, path in self._availThemes.items(): - logger.debug("Checking icon theme '%s'", key) - if name := _loadIconName(path): - themes.append((key, name)) - - self._themeList = sorted(themes, key=_sortTheme) - - return self._themeList - ## # Internal Functions ## @@ -847,7 +890,7 @@ class GuiIcons: return QIcon(str(CONFIG.assetPath("icons") / "x-novelwriter-project.svg")) if svg := self._svgData.get(name, b""): - if fill := self._svgColors.get(color or "default"): + if fill := self._theme.getRawBaseColor(color or "default"): svg = svg.replace(b"#000000", fill) pixmap = QPixmap(w, h) pixmap.fill(QtTransparent) @@ -869,53 +912,46 @@ class GuiIcons: painter = QPainter(pixmap) painter.setRenderHint(QtPaintAntiAlias) - if fill := self._svgColors.get(color or "default"): + if fill := self._theme.getRawBaseColor(color or "default"): painter.fillPath(path, QColor(fill.decode(encoding="utf-8"))) painter.end() tMode = Qt.TransformationMode.SmoothTransformation return pixmap.scaledToHeight(height, tMode) + def _scanThemes(self, entries: list[Path]) -> None: + """Scan the GUI themes folder and list all themes.""" + data: dict[str, tuple[str, str, Path]] = {} + keys = [] + for entry in entries: + try: + with open(entry, mode="r", encoding="utf-8") as fo: + for line in fo: + key, _, value = line.partition("=") + if key.strip() == "meta:name": + if name := value.strip(): + lookup = entry.stem + keys.append(lookup) + data[lookup] = (lookup, name, entry) + break + except Exception: + logger.error("Could not read file: %s", entry) + logException() + + self._allThemes = {} + for lookup in sorted(keys): + key, name, item = data[lookup] + logger.debug("Checking icon theme '%s'", key) + self._allThemes[key] = ThemeEntry(name, False, item) + + return + # Module Functions # ================ -def _listConf(target: dict, path: Path, extension: str) -> None: - """Scan for theme files and populate the dictionary.""" +def _listContent(data: list[Path], path: Path, extension: str) -> None: + """List files of a specific type and extend the list.""" if path.is_dir(): - for item in path.iterdir(): - if item.is_file() and item.name.endswith(extension): - target[item.stem] = item + data.extend(n for n in path.iterdir() if n.is_file() and n.suffix == extension) return - - -def _sortTheme(data: tuple[str, str]) -> str: - """Key function for theme sorting.""" - key, name = data - return f"*{name}" if key.startswith("default_") else name - - -def _loadInternalName(parser: NWConfigParser, path: str | Path) -> str: - """Open a conf file and read the 'name' setting.""" - try: - with open(path, mode="r", encoding="utf-8") as inFile: - parser.read_file(inFile) - return parser.rdStr("Main", "name", "") - except Exception: - logger.error("Could not read file: %s", path) - logException() - return "" - - -def _loadIconName(path: Path) -> str: - """Open an icons file and read the name setting.""" - try: - with open(path, mode="r", encoding="utf-8") as icons: - for icon in icons: - key, _, value = icon.partition("=") - if key.strip() == "meta:name": - return value.strip() - except Exception: - logger.error("Could not read file: %s", path) - logException() - return "" diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index a12383fe..680f35d5 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -30,7 +30,7 @@ from datetime import datetime from pathlib import Path from time import time -from PyQt6.QtCore import Qt, QTimer, pyqtSlot +from PyQt6.QtCore import QEvent, Qt, QTimer, pyqtSlot from PyQt6.QtGui import QCloseEvent, QCursor, QIcon, QShortcut from PyQt6.QtWidgets import ( QApplication, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, @@ -38,7 +38,7 @@ from PyQt6.QtWidgets import ( ) from novelwriter import CONFIG, SHARED, __hexversion__, __version__ -from novelwriter.common import formatFileFilter, formatVersion, hexToInt +from novelwriter.common import formatFileFilter, formatVersion, hexToInt, minmax from novelwriter.constants import nwConst from novelwriter.dialogs.about import GuiAbout from novelwriter.dialogs.preferences import GuiPreferences @@ -103,7 +103,7 @@ class GuiMain(QMainWindow): SHARED.initSharedData(self) # Prepare Main Window - self.resize(*CONFIG.mainWinSize) + self._setWindowSize(CONFIG.mainWinSize) self._updateWindowTitle() nwIcon = CONFIG.assetPath("icons") / "novelwriter.svg" @@ -902,10 +902,46 @@ class GuiMain(QMainWindow): return not self.splitView.isVisible() + def checkThemeUpdate(self) -> None: + """Load theme if mode changed.""" + if SHARED.theme.loadTheme(): + self.refreshThemeColors(syntax=True) + self.docEditor.initEditor() + self.docViewer.initViewer() + return + + def refreshThemeColors(self, syntax: bool = False, force: bool = False) -> None: + """Refresh the GUI theme.""" + SHARED.theme.loadTheme(force=force) + SHARED.project.updateTheme() + self.setPalette(QApplication.palette()) + self.docEditor.updateTheme() + self.docViewer.updateTheme() + self.docViewerPanel.updateTheme() + self.sideBar.updateTheme() + self.projView.updateTheme() + self.novelView.updateTheme() + self.projSearch.updateTheme() + self.outlineView.updateTheme() + self.itemDetails.updateTheme() + self.mainStatus.updateTheme() + SHARED.project.tree.refreshAllItems() + + if syntax: + self.docEditor.updateSyntaxColors() + + return + ## # Events ## + def changeEvent(self, event: QEvent) -> None: + """Capture application change events.""" + if int(event.type()) == 210: # ThemeChange + self.checkThemeUpdate() + return + def closeEvent(self, event: QCloseEvent) -> None: """Capture the closing event of the GUI and call the close function to handle all the close process steps. @@ -1054,23 +1090,7 @@ class GuiMain(QMainWindow): self.novelView.refreshCurrentTree() if theme: - SHARED.theme.loadTheme() - self.setPalette(QApplication.palette()) - self.docEditor.updateTheme() - self.docViewer.updateTheme() - self.docViewerPanel.updateTheme() - self.sideBar.updateTheme() - self.projView.updateTheme() - self.novelView.updateTheme() - self.projSearch.updateTheme() - self.outlineView.updateTheme() - self.itemDetails.updateTheme() - self.mainStatus.updateTheme() - SHARED.project.tree.refreshAllItems() - - if syntax: - SHARED.theme.loadSyntax() - self.docEditor.updateSyntaxColors() + self.refreshThemeColors(syntax=syntax, force=True) self.docEditor.initEditor() self.docViewer.initViewer() @@ -1326,6 +1346,15 @@ class GuiMain(QMainWindow): # Internal Functions ## + def _setWindowSize(self, size: list[int]) -> None: + """Set the main window size.""" + if len(size) == 2 and (screen := SHARED.mainScreen): + availSize = screen.availableSize() + width = minmax(size[0], 900, availSize.width()) + height = minmax(size[1], 500, availSize.height()) + self.resize(width, height) + return + def _updateWindowTitle(self, projName: str | None = None) -> None: """Set the window title and add the project's name.""" self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName]))) diff --git a/novelwriter/shared.py b/novelwriter/shared.py index 04add94c..7a276d02 100644 --- a/novelwriter/shared.py +++ b/novelwriter/shared.py @@ -25,6 +25,7 @@ along with this program. If not, see . from __future__ import annotations import logging +import re from enum import Enum from pathlib import Path @@ -32,8 +33,8 @@ from time import time from typing import TYPE_CHECKING, TypeVar from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot -from PyQt6.QtGui import QDesktopServices, QFont -from PyQt6.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget +from PyQt6.QtGui import QDesktopServices, QFont, QScreen +from PyQt6.QtWidgets import QApplication, QFileDialog, QFontDialog, QMessageBox, QWidget from novelwriter.common import formatFileFilter from novelwriter.constants import nwFiles @@ -41,6 +42,8 @@ from novelwriter.core.spellcheck import NWSpellEnchant from novelwriter.enum import nwChange, nwItemClass if TYPE_CHECKING: + from collections.abc import Callable + from novelwriter.core.project import NWProject from novelwriter.core.status import T_StatusKind from novelwriter.gui.theme import GuiTheme @@ -50,6 +53,8 @@ logger = logging.getLogger(__name__) NWWidget = TypeVar("NWWidget", bound=QWidget) +RX_HTML = re.compile(r"<.*?>") + class SharedData(QObject): @@ -150,6 +155,11 @@ class SharedData(QObject): """Return the last alert message.""" return self._lastAlert + @property + def mainScreen(self) -> QScreen | None: + """Return the screen of the main window.""" + return QApplication.screenAt(self.mainGui.rect().center()) + ## # Setters ## @@ -170,6 +180,7 @@ class SharedData(QObject): is created. """ self._theme = theme + self._theme.initThemes() return def initSharedData(self, gui: GuiMain) -> None: @@ -376,7 +387,7 @@ class SharedData(QObject): alert.setAlertType(_GuiAlert.INFO, False) self._lastAlert = alert.logMessage if log: - logger.info(self._lastAlert, stacklevel=2) + self._logMessage(self._lastAlert, logger.info) alert.exec() return @@ -387,7 +398,7 @@ class SharedData(QObject): alert.setAlertType(_GuiAlert.WARN, False) self._lastAlert = alert.logMessage if log: - logger.warning(self._lastAlert, stacklevel=2) + self._logMessage(self._lastAlert, logger.warning) alert.exec() return @@ -401,7 +412,7 @@ class SharedData(QObject): alert.setException(exc) self._lastAlert = alert.logMessage if log: - logger.error(self._lastAlert, stacklevel=2) + self._logMessage(self._lastAlert, logger.error) alert.exec() return @@ -419,6 +430,12 @@ class SharedData(QObject): # Internal Functions ## + def _logMessage(self, message: str, log: Callable) -> None: + """Print message to log.""" + for text in message.split("
    "): + log(RX_HTML.sub("", text), stacklevel=3) + return + def _resetProject(self) -> None: """Create a new project and spell checking instance.""" from novelwriter.core.project import NWProject diff --git a/novelwriter/text/patterns.py b/novelwriter/text/patterns.py index a5d2b3e9..b847f14b 100644 --- a/novelwriter/text/patterns.py +++ b/novelwriter/text/patterns.py @@ -42,6 +42,7 @@ class RegExPatterns: _rxItalic = re.compile(nwRegEx.FMT_EI, re.UNICODE) _rxBold = re.compile(nwRegEx.FMT_EB, re.UNICODE) _rxStrike = re.compile(nwRegEx.FMT_ST, re.UNICODE) + _rxMark = re.compile(nwRegEx.FMT_HL, re.UNICODE) _rxSCPlain = re.compile(nwRegEx.FMT_SC, re.UNICODE) _rxSCValue = re.compile(nwRegEx.FMT_SV, re.UNICODE) @@ -75,6 +76,11 @@ class RegExPatterns: """Markdown strikethrough style.""" return self._rxStrike + @property + def markdownMark(self) -> re.Pattern: + """Markdown highlight style.""" + return self._rxMark + @property def shortcodePlain(self) -> re.Pattern: """Plain shortcode style.""" diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py index 9b0abe33..c462b9af 100644 --- a/novelwriter/tools/welcome.py +++ b/novelwriter/tools/welcome.py @@ -32,7 +32,7 @@ from PyQt6.QtCore import ( QAbstractListModel, QModelIndex, QObject, QPoint, QSize, Qt, pyqtSignal, pyqtSlot ) -from PyQt6.QtGui import QAction, QCloseEvent, QColor, QFont, QPainter, QPaintEvent, QPen, QShortcut +from PyQt6.QtGui import QAction, QCloseEvent, QFont, QPainter, QPaintEvent, QPen, QShortcut from PyQt6.QtWidgets import ( QApplication, QFileDialog, QFormLayout, QHBoxLayout, QLabel, QLineEdit, QListView, QMenu, QPushButton, QScrollArea, QStackedWidget, @@ -77,7 +77,6 @@ class GuiWelcome(NDialog): self.bgImage = SHARED.theme.getDecoration("welcome") self.nwImage = SHARED.theme.getDecoration("nw-text", h=36) - self.bgColor = QColor(54, 54, 54) if SHARED.theme.isDarkTheme else QColor(255, 255, 255) self.nwLogo = QLabel(self) self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (128, 128))) @@ -178,8 +177,8 @@ class GuiWelcome(NDialog): hPix = min(hWin, 600) tMode = Qt.TransformationMode.SmoothTransformation painter = QPainter(self) - painter.fillRect(self.rect(), self.bgColor) painter.drawPixmap(0, hWin - hPix, self.bgImage.scaledToHeight(hPix, tMode)) + painter.end() super().paintEvent(event) return @@ -420,7 +419,7 @@ class _ProjectListItem(QStyledItemDelegate): painter.save() if opt.state & QtSelected == QtSelected: painter.setOpacity(0.25) - painter.fillRect(rect, QApplication.palette().highlight()) + painter.fillRect(rect, QApplication.palette().text()) painter.setOpacity(1.0) painter.drawPixmap(2, rect.top() + 6, self._icon) diff --git a/novelwriter/types.py b/novelwriter/types.py index fb0faebd..ead3397f 100644 --- a/novelwriter/types.py +++ b/novelwriter/types.py @@ -126,6 +126,7 @@ QtHeaderFixed = QHeaderView.ResizeMode.Fixed # Scroll Bar Policy +QtScrollAlwaysOn = Qt.ScrollBarPolicy.ScrollBarAlwaysOn QtScrollAlwaysOff = Qt.ScrollBarPolicy.ScrollBarAlwaysOff QtScrollAsNeeded = Qt.ScrollBarPolicy.ScrollBarAsNeeded diff --git a/requirements-dev.txt b/requirements-dev.txt index 18c97a30..794644e0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,4 @@ +build isort pyright ruff diff --git a/sample/content/636b6aa9b697b.nwd b/sample/content/636b6aa9b697b.nwd index 416efc4e..11e59489 100644 --- a/sample/content/636b6aa9b697b.nwd +++ b/sample/content/636b6aa9b697b.nwd @@ -1,8 +1,8 @@ %%~name: Making a Scene %%~path: 6a2d6d5f4f401/636b6aa9b697b %%~kind: NOVEL/DOCUMENT -%%~hash: b39201b02e3db63493d61d09e5fec5765a937255 -%%~date: Unknown/2025-06-24 09:09:56 +%%~hash: c1a75b18145e49e5e71b5284f66a261207da37d0 +%%~date: Unknown/2025-06-16 12:49:10 ### Making a Scene @tag: Scene @@ -16,7 +16,7 @@ A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree. The scene document can be sorted after the chapter document, or as a child of the chapter. Both result in the same output in the end, so it is a matter of preference. -Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and **_bold italic_**. You can also ~~strike through~~ text. There is **some support for _nested_ emphasis**, but there are some known limitations. If the syntax highlighter doesn’t show it correctly, the export tool will not either. +Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and **_bold italic_**. You can also ~~strike through~~ text. There is **some support for _nested_ emphasis**, but there are some known limitations. If the ==syntax highlighter== doesn’t show it correctly, the export tool will not either. For special formatting aside from standard emphasis, you can use “shortcodes”. Here we have super[sup]script[/sup] and sub[sub]script[/sub], and [b]part[/b]ial bold too, which ends in the middle of a word. The shortcodes also support [u]underline[/u], and you can [m]highlight text as well[/m]. diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 47a1e5de..812795c4 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Sample Project Jane Smith @@ -20,23 +20,23 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Background - Minor - Major - Main + None + Background + Minor + Major + Main - + Novel @@ -58,7 +58,7 @@ Chapter One - + Making a Scene diff --git a/setup/debian/control b/setup/debian/control index 75e94a20..24f0cd25 100644 --- a/setup/debian/control +++ b/setup/debian/control @@ -2,12 +2,28 @@ Source: novelwriter Maintainer: Veronica Berglyd Olsen Section: text Priority: optional -Build-Depends: dh-python, python3-setuptools, python3-all, debhelper (>= 9), python3 (>=3.10), python3-pyqt6 (>= 6.4), python3-pyqt6.qtsvg (>= 6.4), python3-enchant (>= 2.0) +Build-Depends: + dh-python, + python3-setuptools, + python3-all, + debhelper (>= 9), + python3 (>=3.10), + python3-pyqt6 (>= 6.4), + python3-pyqt6.qtsvg (>= 6.4), + python3-enchant (>= 2.0), + qt6-image-formats-plugins (>= 6.4) Standards-Version: 4.5.1 Homepage: https://novelwriter.io X-Python3-Version: >= 3.10 Package: novelwriter Architecture: all -Depends: ${misc:Depends}, ${python3:Depends}, python3 (>=3.10), python3-pyqt6 (>= 6.4), python3-pyqt6.qtsvg (>= 6.4), python3-enchant (>= 2.0) +Depends: + ${misc:Depends}, + ${python3:Depends}, + python3 (>=3.10), + python3-pyqt6 (>= 6.4), + python3-pyqt6.qtsvg (>= 6.4), + python3-enchant (>= 2.0), + qt6-image-formats-plugins (>= 6.4) Description: A plain text editor for planning and writing novels diff --git a/tests/conftest.py b/tests/conftest.py index 33baa870..d8407d28 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,11 +34,14 @@ from PyQt6.QtWidgets import QMessageBox sys.path.insert(1, str(Path(__file__).parent.parent.absolute())) from novelwriter import CONFIG, SHARED +from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT +from novelwriter.enum import nwTheme -from tests.mocked import MockGuiMain, MockTheme +from tests.mocked import MockGuiMain from tests.tools import cleanProject _TST_ROOT = Path(__file__).parent +_SRC_ROOT = _TST_ROOT.parent _TMP_ROOT = _TST_ROOT / "temp" _TMP_CONF = _TMP_ROOT / "conf" @@ -59,6 +62,14 @@ def resetConfigVars(): CONFIG._dLocale = QLocale("en_GB") CONFIG._manuals = {"manual": _TMP_ROOT / "manual.pdf"} CONFIG.guiLocale = "en_GB" + CONFIG.darkTheme = DEF_GUI_DARK + CONFIG.lightTheme = DEF_GUI_LIGHT + CONFIG.themeMode = nwTheme.LIGHT + + # Enable a few settings to ensure better coverage + CONFIG.emphLabels = True + CONFIG.lineHighlight = True + return @@ -75,6 +86,8 @@ def sessionFixture(): _TMP_ROOT.mkdir() _TMP_CONF.mkdir() (_TMP_ROOT / "manual.pdf").touch() + (_SRC_ROOT / "novelwriter" / "assets"/ "manual.pdf").touch() + (_SRC_ROOT / "novelwriter" / "assets"/ "manual_fr.pdf").touch() return @@ -148,15 +161,27 @@ def projPath(fncPath): @pytest.fixture(scope="function") def mockGUI(qtbot, monkeypatch): """Create a mock instance of novelWriter's main GUI class.""" + from novelwriter.gui.theme import GuiTheme + monkeypatch.setattr(QMessageBox, "exec", lambda *a: None) monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes) gui = MockGuiMain() - theme = MockTheme() + theme = GuiTheme() monkeypatch.setattr(SHARED, "_gui", gui) monkeypatch.setattr(SHARED, "_theme", theme) + return gui +@pytest.fixture(scope="function") +def mockGUIwithTheme(mockGUI): + """Create a mock instance of novelWriter's main GUI class with the + theme instance initialised. + """ + SHARED.theme.initThemes() + return mockGUI + + @pytest.fixture(scope="function") def nwGUI(qtbot, monkeypatch, functionFixture): """Create an instance of the novelWriter GUI.""" diff --git a/tests/files/all_icons.json b/tests/files/all_icons.json new file mode 100644 index 00000000..3d85f818 --- /dev/null +++ b/tests/files/all_icons.json @@ -0,0 +1,114 @@ +[ + "alert_error", + "alert_info", + "alert_question", + "alert_warn", + + "cls_archive", + "cls_character", + "cls_custom", + "cls_entity", + "cls_none", + "cls_novel", + "cls_object", + "cls_plot", + "cls_template", + "cls_timeline", + "cls_trash", + "cls_world", + + "prj_folder", + "prj_document", + "prj_title", + "prj_chapter", + "prj_scene", + "prj_note", + + "fmt_bold", + "fmt_italic", + "fmt_mark", + "fmt_strike", + "fmt_subscript", + "fmt_superscript", + "fmt_underline", + "fmt_toolbar", + + "search", + "search_cancel", + "search_case", + "search_loop", + "search_preserve", + "search_project", + "search_regex", + "search_replace", + "search_word", + + "bullet-off", + "bullet-on", + "unfold-hide", + "unfold-show", + + "sb_build", + "sb_details", + "sb_novel", + "sb_outline", + "sb_project", + "sb_search", + "sb_stats", + + "theme_light", + "theme_dark", + "theme_auto", + + "add", + "bookmarks", + "browse", + "build_settings", + "cancel", + "checked", + "chevron_down", + "chevron_left", + "chevron_right", + "chevron_up", + "close", + "copy", + "document_add", + "document", + "edit", + "exclude", + "export", + "filter", + "fit_height", + "fit_width", + "folder", + "font", + "import", + "language", + "lines", + "list", + "margin_bottom", + "margin_left", + "margin_right", + "margin_top", + "maximise", + "minimise", + "more_arrow", + "more_vertical", + "noncheckable", + "open", + "panel", + "pin", + "project_copy", + "quote", + "refresh", + "remove", + "revert", + "settings", + "star", + "stats", + "text", + "timer_off", + "timer", + "unchecked", + "view" +] diff --git a/tests/files/nwProject-1.5.nwx b/tests/files/nwProject-1.5.nwx index cd103d11..839b0876 100644 --- a/tests/files/nwProject-1.5.nwx +++ b/tests/files/nwProject-1.5.nwx @@ -1,5 +1,5 @@ - + Sample Project Jane Smith @@ -20,20 +20,20 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Background - Minor - Major - Main + None + Background + Minor + Major + Main diff --git a/tests/lipsum/content/04468803b92e1.nwd b/tests/lipsum/content/04468803b92e1.nwd index 35a2dad3..c1a7ac41 100644 --- a/tests/lipsum/content/04468803b92e1.nwd +++ b/tests/lipsum/content/04468803b92e1.nwd @@ -1,12 +1,14 @@ %%~name: Ancient Europe %%~path: 60bdf227455cc/04468803b92e1 %%~kind: WORLD/NOTE -%%~hash: 7b746485a1d06c64a5e0bf671e3141806ed51a54 -%%~date: Unknown/2024-01-30 21:36:44 +%%~hash: 9a0a98cda777f557ea10670ea1618cde253084b7 +%%~date: Unknown/2025-06-19 21:02:43 # Ancient Europe @tag: Europe | Ancient Europe +%Short: A strange place + Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula. Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque. diff --git a/tests/lipsum/content/2426c6f0ca922.nwd b/tests/lipsum/content/2426c6f0ca922.nwd index e6d5dd54..47db3a77 100644 --- a/tests/lipsum/content/2426c6f0ca922.nwd +++ b/tests/lipsum/content/2426c6f0ca922.nwd @@ -1,12 +1,14 @@ %%~name: Main %%~path: 6c6afb1247750/2426c6f0ca922 %%~kind: PLOT/NOTE -%%~hash: db3897d166e246acdb5e25e9bd98c5a40a699ed0 -%%~date: Unknown/Unknown +%%~hash: ffc82e98d37621d83a808a75f06bd36923356706 +%%~date: Unknown/2025-06-19 21:02:33 # Main Plot @tag: Main +%Short: Where it all happens + Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet. Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur. \ No newline at end of file diff --git a/tests/lipsum/content/441420a886d82.nwd b/tests/lipsum/content/441420a886d82.nwd index 05881160..b31445e3 100644 --- a/tests/lipsum/content/441420a886d82.nwd +++ b/tests/lipsum/content/441420a886d82.nwd @@ -1,14 +1,14 @@ %%~name: Chapter Two %%~path: 6bd935d2490cd/441420a886d82 %%~kind: NOVEL/DOCUMENT -%%~hash: fd6d46708faa1333f8f7ba0442fc5b35bf1e3f85 -%%~date: Unknown/Unknown +%%~hash: 81d30d3448202c782a6e252c5f7ef030bca3bf7f +%%~date: Unknown/2025-06-19 21:01:48 ## Chapter Two @pov: Bod @plot: Main @location: Europe -% Synopsis: Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. +%Synopsis: Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. Vestibulum pharetra fermentum leo, sed faucibus eros placerat quis. In hac habitasse platea dictumst. Donec metus massa, rutrum quis consequat et, tincidunt ac felis. Duis mollis metus ac nunc tincidunt blandit. Ut aliquet velit eu odio pharetra condimentum. Integer rutrum lacus orci, id venenatis libero accumsan at. diff --git a/tests/lipsum/content/47666c91c7ccf.nwd b/tests/lipsum/content/47666c91c7ccf.nwd index e551a0e3..5f6011ef 100644 --- a/tests/lipsum/content/47666c91c7ccf.nwd +++ b/tests/lipsum/content/47666c91c7ccf.nwd @@ -1,15 +1,15 @@ %%~name: Scene Five %%~path: 6bd935d2490cd/47666c91c7ccf %%~kind: NOVEL/DOCUMENT -%%~hash: d210c26966da6f9edea8726567abf4860b7bb9b7 -%%~date: Unknown/Unknown +%%~hash: 680e26046056165f1560ed1468cf316a3c51f90e +%%~date: Unknown/2025-06-19 21:01:59 ### Scene Five @pov: Bod @plot: Main @location: Europe -% Synopsis: Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. +%Synopsis: Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In sed felis auctor, rhoncus dui ac, consequat dolor. Integer volutpat libero sed nisl aliquet varius. Suspendisse et lorem sapien. Proin id ultrices nibh, ac suscipit diam. Suspendisse placerat varius porttitor. Curabitur elementum sed enim ultrices imperdiet. diff --git a/tests/lipsum/content/4c4f28287af27.nwd b/tests/lipsum/content/4c4f28287af27.nwd index 4bf57987..7ee071db 100644 --- a/tests/lipsum/content/4c4f28287af27.nwd +++ b/tests/lipsum/content/4c4f28287af27.nwd @@ -1,13 +1,15 @@ %%~name: Mr. Nobody %%~path: 67a8707f2f249/4c4f28287af27 %%~kind: CHARACTER/NOTE -%%~hash: 2a9b9751e8207b719bfa6233ebe04e24928ff8a8 -%%~date: Unknown/2024-01-30 21:36:26 +%%~hash: 84639e78d265b3a1f2fb8e3211a08c223d95500a +%%~date: Unknown/2025-06-19 21:02:21 # Nobody Owens @tag: Bod | Nobody Owens @plot: Main +%Short: The Hero + Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus. Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat. diff --git a/tests/lipsum/content/88243afbe5ed8.nwd b/tests/lipsum/content/88243afbe5ed8.nwd index 3f40135a..c8276545 100644 --- a/tests/lipsum/content/88243afbe5ed8.nwd +++ b/tests/lipsum/content/88243afbe5ed8.nwd @@ -1,15 +1,15 @@ %%~name: Scene One %%~path: 45e6b01ca35c1/88243afbe5ed8 %%~kind: NOVEL/DOCUMENT -%%~hash: a09245a7a772bbe02850b5db109977e336cd9cc1 -%%~date: Unknown/Unknown +%%~hash: d660202d320b3c99dbcc3bd9dd39d464d4f422ae +%%~date: Unknown/2025-06-19 21:01:38 ### Scene One @pov: Bod @plot: Main @location: Europe -% Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. +%Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. Donec quis ante nunc. Mauris ut leo ipsum. Vestibulum est neque, hendrerit nec neque a, ullamcorper lobortis tellus. Fusce sollicitudin purus quis congue bibendum. Aliquam condimentum ipsum tristique blandit tristique. Donec pulvinar neque ac suscipit malesuada. diff --git a/tests/lipsum/content/88d59a277361b.nwd b/tests/lipsum/content/88d59a277361b.nwd index a05c84e6..367ea3e1 100644 --- a/tests/lipsum/content/88d59a277361b.nwd +++ b/tests/lipsum/content/88d59a277361b.nwd @@ -1,11 +1,12 @@ %%~name: Prologue %%~path: b3643d0f92e32/88d59a277361b %%~kind: NOVEL/DOCUMENT -%%~hash: 377a72ba340ff458a00d98af745e9fa0c79fb26c -%%~date: Unknown/2024-10-25 19:16:49 +%%~hash: d43324a30d3f1e5608711ca71515dee429b30925 +%%~date: Unknown/2025-06-19 21:01:07 ##! Prologue -% Synopsis: Explanation from the lipsum.com website. +%Synopsis: Explanation from the lipsum.com website. +%Note.Purpose: Introduction _Lorem Ipsum_ is simply dummy text[footnote:f9kgf] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. diff --git a/tests/lipsum/content/eb103bc70c90c.nwd b/tests/lipsum/content/eb103bc70c90c.nwd index bdfac437..3c126692 100644 --- a/tests/lipsum/content/eb103bc70c90c.nwd +++ b/tests/lipsum/content/eb103bc70c90c.nwd @@ -1,15 +1,15 @@ %%~name: Scene Three %%~path: 6bd935d2490cd/eb103bc70c90c %%~kind: NOVEL/DOCUMENT -%%~hash: c4eda49e4fe81dc450d547eee0bdabe77fdaaa98 -%%~date: Unknown/Unknown +%%~hash: 59eeb405f5b26cbe1056d1f7684f7ed40ed6685a +%%~date: Unknown/2025-06-19 21:01:52 ### Scene Three @pov: Bod @plot: Main @location: Europe -% Synopsis: Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. +%Synopsis: Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean tincidunt lacus vitae nibh elementum eleifend. Sed rutrum condimentum sem quis blandit. Duis imperdiet libero metus, quis convallis quam faucibus a. Nulla ligula est, semper quis sollicitudin et, pretium id justo. Curabitur pharetra risus eget consectetur commodo. Duis mattis arcu non est condimentum, id venenatis risus volutpat. Pellentesque aliquet mauris non mauris porttitor ultrices. Phasellus ut vestibulum mi. Suspendisse malesuada metus lorem, a malesuada orci rhoncus a. Praesent euismod convallis ante, lacinia tincidunt ex egestas id. Praesent sit amet efficitur sapien. Morbi tincidunt volutpat nunc sed dictum. Aliquam ultrices metus id fermentum lobortis. diff --git a/tests/lipsum/content/f8c0562e50f1b.nwd b/tests/lipsum/content/f8c0562e50f1b.nwd index 73bedd5f..890a7e06 100644 --- a/tests/lipsum/content/f8c0562e50f1b.nwd +++ b/tests/lipsum/content/f8c0562e50f1b.nwd @@ -1,15 +1,15 @@ %%~name: Scene Four %%~path: 6bd935d2490cd/f8c0562e50f1b %%~kind: NOVEL/DOCUMENT -%%~hash: 9461a279b9fb6ef005ee4d432fcda77ff5bfbd42 -%%~date: Unknown/Unknown +%%~hash: f4848b56d1f41a04de1579d772a4d1ddd3e28e55 +%%~date: Unknown/2025-06-19 21:01:55 ### Scene Four @pov: Bod @plot: Main @location: Europe -% Synopsis: Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. +%Synopsis: Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. Nullam viverra dui et auctor pretium. Ut ullamcorper velit urna, sed imperdiet massa convallis a. Suspendisse efficitur, ipsum nec cursus pulvinar, eros urna posuere diam, nec elementum mi felis vitae sapien. diff --git a/tests/lipsum/content/f96ec11c6a3da.nwd b/tests/lipsum/content/f96ec11c6a3da.nwd index b6434395..a71ad88a 100644 --- a/tests/lipsum/content/f96ec11c6a3da.nwd +++ b/tests/lipsum/content/f96ec11c6a3da.nwd @@ -1,15 +1,15 @@ %%~name: Scene Two %%~path: 45e6b01ca35c1/f96ec11c6a3da %%~kind: NOVEL/DOCUMENT -%%~hash: ebe3fbaa16d9d81bc1a139822e3bf39bb357866d -%%~date: Unknown/Unknown +%%~hash: e95ff5b633e74c9cab8dc32b6a77b3e40d265f2b +%%~date: Unknown/2025-06-19 21:01:41 ### Scene Two @pov: Bod @plot: Main @location: Europe -% Synopsis: Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. +%Synopsis: Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. Vestibulum facilisis bibendum aliquam. Aliquam posuere, turpis ac bibendum varius, sem tellus venenatis risus, in elementum massa enim ac lorem. Integer in sem ac diam blandit ultricies ut in nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet erat est. Curabitur vitae cursus justo, sit amet placerat dolor. Vivamus eu felis hendrerit, tincidunt massa rutrum, maximus arcu. Pellentesque commodo justo odio, vel rutrum nulla tincidunt eu. Integer non neque condimentum, convallis diam non, varius ligula. Aliquam eget sapien mauris. Aenean pharetra nunc nisi, vel maximus ante tristique sit amet. Aliquam risus metus, interdum non odio eu, consectetur lacinia sapien. diff --git a/tests/lipsum/content/fb609cd8319dc.nwd b/tests/lipsum/content/fb609cd8319dc.nwd index 10e465ae..ff643fb1 100644 --- a/tests/lipsum/content/fb609cd8319dc.nwd +++ b/tests/lipsum/content/fb609cd8319dc.nwd @@ -1,14 +1,14 @@ %%~name: Chapter One %%~path: 45e6b01ca35c1/fb609cd8319dc %%~kind: NOVEL/DOCUMENT -%%~hash: 5dabeaa7a58238a6ad99ce73176b1bdfad1a71b7 -%%~date: Unknown/2024-01-30 21:35:57 +%%~hash: 73a1008ed17a84a48b14b41109a842a6ed300eec +%%~date: Unknown/2025-06-19 21:01:19 ## Chapter One @pov: Bod @plot: Main @location: Europe -% Synopsis: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. +%Synopsis: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. Praesent magna nunc, lacinia sit amet quam eget, aliquet ultrices justo. Morbi ornare enim et lorem rutrum finibus ut eu dolor. Aliquam a orci odio. Ut ultrices sem quis massa placerat, eget mollis nisl cursus. Cras vel sagittis justo. Ut non ultricies leo. Maecenas rutrum velit in est varius, et egestas massa pulvinar. diff --git a/tests/lipsum/nwProject.nwx b/tests/lipsum/nwProject.nwx index de32f1f6..a403da4a 100644 --- a/tests/lipsum/nwProject.nwx +++ b/tests/lipsum/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Lorem Ipsum lipsum.com @@ -9,7 +9,7 @@ en_GB None - 7a992350f3eb6 + 04468803b92e1 None b3643d0f92e32 None @@ -19,16 +19,16 @@ Replace Text 2 - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main @@ -45,7 +45,7 @@ Front Matter - + Prologue @@ -57,7 +57,7 @@ Chapter One - + Chapter One @@ -65,7 +65,7 @@ Scene One - + Scene Two @@ -77,19 +77,19 @@ Chapter Two - + Chapter Two - + Scene Three - + Scene Four - + Scene Five @@ -97,24 +97,24 @@ Characters - - Mr. Nobody + + Mr. Nobody Plot - - Main + + Main World - - Ancient Europe + + Ancient Europe diff --git a/tests/mocked.py b/tests/mocked.py index d28cdbba..e1e4097c 100644 --- a/tests/mocked.py +++ b/tests/mocked.py @@ -65,6 +65,9 @@ class MockTheme: self.guiFontBU = QFont() return + def initThemes(self) -> None: + return + def getPixmap(self, *a) -> QPixmap: return QPixmap() diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf index 78441636..c3a45e6a 100644 --- a/tests/reference/baseConfig_novelwriter.conf +++ b/tests/reference/baseConfig_novelwriter.conf @@ -1,10 +1,11 @@ [Meta] -timestamp = 2025-05-11 13:28:11 +timestamp = 2025-06-14 17:03:01 [Main] font = -theme = default -syntax = default_light +lighttheme = default_light +darktheme = default_dark +thememode = AUTO icons = material_rounded_normal iconcoltree = theme iconcoldocs = False @@ -26,9 +27,9 @@ outlinepane = 500, 150 [Project] autosaveproject = 60 autosavedoc = 30 -emphlabels = True +emphlabels = False backuppath = -backuponclose = False +backuponclose = True askbeforebackup = True askbeforeexit = True lastauthor = @@ -39,6 +40,7 @@ width = 700 margin = 40 tabwidth = 40 cursorwidth = 1 +linehighlight = False focuswidth = 800 hidefocusfooter = False justify = False @@ -48,7 +50,7 @@ repsquotes = True repdquotes = True repdash = True repdots = True -autoscroll = False +autoscroll = True autoscrollpos = 30 scrollpastend = True fmtsquoteopen = ‘ @@ -81,6 +83,7 @@ showedittoolbar = False showsessiontime = True viewcomments = True viewsynopsis = True +viewnotes = True searchcase = False searchword = False searchregex = False @@ -90,3 +93,4 @@ searchmatchcap = False searchprojcase = False searchprojword = False searchprojregex = False + diff --git a/tests/reference/coreIndex_LoadSave_tagsIndex.json b/tests/reference/coreIndex_LoadSave_tagsIndex.json index 99201a13..c8365601 100644 --- a/tests/reference/coreIndex_LoadSave_tagsIndex.json +++ b/tests/reference/coreIndex_LoadSave_tagsIndex.json @@ -18,7 +18,8 @@ "88d59a277361b": { "T0001": { "meta": {"level": "H2", "title": "Prologue", "line": 1, "tag": "", "counts": [605, 94, 2]}, - "summary": "Explanation from the lipsum.com website." + "summary": "Explanation from the lipsum.com website.", + "note.Purpose": "Introduction" }, "document": { "footnotes": ["f9kgf"] @@ -92,17 +93,20 @@ "4c4f28287af27": { "T0001": { "meta": {"level": "H1", "title": "Nobody Owens", "line": 1, "tag": "bod", "counts": [1864, 284, 3]}, - "refs": {"main": "@plot"} + "refs": {"main": "@plot"}, + "summary": "The Hero" } }, "2426c6f0ca922": { "T0001": { - "meta": {"level": "H1", "title": "Main Plot", "line": 1, "tag": "main", "counts": [1369, 195, 2]} + "meta": {"level": "H1", "title": "Main Plot", "line": 1, "tag": "main", "counts": [1369, 195, 2]}, + "summary": "Where it all happens" } }, "04468803b92e1": { "T0001": { - "meta": {"level": "H1", "title": "Ancient Europe", "line": 1, "tag": "europe", "counts": [1770, 259, 3]} + "meta": {"level": "H1", "title": "Ancient Europe", "line": 1, "tag": "europe", "counts": [1770, 259, 3]}, + "summary": "A strange place" } } } diff --git a/tests/reference/coreProject_NewFileFolder_nwProject.nwx b/tests/reference/coreProject_NewFileFolder_nwProject.nwx index b244eeb6..d3eec6c6 100644 --- a/tests/reference/coreProject_NewFileFolder_nwProject.nwx +++ b/tests/reference/coreProject_NewFileFolder_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreProject_NewRoot_nwProject.nwx b/tests/reference/coreProject_NewRoot_nwProject.nwx index abcfdfb7..4c253097 100644 --- a/tests/reference/coreProject_NewRoot_nwProject.nwx +++ b/tests/reference/coreProject_NewRoot_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreTools_DocDuplicator_nwProject.nwx b/tests/reference/coreTools_DocDuplicator_nwProject.nwx index 9b0d2071..2fb52c05 100644 --- a/tests/reference/coreTools_DocDuplicator_nwProject.nwx +++ b/tests/reference/coreTools_DocDuplicator_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx b/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx index 296b8172..5b7cbbab 100644 --- a/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx +++ b/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx @@ -1,5 +1,5 @@ - + Test Project A Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx b/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx index 531aea22..83362eb3 100644 --- a/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx +++ b/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx @@ -1,5 +1,5 @@ - + Test Project B Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/fmtToDocX_SaveDocument_app.xml b/tests/reference/fmtToDocX_SaveDocument_app.xml index 43e25401..0e99320a 100644 --- a/tests/reference/fmtToDocX_SaveDocument_app.xml +++ b/tests/reference/fmtToDocX_SaveDocument_app.xml @@ -1,8 +1,8 @@ - 41 - novelWriter/2.7b1 - 4035 + 45 + novelWriter/2.8a1 + 4050 21296 24964 45 diff --git a/tests/reference/fmtToDocX_SaveDocument_core.xml b/tests/reference/fmtToDocX_SaveDocument_core.xml index 3a0d34b6..35597dc5 100644 --- a/tests/reference/fmtToDocX_SaveDocument_core.xml +++ b/tests/reference/fmtToDocX_SaveDocument_core.xml @@ -1,10 +1,10 @@ - 2025-04-29T22:46:36 - 2025-04-29T22:46:36 + 2025-06-19T21:08:59 + 2025-06-19T21:08:59 lipsum.com Lorem Ipsum en_GB - 52 + 55 lipsum.com diff --git a/tests/reference/fmtToDocX_SaveDocument_document.xml b/tests/reference/fmtToDocX_SaveDocument_document.xml index 68bd89f3..3e631895 100644 --- a/tests/reference/fmtToDocX_SaveDocument_document.xml +++ b/tests/reference/fmtToDocX_SaveDocument_document.xml @@ -34,7 +34,7 @@ - 4,035 + 4,050 @@ -48,7 +48,7 @@ - 27,064 + 27,164 @@ -1250,6 +1250,28 @@ Main + + + + + + + + + + Short Description: + + + + + + + + + + The Hero + + @@ -1323,6 +1345,28 @@ Main + + + + + + + + + + Short Description: + + + + + + + + + + Where it all happens + + @@ -1396,6 +1440,28 @@ Ancient Europe + + + + + + + + + + Short Description: + + + + + + + + + + A strange place + + diff --git a/tests/reference/fmtToDocX_SaveDocument_settings.xml b/tests/reference/fmtToDocX_SaveDocument_settings.xml index 10913023..c1622522 100644 --- a/tests/reference/fmtToDocX_SaveDocument_settings.xml +++ b/tests/reference/fmtToDocX_SaveDocument_settings.xml @@ -9,13 +9,13 @@ - + - + - + diff --git a/tests/reference/guiEditor_Main_Final_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx index 21078178..d415b195 100644 --- a/tests/reference/guiEditor_Main_Final_nwProject.nwx +++ b/tests/reference/guiEditor_Main_Final_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/guiEditor_Main_Initial_nwProject.nwx b/tests/reference/guiEditor_Main_Initial_nwProject.nwx index 1f3260da..bb69bd02 100644 --- a/tests/reference/guiEditor_Main_Initial_nwProject.nwx +++ b/tests/reference/guiEditor_Main_Initial_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/guiOutline_Content_outline.csv b/tests/reference/guiOutline_Content_outline.csv index 62edb467..22bc5537 100644 --- a/tests/reference/guiOutline_Content_outline.csv +++ b/tests/reference/guiOutline_Content_outline.csv @@ -1,13 +1,13 @@ -"H","Title","Document","Line","Status","Chars","Words","Pars","POV","Focus","Characters","Plot","Timeline","Locations","Objects","Entities","Custom","Story","Mentions","Synopsis" -"H1","Lorem Ipsum","Lorem Ipsum","1","Finished","259","44","5","","","","","","","","","","","","" -"H2","Prologue","Prologue","1","Draft","605","94","2","","","","","","","","","","","","Explanation from the lipsum.com website." -"H1","Act One","Act One","1","New","35","6","1","","","","","","","","","","","","" -"H2","Chapter One","Chapter One","1","Draft","419","67","1","Bod","","","Main","","Europe","","","","","","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam." -"H3","Scene One","Scene One","1","Finished","1197","174","2","Bod","","","Main","","Europe","","","","","","Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur." -"H4","Scene One, Section Two","Scene One","13","Finished","1561","230","2","","","","","","","","","","","","" -"H3","Scene Two","Scene Two","1","Finished","2034","299","3","Bod","","","Main","","Europe","","","","","","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci." -"H4","Scene Two, Section Two","Scene Two","15","Finished","2009","301","3","","","","","","","","","","","","" -"H2","Chapter Two","Chapter Two","1","Draft","477","70","1","Bod","","","Main","","Europe","","","","","","Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue." -"H3","Scene Three","Scene Three","1","Finished","3006","439","4","Bod","","","Main","","Europe","","","","","","Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos." -"H3","Scene Four","Scene Four","1","Finished","3839","563","6","Bod","","","Main","","Europe","","","","","","Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo." -"H3","Scene Five","Scene Five","1","Finished","3644","543","5","Bod","","","Main","","Europe","","","","","","Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus." +"H","Title","Document","Line","Status","Chars","Words","Pars","POV","Focus","Characters","Plot","Timeline","Locations","Objects","Entities","Custom","Story","Mentions","Synopsis","Note (Purpose)" +"H1","Lorem Ipsum","Lorem Ipsum","1","Finished","259","44","5","","","","","","","","","","","","","" +"H2","Prologue","Prologue","1","Draft","605","94","2","","","","","","","","","","","","Explanation from the lipsum.com website.","Introduction" +"H1","Act One","Act One","1","New","35","6","1","","","","","","","","","","","","","" +"H2","Chapter One","Chapter One","1","Draft","419","67","1","Bod","","","Main","","Europe","","","","","","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.","" +"H3","Scene One","Scene One","1","Finished","1197","174","2","Bod","","","Main","","Europe","","","","","","Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.","" +"H4","Scene One, Section Two","Scene One","13","Finished","1561","230","2","","","","","","","","","","","","","" +"H3","Scene Two","Scene Two","1","Finished","2034","299","3","Bod","","","Main","","Europe","","","","","","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.","" +"H4","Scene Two, Section Two","Scene Two","15","Finished","2009","301","3","","","","","","","","","","","","","" +"H2","Chapter Two","Chapter Two","1","Draft","477","70","1","Bod","","","Main","","Europe","","","","","","Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.","" +"H3","Scene Three","Scene Three","1","Finished","3006","439","4","Bod","","","Main","","Europe","","","","","","Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.","" +"H3","Scene Four","Scene Four","1","Finished","3839","563","6","Bod","","","Main","","Europe","","","","","","Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.","" +"H3","Scene Five","Scene Five","1","Finished","3644","543","5","Bod","","","Main","","Europe","","","","","","Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.","" diff --git a/tests/reference/mBuildDocBuild_Extended_Markdown_Lorem_Ipsum.md b/tests/reference/mBuildDocBuild_Extended_Markdown_Lorem_Ipsum.md index 96b1fc07..329eeaea 100644 --- a/tests/reference/mBuildDocBuild_Extended_Markdown_Lorem_Ipsum.md +++ b/tests/reference/mBuildDocBuild_Extended_Markdown_Lorem_Ipsum.md @@ -3,9 +3,9 @@ Lorem Ipsum **By lipsum.com** -Word Count: 4,169 +Word Count: 4,184 -Character Count: 27,898 +Character Count: 27,998 “Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit…” @@ -162,6 +162,8 @@ Notes: Characters **Tag:** Bod | Nobody Owens **Plot:** Main +**Short Description:** The Hero + Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus. Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat. @@ -175,6 +177,8 @@ Notes: Plot **Tag:** Main +**Short Description:** Where it all happens + Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet. Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur. @@ -186,6 +190,8 @@ Notes: World **Tag:** Europe | Ancient Europe +**Short Description:** A strange place + Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula. Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque. diff --git a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm index f605b0fa..646a7c05 100644 --- a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm +++ b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm @@ -1,8 +1,9 @@ - Lorem Ipsum + + \n" "\n" "\n" - "{bodyText:s}\n" + "{body}\n" "\n" "\n" ).format( - htmlStyle="\n".join(hStyle), - bodyText="".join(resText).rstrip() + style="\n".join(hStyle), + body="".join(resText).rstrip() ) saveFile = fncPath / "outFile.htm" html.saveDocument(saveFile) - assert saveFile.read_text(encoding="utf-8") == htmlDoc + assert saveFile.read_text(encoding="utf-8") == htmlDocCSS - # JSON + HTML + # HTML wo/Styles + html.setStyles(False) + htmlDocCSS = ( + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "{body}\n" + "\n" + "\n" + ).format( + body="".join(resText).rstrip() + ) + + saveFile = fncPath / "outFile.htm" + html.saveDocument(saveFile) + assert saveFile.read_text(encoding="utf-8") == htmlDocCSS + + # JSON + HTML w/Styles + html.setStyles(True) saveFile = fncPath / "outFile.json" html.saveDocument(saveFile) data = json.loads(saveFile.read_text(encoding="utf-8")) diff --git a/tests/test_formats/test_fmt_tokenizer.py b/tests/test_formats/test_fmt_tokenizer.py index 58106cdd..63c9cab0 100644 --- a/tests/test_formats/test_fmt_tokenizer.py +++ b/tests/test_formats/test_fmt_tokenizer.py @@ -783,7 +783,7 @@ def testFmtToken_MetaFormat(mockGUI): tokens._text = "% synopsis: The synopsis\n" tokens.tokenizeText() assert tokens._blocks == [( - BlockTyp.COMMENT, "", "Synopsis: The synopsis", [ + BlockTyp.SUMMARY, "", "Synopsis: The synopsis", [ (0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "modifier"), (9, TextFmt.COL_E, ""), (9, TextFmt.B_E, ""), (10, TextFmt.COL_B, "note"), (22, TextFmt.COL_E, "") @@ -800,13 +800,52 @@ def testFmtToken_MetaFormat(mockGUI): tokens._text = "% short: A short description\n" tokens.tokenizeText() assert tokens._blocks == [( - BlockTyp.COMMENT, "", "Short Description: A short description", [ + BlockTyp.SUMMARY, "", "Short Description: A short description", [ (0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "modifier"), (18, TextFmt.COL_E, ""), (18, TextFmt.B_E, ""), (19, TextFmt.COL_B, "note"), (38, TextFmt.COL_E, ""), ], BlockFmt.NONE )] + # Story Comments + tokens.setCommentType(nwComment.STORY, False) + tokens.setCommentType(nwComment.NOTE, False) + tokens._text = ( + "%Story.Stuff: Stuff happens\n" + "%Note.More: That stuff that happened\n" + "%Note.Other: That other stuff that happened\n" + ) + tokens.tokenizeText() + assert tokens._blocks == [] + + tokens.setCommentType(nwComment.STORY, True) + tokens.setCommentType(nwComment.NOTE, True) + tokens._text = ( + "%Story.Stuff: Stuff happens\n" + "%Note.More: That stuff that happened\n" + "%Note.Other: That other stuff that happened\n" + ) + tokens.tokenizeText() + assert tokens._blocks == [( + BlockTyp.NOTE, "", "Story Structure (Stuff): Stuff happens", [ + (0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "modifier"), + (24, TextFmt.COL_E, ""), (24, TextFmt.B_E, ""), + (25, TextFmt.COL_B, "note"), (38, TextFmt.COL_E, "") + ], BlockFmt.Z_BTM + ), ( + BlockTyp.NOTE, "", "Note (More): That stuff that happened", [ + (0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "modifier"), + (12, TextFmt.COL_E, ""), (12, TextFmt.B_E, ""), + (13, TextFmt.COL_B, "note"), (37, TextFmt.COL_E, "") + ], BlockFmt.Z_TOP | BlockFmt.Z_BTM + ), ( + BlockTyp.NOTE, "", "Note (Other): That other stuff that happened", [ + (0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "modifier"), + (13, TextFmt.COL_E, ""), (13, TextFmt.B_E, ""), + (14, TextFmt.COL_B, "note"), (44, TextFmt.COL_E, "") + ], BlockFmt.Z_TOP + )] + # Keyword tokens.setKeywords(False) tokens._text = "@char: Bod\n" @@ -919,6 +958,11 @@ def testFmtToken_ExtractFormats(mockGUI): assert text == "Text with strikethrough in it." assert fmt == [(10, TextFmt.D_B, ""), (23, TextFmt.D_E, "")] + # Plain mark + text, fmt = tokens._extractFormats("Text with ==a highlight== in it.") + assert text == "Text with a highlight in it." + assert fmt == [(10, TextFmt.M_B, ""), (21, TextFmt.M_E, "")] + # Nested bold/italics text, fmt = tokens._extractFormats("Text with **bold and _italics_** in it.") assert text == "Text with bold and italics in it." @@ -1632,7 +1676,7 @@ def testFmtToken_TextIndent(mockGUI): ] assert tokens._blocks == [ (BlockTyp.HEAD3, TM1, "Scene Two", [], BlockFmt.NONE), - (BlockTyp.COMMENT, "", "Synopsis: Stuff happens.", tFmt, BlockFmt.NONE), + (BlockTyp.SUMMARY, "", "Synopsis: Stuff happens.", tFmt, BlockFmt.NONE), ] assert tokens._noIndent is True diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 5bda2d82..52cd8826 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -674,9 +674,16 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd): assert docEditor.docAction(nwDocAction.UNDO) is True assert docEditor.getText() == text + # Mark + docEditor.setCursorPosition(50) + assert docEditor.docAction(nwDocAction.MD_MARK) is True + assert docEditor.getText() == text.replace("consectetur", "==consectetur==") + assert docEditor.docAction(nwDocAction.UNDO) is True + assert docEditor.getText() == text + # Redo assert docEditor.docAction(nwDocAction.REDO) is True - assert docEditor.getText() == text.replace("consectetur", "~~consectetur~~") + assert docEditor.getText() == text.replace("consectetur", "==consectetur==") assert docEditor.docAction(nwDocAction.UNDO) is True assert docEditor.getText() == text @@ -2067,7 +2074,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): origText = docEditor.getText() # Select the Word "est" - docEditor.setCursorPosition(645) + docEditor.setCursorPosition(663) docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor) cursor = docEditor.textCursor() assert cursor.selectedText() == "est" @@ -2080,11 +2087,11 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): # Find next by enter key monkeypatch.setattr(docSearch.searchBox, "hasFocus", lambda: True) qtbot.keyClick(docSearch.searchBox, Qt.Key.Key_Return, delay=KEY_DELAY) - assert abs(docEditor.getCursorPosition() - 1299) < 3 + assert abs(docEditor.getCursorPosition() - 1317) < 3 # Find next by button qtbot.mouseClick(docSearch.searchButton, QtMouseLeft, delay=KEY_DELAY) - assert abs(docEditor.getCursorPosition() - 1513) < 3 + assert abs(docEditor.getCursorPosition() - 1531) < 3 # Activate loop search docSearch.toggleLoop.activate(QAction.ActionEvent.Trigger) @@ -2093,7 +2100,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): # Find next by menu Search > Find Next nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 647) < 3 + assert abs(docEditor.getCursorPosition() - 665) < 3 # Close search docSearch.cancelSearch.activate(QAction.ActionEvent.Trigger) @@ -2132,13 +2139,13 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): # Set valid RegEx docSearch.setSearchText(r"\bSus") qtbot.mouseClick(docSearch.searchButton, QtMouseLeft, delay=KEY_DELAY) - assert abs(docEditor.getCursorPosition() - 223) < 3 + assert abs(docEditor.getCursorPosition() - 241) < 3 # Find next and then prev nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 324) < 3 + assert abs(docEditor.getCursorPosition() - 342) < 3 nwGUI.mainMenu.aFindPrev.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 223) < 3 + assert abs(docEditor.getCursorPosition() - 241) < 3 # Make RegEx case sensitive docSearch.toggleCase.activate(QAction.ActionEvent.Trigger) @@ -2147,11 +2154,11 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): # Find next/prev (one result) nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 626) < 3 + assert abs(docEditor.getCursorPosition() - 644) < 3 nwGUI.mainMenu.aFindPrev.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 626) < 3 + assert abs(docEditor.getCursorPosition() - 644) < 3 nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 626) < 3 + assert abs(docEditor.getCursorPosition() - 644) < 3 # Trigger replace nwGUI.mainMenu.aReplace.activate(QAction.ActionEvent.Trigger) @@ -2168,22 +2175,22 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): assert CONFIG.searchMatchCap is True # Replace "Sus" with "Foo" via menu - docEditor.setCursorPosition(605) + docEditor.setCursorPosition(623) nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger) nwGUI.mainMenu.aReplaceNext.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[623:634] == "Foopendisse" + assert docEditor.getText()[641:652] == "Foopendisse" # Find next/prev to loop file nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 223) < 3 + assert abs(docEditor.getCursorPosition() - 241) < 3 nwGUI.mainMenu.aFindPrev.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 1805) < 3 + assert abs(docEditor.getCursorPosition() - 1823) < 3 nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 223) < 3 + assert abs(docEditor.getCursorPosition() - 241) < 3 # Replace "sus" with "foo" via replace button qtbot.mouseClick(docSearch.replaceButton, QtMouseLeft, delay=KEY_DELAY) - assert docEditor.getText()[220:228] == "foocipit" + assert docEditor.getText()[238:246] == "foocipit" # Revert last two replaces assert docEditor.docAction(nwDocAction.UNDO) @@ -2197,7 +2204,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): # Close search and select "est" again docSearch.cancelSearch.activate(QAction.ActionEvent.Trigger) - docEditor.setCursorPosition(645) + docEditor.setCursorPosition(663) docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor) cursor = docEditor.textCursor() assert cursor.selectedText() == "est" @@ -2214,9 +2221,9 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): # Only one match nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 647) < 3 + assert abs(docEditor.getCursorPosition() - 665) < 3 nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 647) < 3 + assert abs(docEditor.getCursorPosition() - 665) < 3 # Enable next doc search docSearch.toggleProject.activate(QAction.ActionEvent.Trigger) @@ -2227,9 +2234,9 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger) assert docEditor.docHandle == "2426c6f0ca922" # Next document nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 620) < 3 + assert abs(docEditor.getCursorPosition() - 651) < 3 nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger) - assert abs(docEditor.getCursorPosition() - 1127) < 3 + assert abs(docEditor.getCursorPosition() - 1157) < 3 # Next doc, no match assert CONFIG.searchNextFile is True diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py index 65c31a24..8890df22 100644 --- a/tests/test_gui/test_gui_docviewer.py +++ b/tests/test_gui/test_gui_docviewer.py @@ -223,17 +223,23 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum): CONFIG.showFullPath = True # Document footer show/hide synopsis - assert nwGUI.viewDocument("f96ec11c6a3da") is True + nwGUI.viewDocument("f96ec11c6a3da") assert len(docViewer.toPlainText()) == 4314 docViewer.docFooter._doToggleSynopsis(False) assert len(docViewer.toPlainText()) == 4098 # Document footer show/hide comments - assert nwGUI.viewDocument("846352075de7d") is True + nwGUI.viewDocument("846352075de7d") assert len(docViewer.toPlainText()) == 683 docViewer.docFooter._doToggleComments(False) assert len(docViewer.toPlainText()) == 634 + # Document footer show/hide notes + nwGUI.viewDocument("88d59a277361b") + assert len(docViewer.toPlainText()) == 900 + docViewer.docFooter._doToggleNotes(False) + assert len(docViewer.toPlainText()) == 871 + # Crash the HTML rendering with monkeypatch.context() as mp: mp.setattr(ToQTextDocument, "doConvert", causeException) diff --git a/tests/test_gui/test_gui_docviewerpanel.py b/tests/test_gui/test_gui_docviewerpanel.py index b0d02493..f51d9a81 100644 --- a/tests/test_gui/test_gui_docviewerpanel.py +++ b/tests/test_gui/test_gui_docviewerpanel.py @@ -224,7 +224,7 @@ def testGuiViewerPanel_Tags(qtbot, monkeypatch, caplog, nwGUI, projPath, mockRnd # Update Labels assert charTab.topLevelItem(0).text(charTab.C_IMPORT) == "New" - SHARED.project.data.itemImport.add(C.iNew, "Stuff", (100, 100, 100), "SQUARE", 0) + SHARED.project.data.itemImport.add(C.iNew, "Stuff", "#646464", "SQUARE", 0) viewPanel.updateStatusLabels("i") assert charTab.topLevelItem(0).text(charTab.C_IMPORT) == "Stuff" diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 6fda9b6a..3940cbba 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -24,6 +24,7 @@ import shutil from pathlib import Path from shutil import copyfile +from unittest.mock import Mock import pytest @@ -32,9 +33,10 @@ from PyQt6.QtGui import QPalette from PyQt6.QtWidgets import QInputDialog, QMessageBox from novelwriter import CONFIG, SHARED +from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT from novelwriter.constants import nwFiles from novelwriter.dialogs.editlabel import GuiEditLabel -from novelwriter.enum import nwDocAction, nwDocMode, nwFocus, nwItemType, nwView +from novelwriter.enum import nwDocAction, nwDocMode, nwFocus, nwItemType, nwTheme, nwView from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.noveltree import GuiNovelView from novelwriter.gui.outline import GuiOutlineView @@ -179,21 +181,35 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd): @pytest.mark.gui def testGuiMain_UpdateTheme(qtbot, nwGUI): """Test updating the theme in the GUI.""" - mainTheme = SHARED.theme - CONFIG.guiTheme = "default_dark" - CONFIG.guiSyntax = "default_dark" - mainTheme.loadTheme() - mainTheme.loadSyntax() + theme = SHARED.theme + CONFIG.themeMode = nwTheme.DARK + CONFIG.darkTheme = DEF_GUI_DARK + CONFIG.lightTheme = DEF_GUI_LIGHT + theme.loadTheme() + assert theme.isDarkTheme is True + nwGUI._processConfigChanges(False, True, False, False) nwGUI._processConfigChanges(True, True, True, True) + # Check editor syntax syntax = SHARED.theme.syntaxTheme - assert nwGUI.docEditor.palette().color(QPalette.ColorRole.Window) == syntax.back assert nwGUI.docEditor.docHeader.palette().color(QPalette.ColorRole.Window) == syntax.back assert nwGUI.docViewer.palette().color(QPalette.ColorRole.Window) == syntax.back assert nwGUI.docViewer.docHeader.palette().color(QPalette.ColorRole.Window) == syntax.back + # Update by check + CONFIG.themeMode = nwTheme.LIGHT + nwGUI.checkThemeUpdate() + assert theme.isDarkTheme is False + + # Through change event + event = Mock() + event.type.return_value = 210 + CONFIG.themeMode = nwTheme.DARK + nwGUI.changeEvent(event) + assert theme.isDarkTheme is True + # qtbot.stop() @@ -251,8 +267,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): # Change some settings CONFIG.hideHScroll = True CONFIG.hideVScroll = True - CONFIG.autoScrollPos = 80 - CONFIG.autoScroll = True + CONFIG.autoScroll = False # Add a Character File nwGUI._changeView(nwView.PROJECT) diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py index f63f588a..b13b18a1 100644 --- a/tests/test_gui/test_gui_mainmenu.py +++ b/tests/test_gui/test_gui_mainmenu.py @@ -68,154 +68,155 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum): assert docEditor.docAction(nwDocAction.COPY) is False assert nwGUI.openProject(prjLipsum) is True + x = 72 # Split By Chapter assert nwGUI.openDocument("4c4f28287af27") is True - docEditor.setCursorPosition(57) - cleanText = docEditor.getText()[54:101] + docEditor.setCursorPosition(x+3) + cleanText = docEditor.getText()[x:x+47] # Bold mainMenu.aFmtBold.activate(QAction.ActionEvent.Trigger) fmtStr = "**Pellentesque** nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:105] == fmtStr + assert docEditor.getText()[x:x+51] == fmtStr mainMenu.aFmtBold.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:101] == cleanText + assert docEditor.getText()[x:x+47] == cleanText # Italic mainMenu.aFmtItalic.activate(QAction.ActionEvent.Trigger) fmtStr = "_Pellentesque_ nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:103] == fmtStr + assert docEditor.getText()[x:x+49] == fmtStr mainMenu.aFmtItalic.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:101] == cleanText + assert docEditor.getText()[x:x+47] == cleanText # Strikethrough mainMenu.aFmtStrike.activate(QAction.ActionEvent.Trigger) fmtStr = "~~Pellentesque~~ nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:105] == fmtStr + assert docEditor.getText()[x:x+51] == fmtStr mainMenu.aFmtStrike.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:101] == cleanText + assert docEditor.getText()[x:x+47] == cleanText # Should get us back to plain mainMenu.aFmtBold.activate(QAction.ActionEvent.Trigger) mainMenu.aFmtItalic.activate(QAction.ActionEvent.Trigger) mainMenu.aFmtItalic.activate(QAction.ActionEvent.Trigger) mainMenu.aFmtBold.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:101] == cleanText + assert docEditor.getText()[x:x+47] == cleanText # Double Quotes mainMenu.aFmtDQuote.activate(QAction.ActionEvent.Trigger) fmtStr = "“Pellentesque” nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:103] == fmtStr + assert docEditor.getText()[x:x+49] == fmtStr mainMenu.aEditUndo.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:101] == cleanText + assert docEditor.getText()[x:x+47] == cleanText # Single Quotes mainMenu.aFmtSQuote.activate(QAction.ActionEvent.Trigger) fmtStr = "‘Pellentesque’ nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:103] == fmtStr + assert docEditor.getText()[x:x+49] == fmtStr mainMenu.aEditUndo.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:101] == cleanText + assert docEditor.getText()[x:x+47] == cleanText # Block Formats # ============= # cSpell:ignore Pellentesque erat nulla posuere commodo - docEditor.setCursorPosition(57) + docEditor.setCursorPosition(x+3) # Header 1 mainMenu.aFmtHead1.activate(QAction.ActionEvent.Trigger) fmtStr = "# Pellentesque nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:103] == fmtStr + assert docEditor.getText()[x:x+49] == fmtStr # Header 2 mainMenu.aFmtHead2.activate(QAction.ActionEvent.Trigger) fmtStr = "## Pellentesque nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:104] == fmtStr + assert docEditor.getText()[x:x+50] == fmtStr # Header 3 mainMenu.aFmtHead3.activate(QAction.ActionEvent.Trigger) fmtStr = "### Pellentesque nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:105] == fmtStr + assert docEditor.getText()[x:x+51] == fmtStr # Header 4 mainMenu.aFmtHead4.activate(QAction.ActionEvent.Trigger) fmtStr = "#### Pellentesque nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:106] == fmtStr + assert docEditor.getText()[x:x+52] == fmtStr # Title Format mainMenu.aFmtTitle.activate(QAction.ActionEvent.Trigger) fmtStr = "#! Pellentesque nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:104] == fmtStr + assert docEditor.getText()[x:x+50] == fmtStr # Unnumbered Chapter mainMenu.aFmtUnNum.activate(QAction.ActionEvent.Trigger) fmtStr = "##! Pellentesque nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:105] == fmtStr + assert docEditor.getText()[x:x+51] == fmtStr # Hard Scene mainMenu.aFmtHardSc.activate(QAction.ActionEvent.Trigger) fmtStr = "###! Pellentesque nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:106] == fmtStr + assert docEditor.getText()[x:x+52] == fmtStr # Clear Format mainMenu.aFmtNoFormat.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:101] == cleanText + assert docEditor.getText()[x:x+47] == cleanText # Comment On mainMenu.aFmtComment.activate(QAction.ActionEvent.Trigger) fmtStr = "% Pellentesque nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:103] == fmtStr + assert docEditor.getText()[x:x+49] == fmtStr # Comment Off mainMenu.aFmtComment.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:101] == cleanText + assert docEditor.getText()[x:x+47] == cleanText # Check comment with no space before text - docEditor.setCursorPosition(54) + docEditor.setCursorPosition(x) docEditor.insertText("%") fmtStr = "%Pellentesque nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:102] == fmtStr + assert docEditor.getText()[x:x+48] == fmtStr mainMenu.aFmtNoFormat.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:101] == cleanText + assert docEditor.getText()[x:x+47] == cleanText # Undo/Redo mainMenu.aEditUndo.activate(QAction.ActionEvent.Trigger) fmtStr = "%Pellentesque nec erat ut nulla posuere commodo." - assert docEditor.getText()[54:102] == fmtStr + assert docEditor.getText()[x:x+48] == fmtStr mainMenu.aEditRedo.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:101] == cleanText + assert docEditor.getText()[x:x+47] == cleanText # Cut, Copy and Paste - docEditor.setCursorPosition(54) + docEditor.setCursorPosition(x) docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor) mainMenu.aEditCut.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:104] == ( + assert docEditor.getText()[x:x+50] == ( " nec erat ut nulla posuere commodo. Curabitur nisi" ) mainMenu.aEditPaste.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:104] == ( + assert docEditor.getText()[x:x+50] == ( "Pellentesque nec erat ut nulla posuere commodo. Cu" ) - docEditor.setCursorPosition(54) + docEditor.setCursorPosition(x) docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor) mainMenu.aEditCopy.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:104] == ( + assert docEditor.getText()[x:x+50] == ( "Pellentesque nec erat ut nulla posuere commodo. Cu" ) - docEditor.setCursorPosition(54) + docEditor.setCursorPosition(x) mainMenu.aEditPaste.activate(QAction.ActionEvent.Trigger) - assert docEditor.getText()[54:104] == ( + assert docEditor.getText()[x:x+50] == ( "PellentesquePellentesque nec erat ut nulla posuere" ) mainMenu.aEditUndo.activate(QAction.ActionEvent.Trigger) # Select Paragraph/All - docEditor.setCursorPosition(57) + docEditor.setCursorPosition(x+3) mainMenu.aSelectPar.activate(QAction.ActionEvent.Trigger) cursor = docEditor.textCursor() assert cursor.selectedText() == ( @@ -228,10 +229,10 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum): "nunc lacus, imperdiet nec posuere ac, interdum non lectus." ) - docEditor.setCursorPosition(57) + docEditor.setCursorPosition(x+3) mainMenu.aSelectAll.activate(QAction.ActionEvent.Trigger) cursor = docEditor.textCursor() - assert len(cursor.selectedText()) == 1910 + assert len(cursor.selectedText()) == 1928 # Clear the Text docEditor.clear() diff --git a/tests/test_gui/test_gui_sidebar.py b/tests/test_gui/test_gui_sidebar.py new file mode 100644 index 00000000..45e372db --- /dev/null +++ b/tests/test_gui/test_gui_sidebar.py @@ -0,0 +1,53 @@ +""" +novelWriter – Side Bar Class Tester +=================================== + +This file is a part of novelWriter +Copyright (C) 2025 Veronica Berglyd Olsen and novelWriter contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +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 . +""" +from __future__ import annotations + +import pytest + +from novelwriter import CONFIG +from novelwriter.constants import nwLabels +from novelwriter.enum import nwTheme + + +@pytest.mark.gui +def testGuiSideBar_CycleColourTheme(nwGUI): + """Test theme cycle feature on the side bar.""" + CONFIG.themeMode = nwTheme.AUTO + sidebar = nwGUI.sideBar + sidebar.mainGui.checkThemeUpdate = lambda *a: None + + # Run 3 Cycles + for _ in range(3): + + # Cycle Light + sidebar._cycleColurTheme() + assert CONFIG.themeMode == nwTheme.LIGHT + assert sidebar.tbTheme.toolTip() == nwLabels.THEME_MODE_LABEL[nwTheme.LIGHT] + + # Cycle Dark + sidebar._cycleColurTheme() + assert CONFIG.themeMode == nwTheme.DARK + assert sidebar.tbTheme.toolTip() == nwLabels.THEME_MODE_LABEL[nwTheme.DARK] + + # Cycle Auto + sidebar._cycleColurTheme() + assert CONFIG.themeMode == nwTheme.AUTO + assert sidebar.tbTheme.toolTip() == nwLabels.THEME_MODE_LABEL[nwTheme.AUTO] diff --git a/tests/test_gui/test_gui_statusbar.py b/tests/test_gui/test_gui_statusbar.py index a4d5d91f..efb19f48 100644 --- a/tests/test_gui/test_gui_statusbar.py +++ b/tests/test_gui/test_gui_statusbar.py @@ -31,7 +31,7 @@ from tests.tools import C, buildTestProject @pytest.mark.gui def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd): - """Test the the various features of the status bar.""" + """Test the various features of the status bar.""" buildTestProject(nwGUI, projPath) cHandle = SHARED.project.newFile("A Note", C.hCharRoot) newDoc = SHARED.project.storage.getDocument(cHandle) diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index c64f1172..933043b3 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -20,344 +20,402 @@ along with this program. If not, see . """ from __future__ import annotations -import sys +import json +from configparser import ConfigParser from pathlib import Path +from unittest.mock import MagicMock, Mock import pytest -from PyQt6.QtGui import QColor, QIcon, QPalette, QPixmap +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QColor, QFont, QFontDatabase, QIcon, QPalette, QPixmap, QStyleHints -from novelwriter import CONFIG, SHARED -from novelwriter.common import NWConfigParser -from novelwriter.config import DEF_GUI +from novelwriter import CONFIG +from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS from novelwriter.constants import nwLabels -from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType -from novelwriter.gui.theme import _listConf +from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwTheme +from novelwriter.gui.theme import ( + STYLES_BIG_TOOLBUTTON, STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON, GuiTheme, + ThemeMeta, _listContent +) from tests.mocked import causeOSError -from tests.tools import writeFile @pytest.mark.gui -def testGuiTheme_Main(qtbot, nwGUI, tstPaths): - """Test the theme class init.""" - mainTheme = SHARED.theme +def testGuiTheme_ParseColor(): + """Test the colour parsing.""" + theme = GuiTheme() - # Methods - # ======= + # Pre-Populate + theme._qColors["red"] = QColor(255, 0, 0) + theme._qColors["green"] = QColor(0, 255, 0) + theme._qColors["blue"] = QColor(0, 0, 255) + theme._qColors["grey"] = QColor(127, 127, 127) - mSize = mainTheme.getTextWidth("m") - assert mSize > 0 - assert mainTheme.getTextWidth("m", mainTheme.guiFont) == mSize + # By Name + assert theme.parseColor("red").getRgb() == (255, 0, 0, 255) + assert theme.parseColor("green").getRgb() == (0, 255, 0, 255) + assert theme.parseColor("blue").getRgb() == (0, 0, 255, 255) + assert theme.parseColor("bob").getRgb() == (0, 0, 0, 255) - # Scan for Themes - # =============== + # CSS Format + assert theme.parseColor("#ff0000").getRgb() == (255, 0, 0, 255) + assert theme.parseColor("#ff00007f").getRgb() == (255, 0, 0, 127) + assert theme.parseColor("#ff00").getRgb() == (0, 0, 0, 255) # Too short -> ignored + assert theme.parseColor("#ff00007f15").getRgb() == (0, 0, 0, 255) # Too long -> ignored - result = {} - _listConf({}, Path("not_a_path"), ".conf") - assert result == {} + # Name + Alpha + assert theme.parseColor("red:255").getRgb() == (255, 0, 0, 255) + assert theme.parseColor("red:127").getRgb() == (255, 0, 0, 127) + assert theme.parseColor("red:512").getRgb() == (255, 0, 0, 255) # Value truncated - themeOne = tstPaths.cnfDir / "themes" / "themeone.conf" - themeTwo = tstPaths.cnfDir / "themes" / "themetwo.conf" - writeFile(themeOne, "# Stuff") - writeFile(themeTwo, "# Stuff") + # Name + Lighter + assert theme.parseColor("grey:L100").getRgb() == (127, 127, 127, 255) + assert theme.parseColor("grey:L150").getRgb() == (190, 190, 190, 255) + assert theme.parseColor("grey:L50").getRgb() == (63, 63, 63, 255) - _listConf(result, tstPaths.cnfDir / "themes", ".conf") - assert result["themeone"] == themeOne - assert result["themetwo"] == themeTwo + # Name + Darker + assert theme.parseColor("grey:D100").getRgb() == (127, 127, 127, 255) + assert theme.parseColor("grey:D150").getRgb() == (85, 85, 85, 255) + assert theme.parseColor("grey:D50").getRgb() == (254, 254, 254, 255) - # Parse Colours - # ============= - - parser = NWConfigParser() - parser["Palette"] = { - "colour1": "100, 150, 200", # Valid - "colour2": "100, 150, 200, 250", # With alpha - "colour3": "100, 150, 200, 250, 300", # Too many values - "colour4": "250, 250", # Missing blue - "colour5": "-10, 127, 300", # Invalid red and blue - "colour6": "bob, 127, 255", # Invalid red - } - - # Test the parser for several valid and invalid values - assert mainTheme._parseColor(parser, "Palette", "colour1").getRgb() == (100, 150, 200, 255) - assert mainTheme._parseColor(parser, "Palette", "colour2").getRgb() == (100, 150, 200, 250) - assert mainTheme._parseColor(parser, "Palette", "colour3").getRgb() == (100, 150, 200, 250) - assert mainTheme._parseColor(parser, "Palette", "colour4").getRgb() == (250, 250, 0, 255) - assert mainTheme._parseColor(parser, "Palette", "colour5").getRgb() == (0, 0, 0, 0) - assert mainTheme._parseColor(parser, "Palette", "colour6").getRgb() == (0, 127, 255, 255) - - # The palette should load with the parsed values - mainTheme._setPalette(parser, "Palette", "colour1", QPalette.ColorRole.Window) - assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 255) - mainTheme._setPalette(parser, "Palette", "colour2", QPalette.ColorRole.Window) - assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 250) - mainTheme._setPalette(parser, "Palette", "colour3", QPalette.ColorRole.Window) - assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 250) - mainTheme._setPalette(parser, "Palette", "colour4", QPalette.ColorRole.Window) - assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (250, 250, 0, 255) - mainTheme._setPalette(parser, "Palette", "colour5", QPalette.ColorRole.Window) - assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 0) - mainTheme._setPalette(parser, "Palette", "colour6", QPalette.ColorRole.Window) - assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 127, 255, 255) - - # Non-existing value should return default colour - mainTheme._setPalette(parser, "Palette", "stuff", QPalette.ColorRole.Window) - assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 255) - - # qtbot.stop() + # Values + assert theme.parseColor("255, 0, 0").getRgb() == (255, 0, 0, 255) + assert theme.parseColor("255, 0, 0, 255").getRgb() == (255, 0, 0, 255) + assert theme.parseColor("255, 0, 0, 127").getRgb() == (255, 0, 0, 127) + assert theme.parseColor("255, 0, 0, 127, 42").getRgb() == (255, 0, 0, 127) # Truncated @pytest.mark.gui -def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths): - """Test the theme part of the class.""" - mainTheme = SHARED.theme +def testGuiTheme_ScanThemes(monkeypatch): + """Test the themes scanning.""" + theme = GuiTheme() - # List Themes + # Load built-in themes + files = [] + _listContent(files, CONFIG.assetPath("themes"), ".conf") + assert len(files) > 0 + + # Block reading theme files + with monkeypatch.context() as mp: + mp.setattr(ConfigParser, "read", causeOSError) + theme._scanThemes(files) + assert theme.colourThemes == {} + + # Read all themes correctly + theme._scanThemes(files) + assert len(theme.colourThemes) > 0 + + dark = theme.colourThemes[DEF_GUI_DARK] + light = theme.colourThemes[DEF_GUI_LIGHT] + + assert dark.name == "Default Dark Theme" + assert dark.dark is True + assert light.name == "Default Light Theme" + assert light.dark is False + + +@pytest.mark.gui +def testGuiTheme_LoadThemes(monkeypatch): + """Test loading themes.""" + theme = GuiTheme() + theme.iconCache = MagicMock() + CONFIG.lightTheme = DEF_GUI_LIGHT + CONFIG.darkTheme = DEF_GUI_DARK + + # Load built-in themes + files = [] + _listContent(files, CONFIG.assetPath("themes"), ".conf") + theme._scanThemes(files) + assert DEF_GUI_LIGHT in theme._allThemes + assert DEF_GUI_DARK in theme._allThemes + assert theme._currentTheme == "" + + # Load light theme + CONFIG.themeMode = nwTheme.LIGHT + theme.loadTheme() + assert theme._currentTheme == DEF_GUI_LIGHT + + # Load dark theme + CONFIG.themeMode = nwTheme.DARK + theme.loadTheme() + assert theme._currentTheme == DEF_GUI_DARK + + # Let auto switch back to light, then dark + with monkeypatch.context() as mp: + mp.setattr(CONFIG, "verQtValue", 0x060500) + mp.setattr(QStyleHints, "colorScheme", lambda *a: Qt.ColorScheme.Light) + CONFIG.themeMode = nwTheme.AUTO + theme.loadTheme() + assert theme._currentTheme == DEF_GUI_LIGHT + + with monkeypatch.context() as mp: + mp.setattr(CONFIG, "verQtValue", 0x060500) + mp.setattr(QStyleHints, "colorScheme", lambda *a: Qt.ColorScheme.Dark) + CONFIG.themeMode = nwTheme.AUTO + theme.loadTheme() + assert theme._currentTheme == DEF_GUI_DARK + + # Error Cases # =========== - # Block the reading of the files + # Invalid light theme + CONFIG.lightTheme = "not_a_theme" + CONFIG.themeMode = nwTheme.LIGHT + theme.loadTheme() + assert theme._currentTheme == DEF_GUI_LIGHT + + # Invalid dark theme + CONFIG.darkTheme = "not_a_theme" + CONFIG.themeMode = nwTheme.DARK + theme.loadTheme() + assert theme._currentTheme == DEF_GUI_DARK + + # Clear meta and check exit early cases + theme._meta = ThemeMeta() + assert theme._meta.name == "" + + # Reload dark should not load anything + CONFIG.darkTheme = DEF_GUI_DARK + CONFIG.themeMode = nwTheme.DARK + theme.loadTheme() + assert theme._meta.name == "" + + # Force reload, but fail parsing with monkeypatch.context() as mp: - mp.setattr("builtins.open", causeOSError) - assert mainTheme.listThemes() == [] + mp.setattr(ConfigParser, "read", causeOSError) + CONFIG.darkTheme = DEF_GUI_DARK + CONFIG.themeMode = nwTheme.DARK + theme.loadTheme(force=True) + assert theme._meta.name == "" - # Load the theme info, default themes first - themesList = mainTheme.listThemes() - assert themesList[0] == ("default_dark", "Default Dark Theme") - assert themesList[1] == ("default_light", "Default Light Theme") - assert themesList[2] == ("cyberpunk_night", "Cyberpunk Night") - assert themesList[3] == ("dracula", "Dracula") + # Invalid theme, and defaults are missing + del theme._allThemes[DEF_GUI_DARK] + del theme._allThemes[DEF_GUI_LIGHT] - # A second call should returned the cached list - assert mainTheme.listThemes() == mainTheme._themeList + CONFIG.lightTheme = "not_a_theme" + CONFIG.themeMode = nwTheme.LIGHT + theme.loadTheme(force=True) + assert theme._meta.name == "" - # Check handling of broken theme settings - CONFIG.guiTheme = "not_a_theme" - availThemes = mainTheme._availThemes - mainTheme._availThemes = {} - assert mainTheme.loadTheme() is False - mainTheme._availThemes = availThemes + CONFIG.darkTheme = "not_a_theme" + CONFIG.themeMode = nwTheme.DARK + theme.loadTheme(force=True) + assert theme._meta.name == "" - # Check handling of unreadable file - CONFIG.guiTheme = DEF_GUI - with monkeypatch.context() as mp: - mp.setattr("builtins.open", causeOSError) - assert mainTheme.loadTheme() is False - # Load Default Theme - # ================== +@pytest.mark.gui +def testGuiTheme_SpecialColors(tstPaths): + """Test handling special cases for colours.""" + theme = GuiTheme() + theme.iconCache = MagicMock() - if sys.platform != "win32": - # Set a mock colour for the window background - mainTheme._guiPalette.color(QPalette.ColorRole.Window).setRgb(0, 0, 0, 0) - - # Load the default theme - CONFIG.guiTheme = DEF_GUI - assert mainTheme.loadTheme() is True - - # This should load a standard palette - wCol = QPalette().color(QPalette.ColorRole.Window).getRgb() - assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == wCol - - # Mock Dark Theme - # =============== - - mockTheme: Path = tstPaths.cnfDir / "themes" / "test.conf" - mockTheme.write_text( + testTheme: Path = tstPaths.cnfDir / "themes" / "test.conf" + testTheme.write_text( "[Main]\n" "name = Test\n" + "mode = light\n" + "\n" + "[Base]\n" + "default = #cccccc\n" + "faded = #949494\n" + "red = #ff0000\n" + "orange = #ff7f00\n" + "yellow = #ffff00\n" + "green = #00ff00\n" + "cyan = #00ffff\n" + "blue = #0000ff\n" + "purple = #ff00ff\n" + "\n" + "[Project]\n" + "root = blue\n" + "folder = yellow\n" + "file = default\n" + "title = green\n" + "chapter = red\n" + "scene = blue\n" + "note = yellow\n" "\n" "[Palette]\n" - "window = 0, 0, 0\n" - "text = 255, 255, 255\n" + "window = #000000\n" + "text = #ffffff\n" ) - mainTheme._availThemes["test"] = mockTheme + theme._scanThemes([testTheme]) + assert len(theme.colourThemes) == 1 + CONFIG.themeMode = nwTheme.LIGHT + CONFIG.lightTheme = "test" - CONFIG.guiTheme = "test" - assert mainTheme.loadTheme() is True - assert mainTheme._guiPalette.window().color().getRgb() == (0, 0, 0, 255) - assert mainTheme._guiPalette.text().color().getRgb() == (255, 255, 255, 255) - assert mainTheme._guiPalette.light().color().getRgb() == (57, 57, 57, 255) - assert mainTheme.isDarkTheme is True + # Load theme + theme.loadTheme() - # Load Default Light Theme - # ======================== + # Since window is black, a lighter version should be generated + assert theme._guiPalette.light().color().getRgb() == (57, 57, 57, 255) - CONFIG.guiTheme = "default_light" - assert mainTheme.loadTheme() is True + # Reload with project override to red + CONFIG.iconColTree = "red" + CONFIG.iconColDocs = True + theme.loadTheme(force=True) - # Check a few values - assert mainTheme._guiPalette.color( - QPalette.ColorRole.Window - ).getRgb() == (239, 239, 239, 255) - assert mainTheme._guiPalette.color( - QPalette.ColorRole.WindowText - ).getRgb() == (0, 0, 0, 255) - assert mainTheme._guiPalette.color( - QPalette.ColorRole.Base - ).getRgb() == (255, 255, 255, 255) - assert mainTheme._guiPalette.color( - QPalette.ColorRole.AlternateBase - ).getRgb() == (224, 224, 224, 255) + assert theme.getBaseColor("root").getRgb() == (255, 0, 0, 255) + assert theme.getBaseColor("folder").getRgb() == (255, 0, 0, 255) + assert theme.getBaseColor("file").getRgb() == (204, 204, 204, 255) + assert theme.getBaseColor("title").getRgb() == (0, 255, 0, 255) + assert theme.getBaseColor("chapter").getRgb() == (255, 0, 0, 255) + assert theme.getBaseColor("scene").getRgb() == (0, 0, 255, 255) + assert theme.getBaseColor("note").getRgb() == (255, 255, 0, 255) - # Load Default Dark Theme - # ======================= - - CONFIG.guiTheme = "default_dark" - assert mainTheme.loadTheme() is True - - # Check a few values - assert mainTheme._guiPalette.color( - QPalette.ColorRole.Window).getRgb() == (54, 54, 54, 255) - assert mainTheme._guiPalette.color( - QPalette.ColorRole.WindowText).getRgb() == (204, 204, 204, 255) - assert mainTheme._guiPalette.color( - QPalette.ColorRole.Base).getRgb() == (62, 62, 62, 255) - assert mainTheme._guiPalette.color( - QPalette.ColorRole.AlternateBase).getRgb() == (78, 78, 78, 255) - - # qtbot.stop() - - -@pytest.mark.gui -def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI): - """Test the syntax part of the class.""" - mainTheme = SHARED.theme - - # List Themes - # =========== - - # Block the reading of the files - with monkeypatch.context() as mp: - mp.setattr("builtins.open", causeOSError) - assert mainTheme.listThemes() == [] - - # Load the syntax info - syntaxList = mainTheme.listSyntax() - assert syntaxList[0] == ("default_dark", "Default Dark") - assert syntaxList[1] == ("default_light", "Default Light") - - # A second call should returned the cached list - assert mainTheme.listSyntax() == mainTheme._syntaxList - - # Check handling of broken theme settings - availSyntax = mainTheme._availSyntax - mainTheme._availSyntax = {} - CONFIG.guiSyntax = "not_a_syntax" - assert mainTheme.loadSyntax() is False - mainTheme._availSyntax = availSyntax - - # Check handling of unreadable file - CONFIG.guiSyntax = "default_light" - with monkeypatch.context() as mp: - mp.setattr("builtins.open", causeOSError) - assert mainTheme.loadSyntax() is False - - # Load Default Light Syntax - # ========================= - - # Load the default syntax - CONFIG.guiSyntax = "default_light" - assert mainTheme.loadSyntax() is True - - # Check some values - assert mainTheme.syntaxMeta.name == "Default Light" - assert mainTheme.syntaxTheme.back == QColor(255, 255, 255) - assert mainTheme.syntaxTheme.text == QColor(0, 0, 0) - assert mainTheme.syntaxTheme.link == QColor(0, 0, 200) - - # Load Default Dark Theme - # ======================= - - # Load the default syntax - CONFIG.guiSyntax = "default_dark" - assert mainTheme.loadSyntax() is True - - # Check some values - assert mainTheme.syntaxMeta.name == "Default Dark" - assert mainTheme.syntaxTheme.back == QColor(42, 42, 42) - assert mainTheme.syntaxTheme.text == QColor(204, 204, 204) - assert mainTheme.syntaxTheme.link == QColor(102, 153, 204) - - # qtbot.stop() - - -@pytest.mark.gui -def testGuiTheme_IconThemes(qtbot, caplog, monkeypatch, nwGUI, tstPaths): - """Test the icon cache class.""" - iconCache = SHARED.theme.iconCache - - # Load Theme - # ========== - - # Invalid theme name - availThemes = iconCache._availThemes - iconCache._availThemes = {} - assert iconCache.loadTheme("not_a_theme") is False - iconCache._availThemes = availThemes - - # Check handling of unreadable file - with monkeypatch.context() as mp: - mp.setattr("builtins.open", causeOSError) - assert iconCache.loadTheme("material_rounded_normal") is False - - # Load working theme file - assert iconCache.loadTheme("material_rounded_normal") is True - assert iconCache.themeMeta.name == "Material Symbols - Rounded Medium" - - # Load with project colour override - purple = iconCache._svgColors["purple"] - assert iconCache._svgColors["root"] != purple - assert iconCache._svgColors["folder"] != purple - assert iconCache._svgColors["file"] != purple - assert iconCache._svgColors["title"] != purple - assert iconCache._svgColors["chapter"] != purple - assert iconCache._svgColors["scene"] != purple - assert iconCache._svgColors["note"] != purple + assert theme.getRawBaseColor("root") == b"#ff0000" + assert theme.getRawBaseColor("folder") == b"#ff0000" + assert theme.getRawBaseColor("file") == b"#cccccc" + assert theme.getRawBaseColor("title") == b"#00ff00" + assert theme.getRawBaseColor("chapter") == b"#ff0000" + assert theme.getRawBaseColor("scene") == b"#0000ff" + assert theme.getRawBaseColor("note") == b"#ffff00" + # Reload with project override to purple, also for docs CONFIG.iconColTree = "purple" - assert iconCache.loadTheme("material_rounded_normal") is True - assert iconCache._svgColors["root"] == purple - assert iconCache._svgColors["folder"] == purple - assert iconCache._svgColors["file"] == purple - assert iconCache._svgColors["title"] == purple - assert iconCache._svgColors["chapter"] == purple - assert iconCache._svgColors["scene"] == purple - assert iconCache._svgColors["note"] == purple + CONFIG.iconColDocs = False + theme.loadTheme(force=True) - # Change some colours - iconCache.setIconColor("root", QColor(255, 255, 255)) - assert iconCache._svgColors["root"] != purple - assert iconCache._svgColors["root"] == b"#ffffff" + assert theme.getBaseColor("root").getRgb() == (255, 0, 255, 255) + assert theme.getBaseColor("folder").getRgb() == (255, 0, 255, 255) + assert theme.getBaseColor("file").getRgb() == (255, 0, 255, 255) + assert theme.getBaseColor("title").getRgb() == (255, 0, 255, 255) + assert theme.getBaseColor("chapter").getRgb() == (255, 0, 255, 255) + assert theme.getBaseColor("scene").getRgb() == (255, 0, 255, 255) + assert theme.getBaseColor("note").getRgb() == (255, 0, 255, 255) - # List Themes - # =========== - - # Load error returns empty list - with monkeypatch.context() as mp: - mp.setattr("builtins.open", causeOSError) - themes = iconCache.listThemes() - assert themes == [] - - # Successful read - themes = iconCache.listThemes() - assert len(themes) > 1 - assert "material_rounded_normal" in dict(themes) - - # Load error doesn't matter on second read since list is cached - with monkeypatch.context() as mp: - mp.setattr("builtins.open", causeOSError) - assert iconCache.listThemes() == themes - - # qtbot.stop() + assert theme.getRawBaseColor("root") == b"#ff00ff" + assert theme.getRawBaseColor("folder") == b"#ff00ff" + assert theme.getRawBaseColor("file") == b"#ff00ff" + assert theme.getRawBaseColor("title") == b"#ff00ff" + assert theme.getRawBaseColor("chapter") == b"#ff00ff" + assert theme.getRawBaseColor("scene") == b"#ff00ff" + assert theme.getRawBaseColor("note") == b"#ff00ff" @pytest.mark.gui -def testGuiTheme_LoadIcons(qtbot, nwGUI): +def testGuiTheme_Methods(monkeypatch): + """Test other themes methods.""" + theme = GuiTheme() + theme.iconCache = MagicMock() + CONFIG.darkTheme = DEF_GUI_DARK + CONFIG.themeMode = nwTheme.DARK + + # Init theme + assert theme.colourThemes == {} + theme.initThemes() + assert theme._meta.name == "Default Dark Theme" + + # Text width + theme.guiFont = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont) + assert theme.getTextWidth("MMMMM") > theme.getTextWidth("MMM") + font = QFont(theme.guiFont) + font.setPointSizeF(0.5*font.pointSizeF()) + assert theme.getTextWidth("MMMMM", font) < theme.getTextWidth("MMMMM") + + # Detect desktop mode Qt 6.5+ + with monkeypatch.context() as mp: + mp.setattr(CONFIG, "verQtValue", 0x060500) + + mp.setattr(QStyleHints, "colorScheme", lambda *a: Qt.ColorScheme.Light) + assert theme.isDesktopDarkMode() is False + + mp.setattr(QStyleHints, "colorScheme", lambda *a: Qt.ColorScheme.Dark) + assert theme.isDesktopDarkMode() is True + + # Detect desktop mode Qt 6.4 + mockWhite = Mock() + mockWhite.color.return_value = QColor(255, 255, 255) + + mockBlack = Mock() + mockBlack.color.return_value = QColor(0, 0, 0) + + with monkeypatch.context() as mp: + mp.setattr(CONFIG, "verQtValue", 0x060400) + + mp.setattr(QPalette, "window", lambda *a: mockWhite) + mp.setattr(QPalette, "windowText", lambda *a: mockBlack) + assert theme.isDesktopDarkMode() is False + + mp.setattr(QPalette, "window", lambda *a: mockBlack) + mp.setattr(QPalette, "windowText", lambda *a: mockWhite) + assert theme.isDesktopDarkMode() is True + + # Stylesheets + assert theme.getStyleSheet(STYLES_FLAT_TABS) != "" + assert theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) != "" + assert theme.getStyleSheet(STYLES_BIG_TOOLBUTTON) != "" + assert theme.getStyleSheet("stuff") == "" + + +@pytest.mark.gui +def testGuiTheme_ScanIcons(monkeypatch): + """Test the icon theme scanning.""" + theme = GuiTheme() + + # Load built-in themes + files = [] + _listContent(files, CONFIG.assetPath("icons"), ".icons") + assert len(files) > 0 + + # Block reading theme files + with monkeypatch.context() as mp: + mp.setattr("builtins.open", causeOSError) + theme.iconCache._scanThemes(files) + assert theme.iconCache.iconThemes == {} + + # Read all themes correctly + theme.iconCache._scanThemes(files) + assert len(theme.iconCache.iconThemes) > 0 + + +@pytest.mark.gui +def testGuiTheme_IconThemes(monkeypatch): + """Test loading icon theme.""" + CONFIG.lightTheme = DEF_GUI_LIGHT + CONFIG.themeMode = nwTheme.LIGHT + CONFIG.iconTheme = DEF_ICONS + + theme = GuiTheme() + + # Init should load default theme + theme.initThemes() + assert theme.iconCache._meta.name == "Material Symbols - Rounded Medium" + assert DEF_ICONS in theme.iconCache.iconThemes + + # Load default theme directly + theme.iconCache._meta = ThemeMeta() + theme.iconCache.loadTheme("DEF_ICONS") + assert theme.iconCache._meta.name == "Material Symbols - Rounded Medium" + + # Failed loading should load nothing + theme.iconCache._meta = ThemeMeta() + with monkeypatch.context() as mp: + mp.setattr("builtins.open", causeOSError) + theme.iconCache.loadTheme("DEF_ICONS") + assert theme.iconCache._meta.name == "" + + # Reload with non-existent theme should reload default + theme.iconCache._meta = ThemeMeta() + theme.iconCache.loadTheme("not_a_theme") + assert theme.iconCache._meta.name == "Material Symbols - Rounded Medium" + + # If default theme is missing, load nothing + del theme.iconCache._allThemes[DEF_ICONS] + theme.iconCache._meta = ThemeMeta() + theme.iconCache.loadTheme("not_a_theme") + assert theme.iconCache._meta.name == "" + + +@pytest.mark.gui +def testGuiTheme_LoadIcons(): """Test the icon cache class.""" - iconCache = SHARED.theme.iconCache - assert iconCache.loadTheme("material_rounded_normal") is True + theme = GuiTheme() + theme.initThemes() + iconCache = theme.iconCache # Load Icons # ========== @@ -455,14 +513,13 @@ def testGuiTheme_LoadIcons(qtbot, nwGUI): nwItemType.NO_TYPE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H0" ) == iconCache._noIcon - # qtbot.stop() - @pytest.mark.gui -def testGuiTheme_LoadDecorations(qtbot, monkeypatch, nwGUI): +def testGuiTheme_LoadDecorations(monkeypatch): """Test the icon cache class.""" - iconCache = SHARED.theme.iconCache - assert iconCache.loadTheme("material_rounded_normal") is True + theme = GuiTheme() + theme.initThemes() + iconCache = theme.iconCache # Load Decorations # ================ @@ -520,4 +577,114 @@ def testGuiTheme_LoadDecorations(qtbot, monkeypatch, nwGUI): assert iconCache.getHeaderDecorationNarrow(5) == iconCache._headerDecNarrow[5] assert iconCache.getHeaderDecorationNarrow(6) == iconCache._headerDecNarrow[5] - # qtbot.stop() + +THEMES = [] +_listContent(THEMES, CONFIG.assetPath("themes"), ".conf") + + +@pytest.mark.gui +@pytest.mark.parametrize("theme", [a.stem for a in THEMES]) +def testGuiTheme_CheckTheme(theme): + """Test loading all themes.""" + themes = GuiTheme() + themes.iconCache = MagicMock() + themes._scanThemes(THEMES) + + assert theme in themes.colourThemes + current = themes.colourThemes[theme] + if current.dark: + CONFIG.darkTheme = theme + CONFIG.themeMode = nwTheme.DARK + else: + CONFIG.lightTheme = theme + CONFIG.themeMode = nwTheme.LIGHT + + # Check loading + themes.loadTheme() + assert themes._meta.name == current.name + assert themes.isDarkTheme == current.dark + + # Check completeness + parser = ConfigParser() + parser.read(current.path, encoding="utf-8") + + sections = ["Main", "Base", "Project", "Palette", "GUI", "Syntax"] + assert sorted(parser.sections()) == sorted(sections) + + structure = { + "Main": [ + "name", "mode", "description", "author", # The rest are not required + ], + "Base": [ + "base", "default", "faded", "red", "orange", "yellow", "green", + "cyan", "blue", "purple", + ], + "Project": [ + "root", "folder", "file", "title", "chapter", "scene", "note", + "active", "inactive", "disabled", + ], + "Palette": [ + "window", "windowtext", "base", "alternatebase", "text", + "tooltipbase", "tooltiptext", "button", "buttontext", "brighttext", + "highlight", "highlightedtext", "link", "linkvisited", "accent", + ], + "GUI": [ + "helptext", "fadedtext", "errortext", + ], + "Syntax": [ + "background", "text", "line", "link", "headertext", "headertag", + "emphasis", "dialog", "altdialog", "hidden", "note", "shortcode", + "keyword", "tag", "value", "optional", "spellcheckline", + "errorline", "replacetag", "modifier", "texthighlight", + ], + } + optional = ["credit", "url"] + missing = [] + for section, options in structure.items(): + missing.extend(opt for opt in options if opt not in parser[section]) + assert missing == [], "Missing options in theme file" + + # Check deprecated + deprecated = [] + for section in sections: + deprecated.extend( + opt for opt in parser[section] + if opt not in structure[section] and opt not in optional + ) + assert deprecated == [], "Deprecated options in theme file" + + +ICONS = [] +_listContent(ICONS, CONFIG.assetPath("icons"), ".icons") + + +@pytest.mark.gui +@pytest.mark.parametrize("icons", [a.stem for a in ICONS]) +def testGuiTheme_CheckIcons(icons, tstPaths): + """Test loading all icons.""" + keysFile: Path = tstPaths.filesDir / "all_icons.json" + iconKeys = json.loads(keysFile.read_text(encoding="utf-8")) + assert isinstance(iconKeys, list) + + CONFIG.lightTheme = DEF_GUI_LIGHT + CONFIG.themeMode = nwTheme.LIGHT + + themes = GuiTheme() + themes.initThemes() + iconCache = themes.iconCache + + assert icons in iconCache.iconThemes + current = iconCache.iconThemes[icons] + CONFIG.iconTheme = icons + + # Check loading + iconCache.loadTheme(icons) + assert iconCache._meta.name == current.name + + # Check completeness + missing = [key for key in iconKeys if key not in iconCache._svgData] + assert missing == [], "Missing keys in icons file" + + # Check deprecated + deprecated = [key for key in iconCache._svgData if key not in iconKeys] + assert deprecated == [], "Deprecated keys in icons file" diff --git a/tests/test_text/test_text_patterns.py b/tests/test_text/test_text_patterns.py index e6a8b599..2670401e 100644 --- a/tests/test_text/test_text_patterns.py +++ b/tests/test_text/test_text_patterns.py @@ -157,6 +157,15 @@ def testTextPatterns_Markdown(): assert allMatches(regEx, "one ~two~~ three") == [] assert allMatches(regEx, "one~~two~~three") == [] + # Mark + regEx = REGEX_PATTERNS.markdownMark + assert allMatches(regEx, "one ==two== three") == [ + [("==two==", 4, 11), ("==", 4, 6), ("two", 6, 9), ("==", 9, 11)] + ] + assert allMatches(regEx, "one ==two= three") == [] + assert allMatches(regEx, "one =two== three") == [] + assert allMatches(regEx, "one==two==three") == [] + @pytest.mark.core def testTextPatterns_ShortcodesPlain(): diff --git a/utils/icon_themes.py b/utils/icon_themes.py index 00d75714..acb2e230 100644 --- a/utils/icon_themes.py +++ b/utils/icon_themes.py @@ -33,6 +33,7 @@ from utils.common import ROOT_DIR UTILS = Path(__file__).parent ET.register_namespace("", "http://www.w3.org/2000/svg") ICONS = [ + # Remember to also update tests/files/all_icons.json for test coverage "alert_error", "alert_info", "alert_question", @@ -90,6 +91,10 @@ ICONS = [ "sb_search", "sb_stats", + "theme_light", + "theme_dark", + "theme_auto", + "add", "bookmarks", "browse", diff --git a/utils/icon_themes/font_awesome.json b/utils/icon_themes/font_awesome.json index 5ed09376..d98dc9e2 100644 --- a/utils/icon_themes/font_awesome.json +++ b/utils/icon_themes/font_awesome.json @@ -56,6 +56,10 @@ "sb_search": "magnifying-glass", "sb_stats": "chart-simple", + "theme_light": "sun", + "theme_dark": "moon", + "theme_auto": "circle-half-stroke", + "add": "plus", "bookmarks": "bookmark", "browse": "folder-open", diff --git a/utils/icon_themes/material_symbols.json b/utils/icon_themes/material_symbols.json index 230f5c60..61c1975a 100644 --- a/utils/icon_themes/material_symbols.json +++ b/utils/icon_themes/material_symbols.json @@ -56,6 +56,10 @@ "sb_search": "search", "sb_stats": "bar_chart", + "theme_light": "light_mode", + "theme_dark": "dark_mode", + "theme_auto": "contrast", + "add": "add", "bookmarks": "bookmarks", "browse": "folder_open", diff --git a/utils/icon_themes/remix.json b/utils/icon_themes/remix.json index 300b8bbe..0ac173ec 100644 --- a/utils/icon_themes/remix.json +++ b/utils/icon_themes/remix.json @@ -56,6 +56,10 @@ "sb_search": "file-search", "sb_stats": "bar-chart-fill", + "theme_light": "sun", + "theme_dark": "moon", + "theme_auto": "contrast", + "add": "add", "bookmarks": "bookmark", "browse": "folder-2",