diff --git a/CREDITS.md b/CREDITS.md
index dc6b4c53..66d2f64b 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -31,7 +31,6 @@ The following libraries are dependencies of novelWriter:
Some of the assets bundled with novelWriter were adapted from the following sources:
* Typicons icons by Stephen Hutchings (CC BY-SA 4.0)
-* Cantarell font by Dave Crossland (OPEN FONT LICENSE Version 1.1)
* Tomorrow syntax themes by Chris Kempson (MIT License)
* Owl syntax themes by Sarah Drasner (MIT License)
* Solarized themes by Ethan Schoonover, added by @nullbasis (MIT License)
diff --git a/novelWriter.pro b/novelWriter.pro
index d1bc3e7b..2d948874 100644
--- a/novelWriter.pro
+++ b/novelWriter.pro
@@ -22,7 +22,6 @@ SOURCES += \
novelwriter/gui/projdetails.py \
novelwriter/gui/projtree.py \
novelwriter/gui/statusbar.py \
- novelwriter/gui/theme.py \
novelwriter/tools/build.py \
novelwriter/tools/projwizard.py \
novelwriter/tools/writingstats.py \
diff --git a/novelwriter/assets/fonts/Cantarell/Cantarell-Bold.ttf b/novelwriter/assets/fonts/Cantarell/Cantarell-Bold.ttf
deleted file mode 100644
index ca364663..00000000
Binary files a/novelwriter/assets/fonts/Cantarell/Cantarell-Bold.ttf and /dev/null differ
diff --git a/novelwriter/assets/fonts/Cantarell/Cantarell-BoldItalic.ttf b/novelwriter/assets/fonts/Cantarell/Cantarell-BoldItalic.ttf
deleted file mode 100644
index a54eab4c..00000000
Binary files a/novelwriter/assets/fonts/Cantarell/Cantarell-BoldItalic.ttf and /dev/null differ
diff --git a/novelwriter/assets/fonts/Cantarell/Cantarell-Italic.ttf b/novelwriter/assets/fonts/Cantarell/Cantarell-Italic.ttf
deleted file mode 100644
index 725b1986..00000000
Binary files a/novelwriter/assets/fonts/Cantarell/Cantarell-Italic.ttf and /dev/null differ
diff --git a/novelwriter/assets/fonts/Cantarell/Cantarell-Regular.ttf b/novelwriter/assets/fonts/Cantarell/Cantarell-Regular.ttf
deleted file mode 100644
index ceb92751..00000000
Binary files a/novelwriter/assets/fonts/Cantarell/Cantarell-Regular.ttf and /dev/null differ
diff --git a/novelwriter/assets/fonts/Cantarell/OFL.txt b/novelwriter/assets/fonts/Cantarell/OFL.txt
deleted file mode 100644
index 4e495019..00000000
--- a/novelwriter/assets/fonts/Cantarell/OFL.txt
+++ /dev/null
@@ -1,93 +0,0 @@
-Copyright (c) 2009-2010, Understanding Limited (dave@understandinglimited.com)
-
-This Font Software is licensed under the SIL Open Font License, Version 1.1.
-This license is copied below, and is also available with a FAQ at:
-http://scripts.sil.org/OFL
-
-
------------------------------------------------------------
-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
------------------------------------------------------------
-
-PREAMBLE
-The goals of the Open Font License (OFL) are to stimulate worldwide
-development of collaborative font projects, to support the font creation
-efforts of academic and linguistic communities, and to provide a free and
-open framework in which fonts may be shared and improved in partnership
-with others.
-
-The OFL allows the licensed fonts to be used, studied, modified and
-redistributed freely as long as they are not sold by themselves. The
-fonts, including any derivative works, can be bundled, embedded,
-redistributed and/or sold with any software provided that any reserved
-names are not used by derivative works. The fonts and derivatives,
-however, cannot be released under any other type of license. The
-requirement for fonts to remain under this license does not apply
-to any document created using the fonts or their derivatives.
-
-DEFINITIONS
-"Font Software" refers to the set of files released by the Copyright
-Holder(s) under this license and clearly marked as such. This may
-include source files, build scripts and documentation.
-
-"Reserved Font Name" refers to any names specified as such after the
-copyright statement(s).
-
-"Original Version" refers to the collection of Font Software components as
-distributed by the Copyright Holder(s).
-
-"Modified Version" refers to any derivative made by adding to, deleting,
-or substituting -- in part or in whole -- any of the components of the
-Original Version, by changing formats or by porting the Font Software to a
-new environment.
-
-"Author" refers to any designer, engineer, programmer, technical
-writer or other person who contributed to the Font Software.
-
-PERMISSION & CONDITIONS
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the Font Software, to use, study, copy, merge, embed, modify,
-redistribute, and sell modified and unmodified copies of the Font
-Software, subject to the following conditions:
-
-1) Neither the Font Software nor any of its individual components,
-in Original or Modified Versions, may be sold by itself.
-
-2) Original or Modified Versions of the Font Software may be bundled,
-redistributed and/or sold with any software, provided that each copy
-contains the above copyright notice and this license. These can be
-included either as stand-alone text files, human-readable headers or
-in the appropriate machine-readable metadata fields within text or
-binary files as long as those fields can be easily viewed by the user.
-
-3) No Modified Version of the Font Software may use the Reserved Font
-Name(s) unless explicit written permission is granted by the corresponding
-Copyright Holder. This restriction only applies to the primary font name as
-presented to the users.
-
-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
-Software shall not be used to promote, endorse or advertise any
-Modified Version, except to acknowledge the contribution(s) of the
-Copyright Holder(s) and the Author(s) or with their explicit written
-permission.
-
-5) The Font Software, modified or unmodified, in part or in whole,
-must be distributed entirely under this license, and must not be
-distributed under any other license. The requirement for fonts to
-remain under this license does not apply to any document created
-using the Font Software.
-
-TERMINATION
-This license becomes null and void if any of the above conditions are
-not met.
-
-DISCLAIMER
-THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
-OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
-COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
-DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
-OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/novelwriter/assets/icons/fallback/README.md b/novelwriter/assets/icons/fallback/README.md
deleted file mode 100644
index 45a06454..00000000
--- a/novelwriter/assets/icons/fallback/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-## License
-
-All icons in the fallback folder are modified from the Typicons theme by
-Stephen Hutchings, released under CC BY-SA 4.0.
-
-See: https://github.com/stephenhutchings/typicons.font
diff --git a/novelwriter/assets/icons/fallback/add-dark.svg b/novelwriter/assets/icons/fallback/add-dark.svg
deleted file mode 100644
index 0846263d..00000000
--- a/novelwriter/assets/icons/fallback/add-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/add.svg b/novelwriter/assets/icons/fallback/add.svg
deleted file mode 100644
index e7399157..00000000
--- a/novelwriter/assets/icons/fallback/add.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/backward-dark.svg b/novelwriter/assets/icons/fallback/backward-dark.svg
deleted file mode 100644
index bc1c987f..00000000
--- a/novelwriter/assets/icons/fallback/backward-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/backward.svg b/novelwriter/assets/icons/fallback/backward.svg
deleted file mode 100644
index 2e7af570..00000000
--- a/novelwriter/assets/icons/fallback/backward.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/bullet-off-dark.svg b/novelwriter/assets/icons/fallback/bullet-off-dark.svg
deleted file mode 100644
index 46e56070..00000000
--- a/novelwriter/assets/icons/fallback/bullet-off-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/bullet-off.svg b/novelwriter/assets/icons/fallback/bullet-off.svg
deleted file mode 100644
index 2e2712be..00000000
--- a/novelwriter/assets/icons/fallback/bullet-off.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/bullet-on-dark.svg b/novelwriter/assets/icons/fallback/bullet-on-dark.svg
deleted file mode 100644
index ba3d9739..00000000
--- a/novelwriter/assets/icons/fallback/bullet-on-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/bullet-on.svg b/novelwriter/assets/icons/fallback/bullet-on.svg
deleted file mode 100644
index 7a077ddd..00000000
--- a/novelwriter/assets/icons/fallback/bullet-on.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/check-dark.svg b/novelwriter/assets/icons/fallback/check-dark.svg
deleted file mode 100644
index 0c1bea67..00000000
--- a/novelwriter/assets/icons/fallback/check-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/check.svg b/novelwriter/assets/icons/fallback/check.svg
deleted file mode 100644
index 894c6186..00000000
--- a/novelwriter/assets/icons/fallback/check.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/cross-dark.svg b/novelwriter/assets/icons/fallback/cross-dark.svg
deleted file mode 100644
index f863c42b..00000000
--- a/novelwriter/assets/icons/fallback/cross-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/cross.svg b/novelwriter/assets/icons/fallback/cross.svg
deleted file mode 100644
index bc451278..00000000
--- a/novelwriter/assets/icons/fallback/cross.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/edit-dark.svg b/novelwriter/assets/icons/fallback/edit-dark.svg
deleted file mode 100644
index f7665c6b..00000000
--- a/novelwriter/assets/icons/fallback/edit-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/edit.svg b/novelwriter/assets/icons/fallback/edit.svg
deleted file mode 100644
index ddd96f64..00000000
--- a/novelwriter/assets/icons/fallback/edit.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/forward-dark.svg b/novelwriter/assets/icons/fallback/forward-dark.svg
deleted file mode 100644
index 18eafe98..00000000
--- a/novelwriter/assets/icons/fallback/forward-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/forward.svg b/novelwriter/assets/icons/fallback/forward.svg
deleted file mode 100644
index 8da983ca..00000000
--- a/novelwriter/assets/icons/fallback/forward.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/hash-dark.svg b/novelwriter/assets/icons/fallback/hash-dark.svg
deleted file mode 100644
index 9f3db253..00000000
--- a/novelwriter/assets/icons/fallback/hash-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/hash.svg b/novelwriter/assets/icons/fallback/hash.svg
deleted file mode 100644
index 99fddb80..00000000
--- a/novelwriter/assets/icons/fallback/hash.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/maximise-dark.svg b/novelwriter/assets/icons/fallback/maximise-dark.svg
deleted file mode 100644
index 1ce767a3..00000000
--- a/novelwriter/assets/icons/fallback/maximise-dark.svg
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/maximise.svg b/novelwriter/assets/icons/fallback/maximise.svg
deleted file mode 100644
index 0e1d4a86..00000000
--- a/novelwriter/assets/icons/fallback/maximise.svg
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/minimise-dark.svg b/novelwriter/assets/icons/fallback/minimise-dark.svg
deleted file mode 100644
index 4bc48ce2..00000000
--- a/novelwriter/assets/icons/fallback/minimise-dark.svg
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/minimise.svg b/novelwriter/assets/icons/fallback/minimise.svg
deleted file mode 100644
index b0a1972f..00000000
--- a/novelwriter/assets/icons/fallback/minimise.svg
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/proj_nwx.svg b/novelwriter/assets/icons/fallback/proj_nwx.svg
deleted file mode 100644
index 08546d15..00000000
--- a/novelwriter/assets/icons/fallback/proj_nwx.svg
+++ /dev/null
@@ -1,207 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/reference-dark.svg b/novelwriter/assets/icons/fallback/reference-dark.svg
deleted file mode 100644
index 4d98eb91..00000000
--- a/novelwriter/assets/icons/fallback/reference-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/reference.svg b/novelwriter/assets/icons/fallback/reference.svg
deleted file mode 100644
index b39357c9..00000000
--- a/novelwriter/assets/icons/fallback/reference.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/refresh-dark.svg b/novelwriter/assets/icons/fallback/refresh-dark.svg
deleted file mode 100644
index 31367a18..00000000
--- a/novelwriter/assets/icons/fallback/refresh-dark.svg
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/refresh.svg b/novelwriter/assets/icons/fallback/refresh.svg
deleted file mode 100644
index c87d3257..00000000
--- a/novelwriter/assets/icons/fallback/refresh.svg
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/remove-dark.svg b/novelwriter/assets/icons/fallback/remove-dark.svg
deleted file mode 100644
index 799e4ff3..00000000
--- a/novelwriter/assets/icons/fallback/remove-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/remove.svg b/novelwriter/assets/icons/fallback/remove.svg
deleted file mode 100644
index eb40a0e2..00000000
--- a/novelwriter/assets/icons/fallback/remove.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/search-dark.svg b/novelwriter/assets/icons/fallback/search-dark.svg
deleted file mode 100644
index ecf47350..00000000
--- a/novelwriter/assets/icons/fallback/search-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/search-replace.svg b/novelwriter/assets/icons/fallback/search-replace.svg
deleted file mode 100644
index cbf10912..00000000
--- a/novelwriter/assets/icons/fallback/search-replace.svg
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/search.svg b/novelwriter/assets/icons/fallback/search.svg
deleted file mode 100644
index d541d53b..00000000
--- a/novelwriter/assets/icons/fallback/search.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/sticky-off-dark.svg b/novelwriter/assets/icons/fallback/sticky-off-dark.svg
deleted file mode 100644
index f571d5c9..00000000
--- a/novelwriter/assets/icons/fallback/sticky-off-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/sticky-off.svg b/novelwriter/assets/icons/fallback/sticky-off.svg
deleted file mode 100644
index 57b2b498..00000000
--- a/novelwriter/assets/icons/fallback/sticky-off.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/sticky-on-dark.svg b/novelwriter/assets/icons/fallback/sticky-on-dark.svg
deleted file mode 100644
index 62c6a66b..00000000
--- a/novelwriter/assets/icons/fallback/sticky-on-dark.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/fallback/sticky-on.svg b/novelwriter/assets/icons/fallback/sticky-on.svg
deleted file mode 100644
index 3473b82f..00000000
--- a/novelwriter/assets/icons/fallback/sticky-on.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
diff --git a/novelwriter/assets/icons/system/icons.conf b/novelwriter/assets/icons/system/icons.conf
deleted file mode 100644
index 9f94d37b..00000000
--- a/novelwriter/assets/icons/system/icons.conf
+++ /dev/null
@@ -1,8 +0,0 @@
-##
-# System Theme
-# Dummy theme that relies on Qt and novelWriter fallback icons
-##
-
-[Main]
-name = System Theme
-description = Dummy theme that relies on Qt and novelWriter fallback icons
diff --git a/novelwriter/assets/icons/typicons_dark/README.md b/novelwriter/assets/icons/typicons_dark/README.md
index cfa5ea08..48c3f4ef 100644
--- a/novelwriter/assets/icons/typicons_dark/README.md
+++ b/novelwriter/assets/icons/typicons_dark/README.md
@@ -1,5 +1,29 @@
-## Typicons
+# Typicons for Dark Backgrounds
+
+This theme is based on Typicons. All files are prefixed, depending on whether they are based
+directly on original Typicons, redesigned, or designed from scratch.
+
+## Typicons Icons
+
+The files have a `typ_` prefix. These are colourised and rescaled Typicons, but with no other
+modifications.
Copyright: Stephen Hutchings
Source: https://github.com/stephenhutchings/typicons.font
License: [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
+
+## Original Icons
+
+The files have a `nw_` prefix. These are made completely from scratch for novelWriter and are not
+using any design elements from Typicons.
+
+Copyright: Veronica Berglyd Olsen
+License: [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
+
+## Mixed Icons
+
+The files have a `mixed_` prefix. These are redesigned from Typicons components, modified from
+Typicons, or consist of a mix of Typicons and new elements.
+
+Copyright: Stephen Hutchings, Veronica Berglyd Olsen
+License: [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf
index 99bee3fe..e06aba68 100644
--- a/novelwriter/assets/icons/typicons_dark/icons.conf
+++ b/novelwriter/assets/icons/typicons_dark/icons.conf
@@ -7,57 +7,70 @@
##
[Main]
-name = Typicons Dark
-description = Colourised icons for dark GUI theme based on Typicons.
-author = Veronica Berglyd Olsen (adaptation)
-credit = Stephen Hutchings (icon design)
-url = https://github.com/stephenhutchings/typicons.font
-license = CC BY-SA 4.0
-licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
+name = Typicons Dark
+description = Colourised icons for dark GUI theme based on Typicons.
+author = Veronica Berglyd Olsen (adaptation)
+credit = Stephen Hutchings (icon design)
+url = https://github.com/stephenhutchings/typicons.font
+license = CC BY-SA 4.0
+licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
[Map]
-cls_none = cancel.svg
-cls_novel = book.svg
-cls_plot = puzzle.svg
-cls_character = user.svg
-cls_world = location.svg
-cls_timeline = calendar.svg
-cls_object = key.svg
-cls_entity = flag.svg
-cls_custom = star.svg
-cls_archive = delete.svg
-cls_trash = trash.svg
-proj_document = document-text.svg
-proj_title = document-title.svg
-proj_chapter = document-chapter.svg
-proj_scene = document-scene.svg
-proj_note = document-note.svg
-proj_folder = folder.svg
-doc_h0 = heading0.svg
-doc_h1 = heading1.svg
-doc_h2 = heading2.svg
-doc_h3 = heading3.svg
-doc_h4 = heading4.svg
-delete = delete.svg
-add = plus.svg
-remove = minus.svg
-close = times.svg
-refresh = refresh.svg
-maximise = arrow-maximise.svg
-minimise = arrow-minimise.svg
-done = input-checked.svg
-search = search.svg
-search-replace = search-replace.svg
-clear = backspace.svg
-save = download.svg
-edit = pencil.svg
-check = tick.svg
-cross = times.svg
-hash = hash.svg
-reference = at.svg
-backward = chevron-left.svg
-forward = chevron-right.svg
-sticky-on = pin.svg
-sticky-off = pin-outline.svg
-bullet-on = media-record.svg
-bullet-off = media-record-outline.svg
+add = typ_plus.svg
+backward = typ_chevron-left.svg
+bullet-off = typ_media-record-outline.svg
+bullet-on = typ_media-record.svg
+check = typ_tick.svg
+clear = typ_backspace.svg
+close = typ_times.svg
+cls_archive = typ_delete.svg
+cls_character = typ_user.svg
+cls_custom = typ_star.svg
+cls_entity = typ_flag.svg
+cls_none = typ_cancel.svg
+cls_novel = typ_book.svg
+cls_object = typ_key.svg
+cls_plot = typ_puzzle.svg
+cls_timeline = typ_calendar.svg
+cls_trash = typ_trash.svg
+cls_world = typ_location.svg
+cross = typ_times.svg
+delete = typ_delete.svg
+doc_h0 = mixed_heading0.svg
+doc_h1 = mixed_heading1.svg
+doc_h2 = mixed_heading2.svg
+doc_h3 = mixed_heading3.svg
+doc_h4 = mixed_heading4.svg
+done = typ_input-checked.svg
+edit = typ_pencil.svg
+forward = typ_chevron-right.svg
+hash = typ_hash.svg
+maximise = typ_arrow-maximise.svg
+minimise = typ_arrow-minimise.svg
+proj_chapter = mixed_document-chapter.svg
+proj_document = typ_document-text.svg
+proj_folder = typ_folder.svg
+proj_note = mixed_document-note.svg
+proj_scene = mixed_document-scene.svg
+proj_title = mixed_document-title.svg
+reference = typ_at.svg
+refresh = typ_refresh.svg
+remove = typ_minus.svg
+save = typ_download.svg
+search = typ_search.svg
+search_cancel = typ_cancel-grey.svg
+search_case = nw_search-case.svg
+search_loop = typ_arrow-repeat-grey.svg
+search_preserve = nw_search-preserve.svg
+search_project = typ_arrow-down-thick-grey.svg
+search_regex = nw_search-regex.svg
+search_replace = mixed_search-replace.svg
+search_word = nw_search-word.svg
+settings = typ_cog.svg
+status_idle = typ_media-pause-grey.svg
+status_lang = typ_globe-grey.svg
+status_lines = typ_th-list-grey.svg
+status_stats = typ_chart-bar-grey.svg
+status_time = typ_stopwatch-grey.svg
+sticky-off = typ_pin-outline.svg
+sticky-on = typ_pin.svg
diff --git a/novelwriter/assets/icons/typicons_dark/document-chapter.svg b/novelwriter/assets/icons/typicons_dark/mixed_document-chapter.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/document-chapter.svg
rename to novelwriter/assets/icons/typicons_dark/mixed_document-chapter.svg
diff --git a/novelwriter/assets/icons/typicons_dark/document-note.svg b/novelwriter/assets/icons/typicons_dark/mixed_document-note.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/document-note.svg
rename to novelwriter/assets/icons/typicons_dark/mixed_document-note.svg
diff --git a/novelwriter/assets/icons/typicons_dark/document-scene.svg b/novelwriter/assets/icons/typicons_dark/mixed_document-scene.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/document-scene.svg
rename to novelwriter/assets/icons/typicons_dark/mixed_document-scene.svg
diff --git a/novelwriter/assets/icons/typicons_dark/document-title.svg b/novelwriter/assets/icons/typicons_dark/mixed_document-title.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/document-title.svg
rename to novelwriter/assets/icons/typicons_dark/mixed_document-title.svg
diff --git a/novelwriter/assets/icons/typicons_dark/heading0.svg b/novelwriter/assets/icons/typicons_dark/mixed_heading0.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/heading0.svg
rename to novelwriter/assets/icons/typicons_dark/mixed_heading0.svg
diff --git a/novelwriter/assets/icons/typicons_dark/heading1.svg b/novelwriter/assets/icons/typicons_dark/mixed_heading1.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/heading1.svg
rename to novelwriter/assets/icons/typicons_dark/mixed_heading1.svg
diff --git a/novelwriter/assets/icons/typicons_dark/heading2.svg b/novelwriter/assets/icons/typicons_dark/mixed_heading2.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/heading2.svg
rename to novelwriter/assets/icons/typicons_dark/mixed_heading2.svg
diff --git a/novelwriter/assets/icons/typicons_dark/heading3.svg b/novelwriter/assets/icons/typicons_dark/mixed_heading3.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/heading3.svg
rename to novelwriter/assets/icons/typicons_dark/mixed_heading3.svg
diff --git a/novelwriter/assets/icons/typicons_dark/heading4.svg b/novelwriter/assets/icons/typicons_dark/mixed_heading4.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/heading4.svg
rename to novelwriter/assets/icons/typicons_dark/mixed_heading4.svg
diff --git a/novelwriter/assets/icons/fallback/search-replace-dark.svg b/novelwriter/assets/icons/typicons_dark/mixed_search-replace.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search-replace-dark.svg
rename to novelwriter/assets/icons/typicons_dark/mixed_search-replace.svg
diff --git a/novelwriter/assets/icons/fallback/search_case-dark.svg b/novelwriter/assets/icons/typicons_dark/nw_search-case.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_case-dark.svg
rename to novelwriter/assets/icons/typicons_dark/nw_search-case.svg
diff --git a/novelwriter/assets/icons/fallback/search_preserve-dark.svg b/novelwriter/assets/icons/typicons_dark/nw_search-preserve.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_preserve-dark.svg
rename to novelwriter/assets/icons/typicons_dark/nw_search-preserve.svg
diff --git a/novelwriter/assets/icons/fallback/search_regex-dark.svg b/novelwriter/assets/icons/typicons_dark/nw_search-regex.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_regex-dark.svg
rename to novelwriter/assets/icons/typicons_dark/nw_search-regex.svg
diff --git a/novelwriter/assets/icons/fallback/search_word-dark.svg b/novelwriter/assets/icons/typicons_dark/nw_search-word.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_word-dark.svg
rename to novelwriter/assets/icons/typicons_dark/nw_search-word.svg
diff --git a/novelwriter/assets/icons/fallback/search_project-dark.svg b/novelwriter/assets/icons/typicons_dark/typ_arrow-down-thick-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_project-dark.svg
rename to novelwriter/assets/icons/typicons_dark/typ_arrow-down-thick-grey.svg
diff --git a/novelwriter/assets/icons/typicons_dark/arrow-maximise.svg b/novelwriter/assets/icons/typicons_dark/typ_arrow-maximise.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/arrow-maximise.svg
rename to novelwriter/assets/icons/typicons_dark/typ_arrow-maximise.svg
diff --git a/novelwriter/assets/icons/typicons_dark/arrow-minimise.svg b/novelwriter/assets/icons/typicons_dark/typ_arrow-minimise.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/arrow-minimise.svg
rename to novelwriter/assets/icons/typicons_dark/typ_arrow-minimise.svg
diff --git a/novelwriter/assets/icons/fallback/search_loop-dark.svg b/novelwriter/assets/icons/typicons_dark/typ_arrow-repeat-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_loop-dark.svg
rename to novelwriter/assets/icons/typicons_dark/typ_arrow-repeat-grey.svg
diff --git a/novelwriter/assets/icons/typicons_dark/at.svg b/novelwriter/assets/icons/typicons_dark/typ_at.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/at.svg
rename to novelwriter/assets/icons/typicons_dark/typ_at.svg
diff --git a/novelwriter/assets/icons/typicons_dark/backspace.svg b/novelwriter/assets/icons/typicons_dark/typ_backspace.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/backspace.svg
rename to novelwriter/assets/icons/typicons_dark/typ_backspace.svg
diff --git a/novelwriter/assets/icons/typicons_dark/book.svg b/novelwriter/assets/icons/typicons_dark/typ_book.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/book.svg
rename to novelwriter/assets/icons/typicons_dark/typ_book.svg
diff --git a/novelwriter/assets/icons/typicons_dark/bookmark.svg b/novelwriter/assets/icons/typicons_dark/typ_bookmark.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/bookmark.svg
rename to novelwriter/assets/icons/typicons_dark/typ_bookmark.svg
diff --git a/novelwriter/assets/icons/typicons_dark/calendar.svg b/novelwriter/assets/icons/typicons_dark/typ_calendar.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/calendar.svg
rename to novelwriter/assets/icons/typicons_dark/typ_calendar.svg
diff --git a/novelwriter/assets/icons/fallback/search_cancel-dark.svg b/novelwriter/assets/icons/typicons_dark/typ_cancel-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_cancel-dark.svg
rename to novelwriter/assets/icons/typicons_dark/typ_cancel-grey.svg
diff --git a/novelwriter/assets/icons/typicons_dark/cancel.svg b/novelwriter/assets/icons/typicons_dark/typ_cancel.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/cancel.svg
rename to novelwriter/assets/icons/typicons_dark/typ_cancel.svg
diff --git a/novelwriter/assets/icons/fallback/status_stats-dark.svg b/novelwriter/assets/icons/typicons_dark/typ_chart-bar-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/status_stats-dark.svg
rename to novelwriter/assets/icons/typicons_dark/typ_chart-bar-grey.svg
diff --git a/novelwriter/assets/icons/typicons_dark/chevron-left.svg b/novelwriter/assets/icons/typicons_dark/typ_chevron-left.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/chevron-left.svg
rename to novelwriter/assets/icons/typicons_dark/typ_chevron-left.svg
diff --git a/novelwriter/assets/icons/typicons_dark/chevron-right.svg b/novelwriter/assets/icons/typicons_dark/typ_chevron-right.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/chevron-right.svg
rename to novelwriter/assets/icons/typicons_dark/typ_chevron-right.svg
diff --git a/novelwriter/assets/icons/fallback/settings-dark.svg b/novelwriter/assets/icons/typicons_dark/typ_cog.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/settings-dark.svg
rename to novelwriter/assets/icons/typicons_dark/typ_cog.svg
diff --git a/novelwriter/assets/icons/typicons_dark/delete.svg b/novelwriter/assets/icons/typicons_dark/typ_delete.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/delete.svg
rename to novelwriter/assets/icons/typicons_dark/typ_delete.svg
diff --git a/novelwriter/assets/icons/typicons_dark/document-text.svg b/novelwriter/assets/icons/typicons_dark/typ_document-text.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/document-text.svg
rename to novelwriter/assets/icons/typicons_dark/typ_document-text.svg
diff --git a/novelwriter/assets/icons/typicons_dark/download.svg b/novelwriter/assets/icons/typicons_dark/typ_download.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/download.svg
rename to novelwriter/assets/icons/typicons_dark/typ_download.svg
diff --git a/novelwriter/assets/icons/typicons_dark/flag.svg b/novelwriter/assets/icons/typicons_dark/typ_flag.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/flag.svg
rename to novelwriter/assets/icons/typicons_dark/typ_flag.svg
diff --git a/novelwriter/assets/icons/typicons_dark/folder.svg b/novelwriter/assets/icons/typicons_dark/typ_folder.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/folder.svg
rename to novelwriter/assets/icons/typicons_dark/typ_folder.svg
diff --git a/novelwriter/assets/icons/fallback/status_lang-dark.svg b/novelwriter/assets/icons/typicons_dark/typ_globe-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/status_lang-dark.svg
rename to novelwriter/assets/icons/typicons_dark/typ_globe-grey.svg
diff --git a/novelwriter/assets/icons/typicons_dark/globe.svg b/novelwriter/assets/icons/typicons_dark/typ_globe.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/globe.svg
rename to novelwriter/assets/icons/typicons_dark/typ_globe.svg
diff --git a/novelwriter/assets/icons/typicons_dark/hash.svg b/novelwriter/assets/icons/typicons_dark/typ_hash.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/hash.svg
rename to novelwriter/assets/icons/typicons_dark/typ_hash.svg
diff --git a/novelwriter/assets/icons/typicons_dark/input-checked.svg b/novelwriter/assets/icons/typicons_dark/typ_input-checked.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/input-checked.svg
rename to novelwriter/assets/icons/typicons_dark/typ_input-checked.svg
diff --git a/novelwriter/assets/icons/typicons_dark/key.svg b/novelwriter/assets/icons/typicons_dark/typ_key.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/key.svg
rename to novelwriter/assets/icons/typicons_dark/typ_key.svg
diff --git a/novelwriter/assets/icons/typicons_dark/location.svg b/novelwriter/assets/icons/typicons_dark/typ_location.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/location.svg
rename to novelwriter/assets/icons/typicons_dark/typ_location.svg
diff --git a/novelwriter/assets/icons/fallback/status_idle-dark.svg b/novelwriter/assets/icons/typicons_dark/typ_media-pause-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/status_idle-dark.svg
rename to novelwriter/assets/icons/typicons_dark/typ_media-pause-grey.svg
diff --git a/novelwriter/assets/icons/typicons_dark/media-record-outline.svg b/novelwriter/assets/icons/typicons_dark/typ_media-record-outline.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/media-record-outline.svg
rename to novelwriter/assets/icons/typicons_dark/typ_media-record-outline.svg
diff --git a/novelwriter/assets/icons/typicons_dark/media-record.svg b/novelwriter/assets/icons/typicons_dark/typ_media-record.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/media-record.svg
rename to novelwriter/assets/icons/typicons_dark/typ_media-record.svg
diff --git a/novelwriter/assets/icons/typicons_dark/minus.svg b/novelwriter/assets/icons/typicons_dark/typ_minus.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/minus.svg
rename to novelwriter/assets/icons/typicons_dark/typ_minus.svg
diff --git a/novelwriter/assets/icons/typicons_dark/search-replace.svg b/novelwriter/assets/icons/typicons_dark/typ_mod_search-replace.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/search-replace.svg
rename to novelwriter/assets/icons/typicons_dark/typ_mod_search-replace.svg
diff --git a/novelwriter/assets/icons/typicons_dark/pencil.svg b/novelwriter/assets/icons/typicons_dark/typ_pencil.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/pencil.svg
rename to novelwriter/assets/icons/typicons_dark/typ_pencil.svg
diff --git a/novelwriter/assets/icons/typicons_dark/pin-outline.svg b/novelwriter/assets/icons/typicons_dark/typ_pin-outline.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/pin-outline.svg
rename to novelwriter/assets/icons/typicons_dark/typ_pin-outline.svg
diff --git a/novelwriter/assets/icons/typicons_dark/pin.svg b/novelwriter/assets/icons/typicons_dark/typ_pin.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/pin.svg
rename to novelwriter/assets/icons/typicons_dark/typ_pin.svg
diff --git a/novelwriter/assets/icons/typicons_dark/plus.svg b/novelwriter/assets/icons/typicons_dark/typ_plus.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/plus.svg
rename to novelwriter/assets/icons/typicons_dark/typ_plus.svg
diff --git a/novelwriter/assets/icons/typicons_dark/puzzle.svg b/novelwriter/assets/icons/typicons_dark/typ_puzzle.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/puzzle.svg
rename to novelwriter/assets/icons/typicons_dark/typ_puzzle.svg
diff --git a/novelwriter/assets/icons/typicons_dark/refresh.svg b/novelwriter/assets/icons/typicons_dark/typ_refresh.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/refresh.svg
rename to novelwriter/assets/icons/typicons_dark/typ_refresh.svg
diff --git a/novelwriter/assets/icons/typicons_dark/search.svg b/novelwriter/assets/icons/typicons_dark/typ_search.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/search.svg
rename to novelwriter/assets/icons/typicons_dark/typ_search.svg
diff --git a/novelwriter/assets/icons/typicons_dark/star.svg b/novelwriter/assets/icons/typicons_dark/typ_star.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/star.svg
rename to novelwriter/assets/icons/typicons_dark/typ_star.svg
diff --git a/novelwriter/assets/icons/fallback/status_time-dark.svg b/novelwriter/assets/icons/typicons_dark/typ_stopwatch-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/status_time-dark.svg
rename to novelwriter/assets/icons/typicons_dark/typ_stopwatch-grey.svg
diff --git a/novelwriter/assets/icons/typicons_dark/stopwatch.svg b/novelwriter/assets/icons/typicons_dark/typ_stopwatch.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/stopwatch.svg
rename to novelwriter/assets/icons/typicons_dark/typ_stopwatch.svg
diff --git a/novelwriter/assets/icons/fallback/status_lines-dark.svg b/novelwriter/assets/icons/typicons_dark/typ_th-list-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/status_lines-dark.svg
rename to novelwriter/assets/icons/typicons_dark/typ_th-list-grey.svg
diff --git a/novelwriter/assets/icons/typicons_dark/tick.svg b/novelwriter/assets/icons/typicons_dark/typ_tick.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/tick.svg
rename to novelwriter/assets/icons/typicons_dark/typ_tick.svg
diff --git a/novelwriter/assets/icons/typicons_dark/times.svg b/novelwriter/assets/icons/typicons_dark/typ_times.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/times.svg
rename to novelwriter/assets/icons/typicons_dark/typ_times.svg
diff --git a/novelwriter/assets/icons/typicons_dark/trash.svg b/novelwriter/assets/icons/typicons_dark/typ_trash.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/trash.svg
rename to novelwriter/assets/icons/typicons_dark/typ_trash.svg
diff --git a/novelwriter/assets/icons/typicons_dark/user.svg b/novelwriter/assets/icons/typicons_dark/typ_user.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_dark/user.svg
rename to novelwriter/assets/icons/typicons_dark/typ_user.svg
diff --git a/novelwriter/assets/icons/typicons_light/README.md b/novelwriter/assets/icons/typicons_light/README.md
index cfa5ea08..eb6f78a5 100644
--- a/novelwriter/assets/icons/typicons_light/README.md
+++ b/novelwriter/assets/icons/typicons_light/README.md
@@ -1,5 +1,29 @@
-## Typicons
+# Typicons for Light Backgrounds
+
+This theme is based on Typicons. All files are prefixed, depending on whether they are based
+directly on original Typicons, redesigned, or designed from scratch.
+
+## Typicons Icons
+
+The files have a `typ_` prefix. These are colourised and rescaled Typicons, but with no other
+modifications.
Copyright: Stephen Hutchings
Source: https://github.com/stephenhutchings/typicons.font
License: [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
+
+## Original Icons
+
+The files have a `nw_` prefix. These are made completely from scratch for novelWriter and are not
+using any design elements from Typicons.
+
+Copyright: Veronica Berglyd Olsen
+License: [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
+
+## Mixed Icons
+
+The files have a `mixed_` prefix. These are redesigned from Typicons components, modified from
+Typicons, or consist of a mix of Typicons and new elements.
+
+Copyright: Stephen Hutchings, Veronica Berglyd Olsen
+License: [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf
index 64d6ec29..56e18b2a 100644
--- a/novelwriter/assets/icons/typicons_light/icons.conf
+++ b/novelwriter/assets/icons/typicons_light/icons.conf
@@ -7,57 +7,70 @@
##
[Main]
-name = Typicons Light
-description = Colourised icons for light GUI theme based on Typicons.
-author = Veronica Berglyd Olsen (adaptation)
-credit = Stephen Hutchings (icon design)
-url = https://github.com/stephenhutchings/typicons.font
-license = CC BY-SA 4.0
-licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
+name = Typicons Light
+description = Colourised icons for light GUI theme based on Typicons.
+author = Veronica Berglyd Olsen (adaptation)
+credit = Stephen Hutchings (icon design)
+url = https://github.com/stephenhutchings/typicons.font
+license = CC BY-SA 4.0
+licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
[Map]
-cls_none = cancel.svg
-cls_novel = book.svg
-cls_plot = puzzle.svg
-cls_character = user.svg
-cls_world = location.svg
-cls_timeline = calendar.svg
-cls_object = key.svg
-cls_entity = flag.svg
-cls_custom = star.svg
-cls_archive = delete.svg
-cls_trash = trash.svg
-proj_document = document-text.svg
-proj_title = document-title.svg
-proj_chapter = document-chapter.svg
-proj_scene = document-scene.svg
-proj_note = document-note.svg
-proj_folder = folder.svg
-doc_h0 = heading0.svg
-doc_h1 = heading1.svg
-doc_h2 = heading2.svg
-doc_h3 = heading3.svg
-doc_h4 = heading4.svg
-delete = delete.svg
-add = plus.svg
-remove = minus.svg
-close = times.svg
-refresh = refresh.svg
-maximise = arrow-maximise.svg
-minimise = arrow-minimise.svg
-done = input-checked.svg
-search = search.svg
-search-replace = search-replace.svg
-clear = backspace.svg
-save = download.svg
-edit = pencil.svg
-check = tick.svg
-cross = times.svg
-hash = hash.svg
-reference = at.svg
-backward = chevron-left.svg
-forward = chevron-right.svg
-sticky-on = pin.svg
-sticky-off = pin-outline.svg
-bullet-on = media-record.svg
-bullet-off = media-record-outline.svg
+add = typ_plus.svg
+backward = typ_chevron-left.svg
+bullet-off = typ_media-record-outline.svg
+bullet-on = typ_media-record.svg
+check = typ_tick.svg
+clear = typ_backspace.svg
+close = typ_times.svg
+cls_archive = typ_delete.svg
+cls_character = typ_user.svg
+cls_custom = typ_star.svg
+cls_entity = typ_flag.svg
+cls_none = typ_cancel.svg
+cls_novel = typ_book.svg
+cls_object = typ_key.svg
+cls_plot = typ_puzzle.svg
+cls_timeline = typ_calendar.svg
+cls_trash = typ_trash.svg
+cls_world = typ_location.svg
+cross = typ_times.svg
+delete = typ_delete.svg
+doc_h0 = mixed_heading0.svg
+doc_h1 = mixed_heading1.svg
+doc_h2 = mixed_heading2.svg
+doc_h3 = mixed_heading3.svg
+doc_h4 = mixed_heading4.svg
+done = typ_input-checked.svg
+edit = typ_pencil.svg
+forward = typ_chevron-right.svg
+hash = typ_hash.svg
+maximise = typ_arrow-maximise.svg
+minimise = typ_arrow-minimise.svg
+proj_chapter = mixed_document-chapter.svg
+proj_document = typ_document-text.svg
+proj_folder = typ_folder.svg
+proj_note = mixed_document-note.svg
+proj_scene = mixed_document-scene.svg
+proj_title = mixed_document-title.svg
+reference = typ_at.svg
+refresh = typ_refresh.svg
+remove = typ_minus.svg
+save = typ_download.svg
+search = typ_search.svg
+search_cancel = typ_cancel-grey.svg
+search_case = nw_search-case.svg
+search_loop = typ_arrow-repeat-grey.svg
+search_preserve = nw_search-preserve.svg
+search_project = typ_arrow-down-thick-grey.svg
+search_regex = nw_search-regex.svg
+search_replace = mixed_search-replace.svg
+search_word = nw_search-word.svg
+settings = typ_cog.svg
+status_idle = typ_media-pause-grey.svg
+status_lang = typ_globe-grey.svg
+status_lines = typ_th-list-grey.svg
+status_stats = typ_chart-bar-grey.svg
+status_time = typ_stopwatch-grey.svg
+sticky-off = typ_pin-outline.svg
+sticky-on = typ_pin.svg
diff --git a/novelwriter/assets/icons/typicons_light/document-chapter.svg b/novelwriter/assets/icons/typicons_light/mixed_document-chapter.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/document-chapter.svg
rename to novelwriter/assets/icons/typicons_light/mixed_document-chapter.svg
diff --git a/novelwriter/assets/icons/typicons_light/document-note.svg b/novelwriter/assets/icons/typicons_light/mixed_document-note.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/document-note.svg
rename to novelwriter/assets/icons/typicons_light/mixed_document-note.svg
diff --git a/novelwriter/assets/icons/typicons_light/document-scene.svg b/novelwriter/assets/icons/typicons_light/mixed_document-scene.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/document-scene.svg
rename to novelwriter/assets/icons/typicons_light/mixed_document-scene.svg
diff --git a/novelwriter/assets/icons/typicons_light/document-title.svg b/novelwriter/assets/icons/typicons_light/mixed_document-title.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/document-title.svg
rename to novelwriter/assets/icons/typicons_light/mixed_document-title.svg
diff --git a/novelwriter/assets/icons/typicons_light/heading0.svg b/novelwriter/assets/icons/typicons_light/mixed_heading0.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/heading0.svg
rename to novelwriter/assets/icons/typicons_light/mixed_heading0.svg
diff --git a/novelwriter/assets/icons/typicons_light/heading1.svg b/novelwriter/assets/icons/typicons_light/mixed_heading1.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/heading1.svg
rename to novelwriter/assets/icons/typicons_light/mixed_heading1.svg
diff --git a/novelwriter/assets/icons/typicons_light/heading2.svg b/novelwriter/assets/icons/typicons_light/mixed_heading2.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/heading2.svg
rename to novelwriter/assets/icons/typicons_light/mixed_heading2.svg
diff --git a/novelwriter/assets/icons/typicons_light/heading3.svg b/novelwriter/assets/icons/typicons_light/mixed_heading3.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/heading3.svg
rename to novelwriter/assets/icons/typicons_light/mixed_heading3.svg
diff --git a/novelwriter/assets/icons/typicons_light/heading4.svg b/novelwriter/assets/icons/typicons_light/mixed_heading4.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/heading4.svg
rename to novelwriter/assets/icons/typicons_light/mixed_heading4.svg
diff --git a/novelwriter/assets/icons/typicons_light/search-replace.svg b/novelwriter/assets/icons/typicons_light/mixed_search-replace.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/search-replace.svg
rename to novelwriter/assets/icons/typicons_light/mixed_search-replace.svg
diff --git a/novelwriter/assets/icons/fallback/search_case.svg b/novelwriter/assets/icons/typicons_light/nw_search-case.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_case.svg
rename to novelwriter/assets/icons/typicons_light/nw_search-case.svg
diff --git a/novelwriter/assets/icons/fallback/search_preserve.svg b/novelwriter/assets/icons/typicons_light/nw_search-preserve.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_preserve.svg
rename to novelwriter/assets/icons/typicons_light/nw_search-preserve.svg
diff --git a/novelwriter/assets/icons/fallback/search_regex.svg b/novelwriter/assets/icons/typicons_light/nw_search-regex.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_regex.svg
rename to novelwriter/assets/icons/typicons_light/nw_search-regex.svg
diff --git a/novelwriter/assets/icons/fallback/search_word.svg b/novelwriter/assets/icons/typicons_light/nw_search-word.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_word.svg
rename to novelwriter/assets/icons/typicons_light/nw_search-word.svg
diff --git a/novelwriter/assets/icons/fallback/search_project.svg b/novelwriter/assets/icons/typicons_light/typ_arrow-down-thick-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_project.svg
rename to novelwriter/assets/icons/typicons_light/typ_arrow-down-thick-grey.svg
diff --git a/novelwriter/assets/icons/typicons_light/arrow-maximise.svg b/novelwriter/assets/icons/typicons_light/typ_arrow-maximise.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/arrow-maximise.svg
rename to novelwriter/assets/icons/typicons_light/typ_arrow-maximise.svg
diff --git a/novelwriter/assets/icons/typicons_light/arrow-minimise.svg b/novelwriter/assets/icons/typicons_light/typ_arrow-minimise.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/arrow-minimise.svg
rename to novelwriter/assets/icons/typicons_light/typ_arrow-minimise.svg
diff --git a/novelwriter/assets/icons/fallback/search_loop.svg b/novelwriter/assets/icons/typicons_light/typ_arrow-repeat-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_loop.svg
rename to novelwriter/assets/icons/typicons_light/typ_arrow-repeat-grey.svg
diff --git a/novelwriter/assets/icons/typicons_light/at.svg b/novelwriter/assets/icons/typicons_light/typ_at.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/at.svg
rename to novelwriter/assets/icons/typicons_light/typ_at.svg
diff --git a/novelwriter/assets/icons/typicons_light/backspace.svg b/novelwriter/assets/icons/typicons_light/typ_backspace.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/backspace.svg
rename to novelwriter/assets/icons/typicons_light/typ_backspace.svg
diff --git a/novelwriter/assets/icons/typicons_light/book.svg b/novelwriter/assets/icons/typicons_light/typ_book.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/book.svg
rename to novelwriter/assets/icons/typicons_light/typ_book.svg
diff --git a/novelwriter/assets/icons/typicons_light/bookmark.svg b/novelwriter/assets/icons/typicons_light/typ_bookmark.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/bookmark.svg
rename to novelwriter/assets/icons/typicons_light/typ_bookmark.svg
diff --git a/novelwriter/assets/icons/typicons_light/calendar.svg b/novelwriter/assets/icons/typicons_light/typ_calendar.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/calendar.svg
rename to novelwriter/assets/icons/typicons_light/typ_calendar.svg
diff --git a/novelwriter/assets/icons/fallback/search_cancel.svg b/novelwriter/assets/icons/typicons_light/typ_cancel-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/search_cancel.svg
rename to novelwriter/assets/icons/typicons_light/typ_cancel-grey.svg
diff --git a/novelwriter/assets/icons/typicons_light/cancel.svg b/novelwriter/assets/icons/typicons_light/typ_cancel.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/cancel.svg
rename to novelwriter/assets/icons/typicons_light/typ_cancel.svg
diff --git a/novelwriter/assets/icons/fallback/status_stats.svg b/novelwriter/assets/icons/typicons_light/typ_chart-bar-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/status_stats.svg
rename to novelwriter/assets/icons/typicons_light/typ_chart-bar-grey.svg
diff --git a/novelwriter/assets/icons/typicons_light/chevron-left.svg b/novelwriter/assets/icons/typicons_light/typ_chevron-left.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/chevron-left.svg
rename to novelwriter/assets/icons/typicons_light/typ_chevron-left.svg
diff --git a/novelwriter/assets/icons/typicons_light/chevron-right.svg b/novelwriter/assets/icons/typicons_light/typ_chevron-right.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/chevron-right.svg
rename to novelwriter/assets/icons/typicons_light/typ_chevron-right.svg
diff --git a/novelwriter/assets/icons/fallback/settings.svg b/novelwriter/assets/icons/typicons_light/typ_cog.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/settings.svg
rename to novelwriter/assets/icons/typicons_light/typ_cog.svg
diff --git a/novelwriter/assets/icons/typicons_light/delete.svg b/novelwriter/assets/icons/typicons_light/typ_delete.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/delete.svg
rename to novelwriter/assets/icons/typicons_light/typ_delete.svg
diff --git a/novelwriter/assets/icons/typicons_light/document-text.svg b/novelwriter/assets/icons/typicons_light/typ_document-text.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/document-text.svg
rename to novelwriter/assets/icons/typicons_light/typ_document-text.svg
diff --git a/novelwriter/assets/icons/typicons_light/download.svg b/novelwriter/assets/icons/typicons_light/typ_download.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/download.svg
rename to novelwriter/assets/icons/typicons_light/typ_download.svg
diff --git a/novelwriter/assets/icons/typicons_light/flag.svg b/novelwriter/assets/icons/typicons_light/typ_flag.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/flag.svg
rename to novelwriter/assets/icons/typicons_light/typ_flag.svg
diff --git a/novelwriter/assets/icons/typicons_light/folder.svg b/novelwriter/assets/icons/typicons_light/typ_folder.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/folder.svg
rename to novelwriter/assets/icons/typicons_light/typ_folder.svg
diff --git a/novelwriter/assets/icons/fallback/status_lang.svg b/novelwriter/assets/icons/typicons_light/typ_globe-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/status_lang.svg
rename to novelwriter/assets/icons/typicons_light/typ_globe-grey.svg
diff --git a/novelwriter/assets/icons/typicons_light/globe.svg b/novelwriter/assets/icons/typicons_light/typ_globe.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/globe.svg
rename to novelwriter/assets/icons/typicons_light/typ_globe.svg
diff --git a/novelwriter/assets/icons/typicons_light/hash.svg b/novelwriter/assets/icons/typicons_light/typ_hash.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/hash.svg
rename to novelwriter/assets/icons/typicons_light/typ_hash.svg
diff --git a/novelwriter/assets/icons/typicons_light/input-checked.svg b/novelwriter/assets/icons/typicons_light/typ_input-checked.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/input-checked.svg
rename to novelwriter/assets/icons/typicons_light/typ_input-checked.svg
diff --git a/novelwriter/assets/icons/typicons_light/key.svg b/novelwriter/assets/icons/typicons_light/typ_key.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/key.svg
rename to novelwriter/assets/icons/typicons_light/typ_key.svg
diff --git a/novelwriter/assets/icons/typicons_light/location.svg b/novelwriter/assets/icons/typicons_light/typ_location.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/location.svg
rename to novelwriter/assets/icons/typicons_light/typ_location.svg
diff --git a/novelwriter/assets/icons/fallback/status_idle.svg b/novelwriter/assets/icons/typicons_light/typ_media-pause-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/status_idle.svg
rename to novelwriter/assets/icons/typicons_light/typ_media-pause-grey.svg
diff --git a/novelwriter/assets/icons/typicons_light/media-record-outline.svg b/novelwriter/assets/icons/typicons_light/typ_media-record-outline.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/media-record-outline.svg
rename to novelwriter/assets/icons/typicons_light/typ_media-record-outline.svg
diff --git a/novelwriter/assets/icons/typicons_light/media-record.svg b/novelwriter/assets/icons/typicons_light/typ_media-record.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/media-record.svg
rename to novelwriter/assets/icons/typicons_light/typ_media-record.svg
diff --git a/novelwriter/assets/icons/typicons_light/minus.svg b/novelwriter/assets/icons/typicons_light/typ_minus.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/minus.svg
rename to novelwriter/assets/icons/typicons_light/typ_minus.svg
diff --git a/novelwriter/assets/icons/typicons_light/pencil.svg b/novelwriter/assets/icons/typicons_light/typ_pencil.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/pencil.svg
rename to novelwriter/assets/icons/typicons_light/typ_pencil.svg
diff --git a/novelwriter/assets/icons/typicons_light/pin-outline.svg b/novelwriter/assets/icons/typicons_light/typ_pin-outline.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/pin-outline.svg
rename to novelwriter/assets/icons/typicons_light/typ_pin-outline.svg
diff --git a/novelwriter/assets/icons/typicons_light/pin.svg b/novelwriter/assets/icons/typicons_light/typ_pin.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/pin.svg
rename to novelwriter/assets/icons/typicons_light/typ_pin.svg
diff --git a/novelwriter/assets/icons/typicons_light/plus.svg b/novelwriter/assets/icons/typicons_light/typ_plus.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/plus.svg
rename to novelwriter/assets/icons/typicons_light/typ_plus.svg
diff --git a/novelwriter/assets/icons/typicons_light/puzzle.svg b/novelwriter/assets/icons/typicons_light/typ_puzzle.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/puzzle.svg
rename to novelwriter/assets/icons/typicons_light/typ_puzzle.svg
diff --git a/novelwriter/assets/icons/typicons_light/refresh.svg b/novelwriter/assets/icons/typicons_light/typ_refresh.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/refresh.svg
rename to novelwriter/assets/icons/typicons_light/typ_refresh.svg
diff --git a/novelwriter/assets/icons/typicons_light/search.svg b/novelwriter/assets/icons/typicons_light/typ_search.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/search.svg
rename to novelwriter/assets/icons/typicons_light/typ_search.svg
diff --git a/novelwriter/assets/icons/typicons_light/star.svg b/novelwriter/assets/icons/typicons_light/typ_star.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/star.svg
rename to novelwriter/assets/icons/typicons_light/typ_star.svg
diff --git a/novelwriter/assets/icons/fallback/status_time.svg b/novelwriter/assets/icons/typicons_light/typ_stopwatch-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/status_time.svg
rename to novelwriter/assets/icons/typicons_light/typ_stopwatch-grey.svg
diff --git a/novelwriter/assets/icons/typicons_light/stopwatch.svg b/novelwriter/assets/icons/typicons_light/typ_stopwatch.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/stopwatch.svg
rename to novelwriter/assets/icons/typicons_light/typ_stopwatch.svg
diff --git a/novelwriter/assets/icons/fallback/status_lines.svg b/novelwriter/assets/icons/typicons_light/typ_th-list-grey.svg
similarity index 100%
rename from novelwriter/assets/icons/fallback/status_lines.svg
rename to novelwriter/assets/icons/typicons_light/typ_th-list-grey.svg
diff --git a/novelwriter/assets/icons/typicons_light/tick.svg b/novelwriter/assets/icons/typicons_light/typ_tick.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/tick.svg
rename to novelwriter/assets/icons/typicons_light/typ_tick.svg
diff --git a/novelwriter/assets/icons/typicons_light/times.svg b/novelwriter/assets/icons/typicons_light/typ_times.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/times.svg
rename to novelwriter/assets/icons/typicons_light/typ_times.svg
diff --git a/novelwriter/assets/icons/typicons_light/trash.svg b/novelwriter/assets/icons/typicons_light/typ_trash.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/trash.svg
rename to novelwriter/assets/icons/typicons_light/typ_trash.svg
diff --git a/novelwriter/assets/icons/typicons_light/user.svg b/novelwriter/assets/icons/typicons_light/typ_user.svg
similarity index 100%
rename from novelwriter/assets/icons/typicons_light/user.svg
rename to novelwriter/assets/icons/typicons_light/typ_user.svg
diff --git a/novelwriter/assets/themes/syntax/default_dark.conf b/novelwriter/assets/syntax/default_dark.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/default_dark.conf
rename to novelwriter/assets/syntax/default_dark.conf
diff --git a/novelwriter/assets/themes/syntax/default_light.conf b/novelwriter/assets/syntax/default_light.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/default_light.conf
rename to novelwriter/assets/syntax/default_light.conf
diff --git a/novelwriter/assets/themes/syntax/grey_dark.conf b/novelwriter/assets/syntax/grey_dark.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/grey_dark.conf
rename to novelwriter/assets/syntax/grey_dark.conf
diff --git a/novelwriter/assets/themes/syntax/grey_light.conf b/novelwriter/assets/syntax/grey_light.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/grey_light.conf
rename to novelwriter/assets/syntax/grey_light.conf
diff --git a/novelwriter/assets/themes/syntax/light_owl.conf b/novelwriter/assets/syntax/light_owl.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/light_owl.conf
rename to novelwriter/assets/syntax/light_owl.conf
diff --git a/novelwriter/assets/themes/syntax/night_owl.conf b/novelwriter/assets/syntax/night_owl.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/night_owl.conf
rename to novelwriter/assets/syntax/night_owl.conf
diff --git a/novelwriter/assets/themes/syntax/solarized_dark.conf b/novelwriter/assets/syntax/solarized_dark.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/solarized_dark.conf
rename to novelwriter/assets/syntax/solarized_dark.conf
diff --git a/novelwriter/assets/themes/syntax/solarized_light.conf b/novelwriter/assets/syntax/solarized_light.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/solarized_light.conf
rename to novelwriter/assets/syntax/solarized_light.conf
diff --git a/novelwriter/assets/themes/syntax/tomorrow.conf b/novelwriter/assets/syntax/tomorrow.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/tomorrow.conf
rename to novelwriter/assets/syntax/tomorrow.conf
diff --git a/novelwriter/assets/themes/syntax/tomorrow_night.conf b/novelwriter/assets/syntax/tomorrow_night.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/tomorrow_night.conf
rename to novelwriter/assets/syntax/tomorrow_night.conf
diff --git a/novelwriter/assets/themes/syntax/tomorrow_night_blue.conf b/novelwriter/assets/syntax/tomorrow_night_blue.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/tomorrow_night_blue.conf
rename to novelwriter/assets/syntax/tomorrow_night_blue.conf
diff --git a/novelwriter/assets/themes/syntax/tomorrow_night_bright.conf b/novelwriter/assets/syntax/tomorrow_night_bright.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/tomorrow_night_bright.conf
rename to novelwriter/assets/syntax/tomorrow_night_bright.conf
diff --git a/novelwriter/assets/themes/syntax/tomorrow_night_eighties.conf b/novelwriter/assets/syntax/tomorrow_night_eighties.conf
similarity index 100%
rename from novelwriter/assets/themes/syntax/tomorrow_night_eighties.conf
rename to novelwriter/assets/syntax/tomorrow_night_eighties.conf
diff --git a/novelwriter/assets/themes/gui/default/theme.conf b/novelwriter/assets/themes/default.conf
similarity index 100%
rename from novelwriter/assets/themes/gui/default/theme.conf
rename to novelwriter/assets/themes/default.conf
diff --git a/novelwriter/assets/themes/gui/default_dark/theme.conf b/novelwriter/assets/themes/default_dark.conf
similarity index 100%
rename from novelwriter/assets/themes/gui/default_dark/theme.conf
rename to novelwriter/assets/themes/default_dark.conf
diff --git a/novelwriter/assets/themes/gui/default_dark/styles.qss b/novelwriter/assets/themes/default_dark.qss
similarity index 100%
rename from novelwriter/assets/themes/gui/default_dark/styles.qss
rename to novelwriter/assets/themes/default_dark.qss
diff --git a/novelwriter/assets/themes/gui/solarized_dark/theme.conf b/novelwriter/assets/themes/solarized_dark.conf
similarity index 100%
rename from novelwriter/assets/themes/gui/solarized_dark/theme.conf
rename to novelwriter/assets/themes/solarized_dark.conf
diff --git a/novelwriter/assets/themes/gui/solarized_light/theme.conf b/novelwriter/assets/themes/solarized_light.conf
similarity index 100%
rename from novelwriter/assets/themes/gui/solarized_light/theme.conf
rename to novelwriter/assets/themes/solarized_light.conf
diff --git a/novelwriter/common.py b/novelwriter/common.py
index d4eb5a83..7e699e97 100644
--- a/novelwriter/common.py
+++ b/novelwriter/common.py
@@ -23,6 +23,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import os
import json
import hashlib
import logging
@@ -415,6 +416,24 @@ def jsonEncode(data, n=0, nmax=0):
# File and File System Functions
# =============================================================================================== #
+def readTextFile(filePath):
+ """Read the content of a text file in a robust manner.
+ """
+ if not os.path.isfile(filePath):
+ return ""
+
+ fileText = ""
+ try:
+ with open(filePath, mode="r", encoding="utf-8") as inFile:
+ fileText = inFile.read()
+ except Exception:
+ logger.error("Could not read file: %s", filePath)
+ logException()
+ return ""
+
+ return fileText
+
+
def makeFileNameSafe(theText):
"""Returns a filename safe version of the text.
"""
diff --git a/novelwriter/config.py b/novelwriter/config.py
index 16f8e27d..8a7231f8 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -68,24 +68,24 @@ class Config:
self.appRoot = None # The full path to the novelwriter root folder
self.appIcon = None # The full path to the novelwriter icon file
self.assetPath = None # The full path to the novelwriter/assets folder
- self.themeRoot = None # The full path to the novelwriter/assets/themes folder
- self.dictPath = None # The full path to the novelwriter/assets/dict folder
- self.iconPath = None # The full path to the novelwriter/assets/icons folder
self.pdfDocs = None # The location of the PDF manual, if it exists
# Runtime Settings and Variables
self.confChanged = False # True whenever the config has chenged, false after save
# General
- self.guiTheme = "default"
- self.guiSyntax = "default_light"
- self.guiIcons = "typicons_light"
- self.guiDark = False # Load icons for dark backgrounds, if available
+ self.guiTheme = "" # GUI theme
+ self.guiSyntax = "" # Syntax theme
+ self.guiIcons = "" # Icon theme
self.guiFont = "" # Defaults to system default font
self.guiFontSize = 11 # Is overridden if system default is loaded
self.guiScale = 1.0 # Set automatically by Theme class
self.lastNotes = "0x0" # The latest release notes that have been shown
+ self.setDefaultGuiTheme()
+ self.setDefaultSyntaxTheme()
+ self.setDefaultIconTheme()
+
# Localisation
self.qLocal = QLocale.system()
self.guiLang = self.qLocal.name()
@@ -293,10 +293,7 @@ class Config:
# Assets
self.assetPath = os.path.join(self.appPath, "assets")
- self.themeRoot = os.path.join(self.assetPath, "themes")
- self.dictPath = os.path.join(self.assetPath, "dict")
- self.iconPath = os.path.join(self.assetPath, "icons")
- self.appIcon = os.path.join(self.iconPath, "novelwriter.svg")
+ self.appIcon = os.path.join(self.assetPath, "icons", "novelwriter.svg")
# Internationalisation
self.nwLangPath = os.path.join(self.assetPath, "i18n")
@@ -441,7 +438,6 @@ class Config:
self.guiTheme = theConf.rdStr(cnfSec, "theme", self.guiTheme)
self.guiSyntax = theConf.rdStr(cnfSec, "syntax", self.guiSyntax)
self.guiIcons = theConf.rdStr(cnfSec, "icons", self.guiIcons)
- self.guiDark = theConf.rdBool(cnfSec, "guidark", self.guiDark)
self.guiFont = theConf.rdStr(cnfSec, "guifont", self.guiFont)
self.guiFontSize = theConf.rdInt(cnfSec, "guifontsize", self.guiFontSize)
self.lastNotes = theConf.rdStr(cnfSec, "lastnotes", self.lastNotes)
@@ -564,7 +560,6 @@ class Config:
"theme": str(self.guiTheme),
"syntax": str(self.guiSyntax),
"icons": str(self.guiIcons),
- "guidark": str(self.guiDark),
"guifont": str(self.guiFont),
"guifontsize": str(self.guiFontSize),
"lastnotes": str(self.lastNotes),
@@ -852,6 +847,19 @@ class Config:
self.confChanged = True
return self.viewSynopsis
+ ##
+ # Default Setters
+ ##
+
+ def setDefaultGuiTheme(self):
+ self.guiTheme = "default"
+
+ def setDefaultSyntaxTheme(self):
+ self.guiSyntax = "default_light"
+
+ def setDefaultIconTheme(self):
+ self.guiIcons = "typicons_light"
+
##
# Getters
##
diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py
index 498a25df..2aefc3f6 100644
--- a/novelwriter/dialogs/about.py
+++ b/novelwriter/dialogs/about.py
@@ -36,6 +36,8 @@ from PyQt5.QtWidgets import (
QTextBrowser, QLabel
)
+from novelwriter.common import readTextFile
+
logger = logging.getLogger(__name__)
@@ -230,10 +232,9 @@ class GuiAbout(QDialog):
"""Load the content for the Release Notes page.
"""
docPath = os.path.join(self.mainConf.assetPath, "text", "release_notes.htm")
- if os.path.isfile(docPath):
- with open(docPath, mode="r", encoding="utf-8") as inFile:
- helpText = inFile.read()
- self.pageNotes.setHtml(helpText)
+ docText = readTextFile(docPath)
+ if docText:
+ self.pageNotes.setHtml(docText)
else:
self.pageNotes.setHtml("Error loading release notes text ...")
return
@@ -242,10 +243,9 @@ class GuiAbout(QDialog):
"""Load the content for the Licence page.
"""
docPath = os.path.join(self.mainConf.assetPath, "text", "gplv3_en.htm")
- if os.path.isfile(docPath):
- with open(docPath, mode="r", encoding="utf-8") as inFile:
- helpText = inFile.read()
- self.pageLicense.setHtml(helpText)
+ docText = readTextFile(docPath)
+ if docText:
+ self.pageLicense.setHtml(docText)
else:
self.pageLicense.setHtml("Error loading licence text ...")
return
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index cd757f95..98f20993 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -203,15 +203,6 @@ class GuiPreferencesGeneral(QWidget):
self.tr("Changing this requires restarting novelWriter.")
)
- # Dark Icons
- self.guiDark = QSwitch()
- self.guiDark.setChecked(self.mainConf.guiDark)
- self.mainForm.addRow(
- self.tr("Prefer icons for dark backgrounds"),
- self.guiDark,
- self.tr("May improve the look of icons on dark themes.")
- )
-
# Font Family
self.guiFont = QLineEdit()
self.guiFont.setReadOnly(True)
@@ -284,7 +275,6 @@ class GuiPreferencesGeneral(QWidget):
guiLang = self.guiLang.currentData()
guiTheme = self.guiTheme.currentData()
guiIcons = self.guiIcons.currentData()
- guiDark = self.guiDark.isChecked()
guiFont = self.guiFont.text()
guiFontSize = self.guiFontSize.value()
emphLabels = self.emphLabels.isChecked()
@@ -294,7 +284,6 @@ class GuiPreferencesGeneral(QWidget):
needsRestart |= self.mainConf.guiLang != guiLang
needsRestart |= self.mainConf.guiTheme != guiTheme
needsRestart |= self.mainConf.guiIcons != guiIcons
- needsRestart |= self.mainConf.guiDark != guiDark
needsRestart |= self.mainConf.guiFont != guiFont
needsRestart |= self.mainConf.guiFontSize != guiFontSize
@@ -305,7 +294,6 @@ class GuiPreferencesGeneral(QWidget):
self.mainConf.guiLang = guiLang
self.mainConf.guiTheme = guiTheme
self.mainConf.guiIcons = guiIcons
- self.mainConf.guiDark = guiDark
self.mainConf.guiFont = guiFont
self.mainConf.guiFontSize = guiFontSize
self.mainConf.emphLabels = emphLabels
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index 941e83ee..b23a8aaf 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -2244,7 +2244,7 @@ class GuiDocEditSearch(QFrame):
self.searchButton.setToolTip(self.tr("Find in current document"))
self.searchButton.clicked.connect(self._doSearch)
- self.replaceButton = QPushButton(self.theTheme.getIcon("search-replace"), "")
+ self.replaceButton = QPushButton(self.theTheme.getIcon("search_replace"), "")
self.replaceButton.setFixedSize(QSize(bPx, bPx))
self.replaceButton.setToolTip(self.tr("Find and replace in current document"))
self.replaceButton.clicked.connect(self._doReplace)
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index fa2237a5..be1f6754 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -29,16 +29,15 @@ import logging
import novelwriter
from math import ceil
-from functools import partial
-from PyQt5.QtCore import QCoreApplication, Qt
-from PyQt5.QtWidgets import QStyle, qApp
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import qApp
from PyQt5.QtGui import (
QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase, QPixmap
)
-from novelwriter.enum import nwAlert, nwItemLayout, nwItemType
-from novelwriter.common import NWConfigParser
+from novelwriter.enum import nwItemLayout, nwItemType
+from novelwriter.common import NWConfigParser, readTextFile
from novelwriter.constants import nwLabels
logger = logging.getLogger(__name__)
@@ -51,19 +50,10 @@ logger = logging.getLogger(__name__)
class GuiTheme:
- def __init__(self, theParent):
+ def __init__(self):
- self.mainConf = novelwriter.CONFIG
- self.theParent = theParent
- self.theIcons = GuiIcons(self.theParent)
- self.guiPalette = QPalette()
- self.guiPath = "gui"
- self.fontPath = "fonts"
- self.syntaxPath = "syntax"
- self.cssName = "style.qss"
- self.confName = "theme.conf"
- self.themeList = []
- self.syntaxList = []
+ self.mainConf = novelwriter.CONFIG
+ self.theIcons = GuiIcons(self)
# Loaded Theme Settings
# =====================
@@ -84,6 +74,7 @@ class GuiTheme:
self.helpText = [0, 0, 0]
# Loaded Syntax Settings
+ # ======================
# Main
self.syntaxName = ""
@@ -115,14 +106,24 @@ class GuiTheme:
# Changeable Settings
self.guiTheme = None
self.guiSyntax = None
- self.themeRoot = None
- self.themePath = None
self.syntaxFile = None
- self.confFile = None
self.cssFile = None
self.guiFontDB = QFontDatabase()
- self.loadFonts()
+ # Class Setup
+ # ===========
+
+ self._guiPalette = QPalette()
+ self._themeList = []
+ self._syntaxList = []
+ self._availThemes = {}
+ self._availSyntax = {}
+
+ self._listConf(self._availSyntax, os.path.join(self.mainConf.dataPath, "syntax"))
+ self._listConf(self._availSyntax, os.path.join(self.mainConf.assetPath, "syntax"))
+ self._listConf(self._availThemes, os.path.join(self.mainConf.dataPath, "themes"))
+ self._listConf(self._availThemes, os.path.join(self.mainConf.assetPath, "themes"))
+
self.updateFont()
self.updateTheme()
self.theIcons.updateTheme()
@@ -162,9 +163,6 @@ class GuiTheme:
logger.verbose("Text 'N' Height: %d", self.textNHeight)
logger.verbose("Text 'N' Width: %d", self.textNWidth)
- # Internal Mapping
- self.tr = partial(QCoreApplication.translate, "GuiTheme")
-
return
##
@@ -184,43 +182,14 @@ class GuiTheme:
# Actions
##
- def loadFonts(self):
- """Add the fonts in the assets fonts folder to the app.
- """
- logger.debug("Loading additional fonts")
-
- ttfList = []
- fontAssets = os.path.join(self.mainConf.assetPath, self.fontPath)
- for fontFam in os.listdir(fontAssets):
- fontDir = os.path.join(fontAssets, fontFam)
- if os.path.isdir(fontDir):
- logger.verbose("Found font: %s", fontFam)
- if fontFam not in self.guiFontDB.families():
- for fontFile in os.listdir(fontDir):
- ttfFile = os.path.join(fontDir, fontFile)
- if os.path.isfile(ttfFile) and fontFile.endswith(".ttf"):
- ttfList.append(ttfFile)
-
- for ttfFile in ttfList:
- relPath = os.path.relpath(ttfFile, fontAssets)
- logger.verbose("Adding font: %s", relPath)
- fontID = self.guiFontDB.addApplicationFont(ttfFile)
- if fontID < 0:
- logger.error("Failed to add font: %s", relPath)
-
- return
-
def updateFont(self):
"""Update the GUI's font style from settings.
"""
theFont = QFont()
if self.mainConf.guiFont not in self.guiFontDB.families():
- if self.mainConf.osWindows:
- if "Arial" in self.guiFontDB.families():
- theFont.setFamily("Arial")
- else:
- # On Windows, fall back to Cantarell provided by novelWriter
- theFont.setFamily("Cantarell")
+ if self.mainConf.osWindows and "Arial" in self.guiFontDB.families():
+ # On Windows we default to Arial if possible
+ theFont.setFamily("Arial")
theFont.setPointSize(10)
else:
theFont = self.guiFontDB.systemFont(QFontDatabase.GeneralFont)
@@ -237,16 +206,21 @@ class GuiTheme:
def updateTheme(self):
"""Update the GUI theme from theme files.
"""
- self.guiTheme = self.mainConf.guiTheme
- self.guiSyntax = self.mainConf.guiSyntax
- self.themeRoot = self.mainConf.themeRoot
- self.themePath = os.path.join(self.mainConf.themeRoot, self.guiPath, self.guiTheme)
- self.syntaxFile = os.path.join(self.themeRoot, self.syntaxPath, self.guiSyntax+".conf")
- self.confFile = os.path.join(self.themePath, self.confName)
- self.cssFile = os.path.join(self.themePath, self.cssName)
+ self.guiTheme = self.mainConf.guiTheme
+ self.guiSyntax = self.mainConf.guiSyntax
- self.loadTheme()
- self.loadSyntax()
+ self.themeFile = self._availThemes.get(self.guiTheme, None)
+ if self.themeFile is None:
+ logger.error("Could not find GUI theme '%s'", self.guiTheme)
+ else:
+ self.cssFile = self.themeFile[:-5]+".css"
+ self.loadTheme()
+
+ self.syntaxFile = self._availSyntax.get(self.guiSyntax, None)
+ if self.syntaxFile is None:
+ logger.error("Could not find syntax theme '%s'", self.guiSyntax)
+ else:
+ self.loadSyntax()
# Update dependant colours
backCol = qApp.palette().window().color()
@@ -267,27 +241,15 @@ class GuiTheme:
def loadTheme(self):
"""Load the currently specified GUI theme.
"""
- logger.debug("Loading theme files")
- logger.debug("System icon theme is '%s'", str(QIcon.themeName()))
-
- # CSS File
- cssData = ""
- try:
- if os.path.isfile(self.cssFile):
- with open(self.cssFile, mode="r", encoding="utf-8") as inFile:
- cssData = inFile.read()
- except Exception:
- logger.error("Could not load theme css file")
- novelwriter.logException()
- return False
+ logger.info("Loading GUI theme '%s'", self.guiTheme)
# Config File
confParser = NWConfigParser()
try:
- with open(self.confFile, mode="r", encoding="utf-8") as inFile:
+ with open(self.themeFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile)
except Exception:
- logger.error("Could not load theme settings from: %s", self.confFile)
+ logger.error("Could not load theme settings from: %s", self.themeFile)
novelwriter.logException()
return False
@@ -327,18 +289,20 @@ class GuiTheme:
self.statUnsaved = self._loadColour(confParser, cnfSec, "statusunsaved")
self.statSaved = self._loadColour(confParser, cnfSec, "statussaved")
- # Apply Styles
- qApp.setStyleSheet(cssData)
- qApp.setPalette(self.guiPalette)
+ # CSS File
+ cssData = readTextFile(self.cssFile)
+ if cssData:
+ qApp.setStyleSheet(cssData)
- logger.info("Loaded theme '%s'", self.guiTheme)
+ # Apply Styles
+ qApp.setPalette(self._guiPalette)
return True
def loadSyntax(self):
"""Load the currently specified syntax highlighter theme.
"""
- logger.debug("Loading syntax theme files")
+ logger.info("Loading syntax theme '%s'", self.guiSyntax)
confParser = NWConfigParser()
try:
@@ -380,79 +344,59 @@ class GuiTheme:
self.colRepTag = self._loadColour(confParser, cnfSec, "replacetag")
self.colMod = self._loadColour(confParser, cnfSec, "modifier")
- logger.info("Loaded syntax theme '%s'", self.guiSyntax)
-
return True
def listThemes(self):
"""Scan the GUI themes folder and list all themes.
"""
- if self.themeList:
- return self.themeList
+ if self._themeList:
+ return self._themeList
confParser = NWConfigParser()
- for themeDir in os.listdir(os.path.join(self.mainConf.themeRoot, self.guiPath)):
- themeConf = os.path.join(
- self.mainConf.themeRoot, self.guiPath, themeDir, self.confName
- )
- logger.verbose("Checking theme config for '%s'", themeDir)
- try:
- with open(themeConf, mode="r", encoding="utf-8") as inFile:
- confParser.read_file(inFile)
- except Exception as e:
- self.theParent.makeAlert([
- self.tr("Could not load theme config file."), str(e)
- ], nwAlert.ERROR)
- continue
- themeName = ""
- if confParser.has_section("Main"):
- if confParser.has_option("Main", "name"):
- themeName = confParser.get("Main", "name")
- logger.verbose("Theme name is '%s'", themeName)
- if themeName != "":
- self.themeList.append((themeDir, themeName))
+ for themeKey, themePath in self._availThemes.items():
+ logger.verbose("Checking theme config for '%s'", themeKey)
+ themeName = _loadInternalName(confParser, themePath)
+ if themeName:
+ self._themeList.append((themeKey, themeName))
- self.themeList = sorted(self.themeList, key=lambda x: x[1])
+ self._themeList = sorted(self._themeList, key=lambda x: x[1])
- return self.themeList
+ return self._themeList
def listSyntax(self):
"""Scan the syntax themes folder and list all themes.
"""
- if self.syntaxList:
- return self.syntaxList
+ if self._syntaxList:
+ return self._syntaxList
confParser = NWConfigParser()
- syntaxDir = os.path.join(self.mainConf.themeRoot, self.syntaxPath)
- for syntaxFile in os.listdir(syntaxDir):
- syntaxPath = os.path.join(syntaxDir, syntaxFile)
- if not os.path.isfile(syntaxPath):
- continue
- logger.verbose("Checking theme syntax for '%s'", syntaxFile)
- try:
- with open(syntaxPath, mode="r", encoding="utf-8") as inFile:
- confParser.read_file(inFile)
- except Exception as e:
- self.theParent.makeAlert([
- self.tr("Could not load syntax file."), str(e)
- ], nwAlert.ERROR)
- return []
- syntaxName = ""
- if confParser.has_section("Main"):
- if confParser.has_option("Main", "name"):
- syntaxName = confParser.get("Main", "name")
- if len(syntaxFile) > 5 and syntaxName != "":
- self.syntaxList.append((syntaxFile[:-5], syntaxName))
- logger.verbose("Syntax name is '%s'", syntaxName)
+ for syntaxKey, syntaxPath in self._availSyntax.items():
+ logger.verbose("Checking theme syntax for '%s'", syntaxKey)
+ syntaxName = _loadInternalName(confParser, syntaxPath)
+ if syntaxName:
+ self._syntaxList.append((syntaxKey, syntaxName))
- self.syntaxList = sorted(self.syntaxList, key=lambda x: x[1])
+ self._syntaxList = sorted(self._syntaxList, key=lambda x: x[1])
- return self.syntaxList
+ return self._syntaxList
##
# Internal Functions
##
+ def _listConf(self, targetDict, checkDir):
+ """Scan for syntax and gui themes and populate the dictionary.
+ """
+ if not os.path.isdir(checkDir):
+ return
+
+ for checkFile in os.listdir(checkDir):
+ confPath = os.path.join(checkDir, checkFile)
+ if os.path.isfile(confPath) and confPath.endswith(".conf"):
+ targetDict[checkFile[:-5]] = confPath
+
+ return
+
def _loadColour(self, confParser, cnfSec, cnfName):
"""Load a colour value from a config string.
"""
@@ -485,7 +429,7 @@ class GuiTheme:
logger.error("Could not load theme colours for '%s' from config file", cnfName)
return
if len(readCol) == 3:
- self.guiPalette.setColor(paletteVal, QColor(*readCol))
+ self._guiPalette.setColor(paletteVal, QColor(*readCol))
return
# End Class GuiTheme
@@ -498,116 +442,57 @@ class GuiTheme:
class GuiIcons:
"""The icon class manages the content of the assets/icons folder,
and provides a simple interface for requesting icons. Only icons
- listed in the ICON_MAP are handled.
+ listed in the ICON_KEYS are handled.
Icons are loaded on first request, and then cached for further
- requests. Each icon key in the ICON_MAP has a series of fallbacks:
- * The first lookup is in the key-to-file map for the selected icon
- theme. The map is specified in the icons.conf file in the theme
- folder. The map makes it possible to preserve the original file
- name from the icon theme were the icons were extracted.
- * Second, if the icon does not exist in the theme map, the
- GuiIcons class will check if there is a QStyle icon specified in
- the ICON_MAP data tuple[0]. This will let Qt pull the closest
- system icon.
- * Third action is to look up the freedesktop icon theme name using
- the fromTheme Qt call. This generally produces the same result
- as the step above, but has more icons available in other cases.
- * Fourth, and finally, the icon is looked up in the fallback
- folder. Files in this folder must have the same file name as the
- novelWriter internal icon key, with '-dark' appended to them for
- the dark background version of the icon. If no dark icon exists,
- the non-dark version will be returned.
+ requests. Each icon key in the ICON_KEYS set has standard icon set
+ in the icon theme conf file. The existence of the file, and the
+ definition of all keys are checked when the theme is loaded.
+
+ When an icon is requested, the icon is loaded and cached. If it is
+ missing, a blank icon is returned and a warning issued.
"""
- ICON_MAP = {
+ ICON_KEYS = {
# Project and GUI icons
- "novelwriter": (None, None),
- "cls_none": (QStyle.SP_DriveHDIcon, "drive-harddisk"),
- "cls_novel": (QStyle.SP_DriveHDIcon, "drive-harddisk"),
- "cls_plot": (QStyle.SP_DriveHDIcon, "drive-harddisk"),
- "cls_character": (QStyle.SP_DriveHDIcon, "drive-harddisk"),
- "cls_world": (QStyle.SP_DriveHDIcon, "drive-harddisk"),
- "cls_timeline": (QStyle.SP_DriveHDIcon, "drive-harddisk"),
- "cls_object": (QStyle.SP_DriveHDIcon, "drive-harddisk"),
- "cls_entity": (QStyle.SP_DriveHDIcon, "drive-harddisk"),
- "cls_custom": (QStyle.SP_DriveHDIcon, "drive-harddisk"),
- "cls_archive": (QStyle.SP_DriveHDIcon, "drive-harddisk"),
- "cls_trash": (QStyle.SP_TrashIcon, "user-trash"),
- "proj_document": (QStyle.SP_FileIcon, "x-office-document"),
- "proj_title": (QStyle.SP_FileIcon, "x-office-document"),
- "proj_chapter": (QStyle.SP_FileIcon, "x-office-document"),
- "proj_scene": (QStyle.SP_FileIcon, "x-office-document"),
- "proj_note": (QStyle.SP_FileIcon, "x-office-document"),
- "proj_folder": (QStyle.SP_DirIcon, "folder"),
- "proj_nwx": (None, None),
- "status_lang": (None, None),
- "status_time": (None, None),
- "status_idle": (None, None),
- "status_stats": (None, None),
- "status_lines": (None, None),
- "doc_h0": (QStyle.SP_FileIcon, "x-office-document"),
- "doc_h1": (QStyle.SP_FileIcon, "x-office-document"),
- "doc_h2": (QStyle.SP_FileIcon, "x-office-document"),
- "doc_h3": (QStyle.SP_FileIcon, "x-office-document"),
- "doc_h4": (QStyle.SP_FileIcon, "x-office-document"),
- "search_case": (None, None),
- "search_regex": (None, None),
- "search_word": (None, None),
- "search_loop": (None, None),
- "search_project": (None, None),
- "search_cancel": (None, None),
- "search_preserve": (None, None),
+ "novelwriter", "proj_nwx",
+ "cls_none", "cls_novel", "cls_plot", "cls_character", "cls_world",
+ "cls_timeline", "cls_object", "cls_entity", "cls_custom", "cls_archive", "cls_trash",
+ "proj_document", "proj_title", "proj_chapter", "proj_scene", "proj_note", "proj_folder",
+ "status_lang", "status_time", "status_idle", "status_stats", "status_lines",
+ "doc_h0", "doc_h1", "doc_h2", "doc_h3", "doc_h4",
+ "search_case", "search_regex", "search_word", "search_loop", "search_project",
+ "search_cancel", "search_preserve",
# General Button Icons
- "folder-open": (QStyle.SP_DirOpenIcon, "folder-open"),
- "delete": (QStyle.SP_DialogDiscardButton, "edit-delete"),
- "close": (QStyle.SP_DialogCloseButton, "window-close"),
- "done": (QStyle.SP_DialogApplyButton, None),
- "clear": (QStyle.SP_LineEditClearButton, "clear_left"),
- "save": (QStyle.SP_DialogSaveButton, "document-save"),
- "add": (None, "list-add"),
- "remove": (None, "list-remove"),
- "search": (None, "edit-find"),
- "search-replace": (None, "edit-find-replace"),
- "edit": (None, None),
- "check": (None, None),
- "cross": (None, None),
- "hash": (None, None),
- "maximise": (None, None),
- "minimise": (None, None),
- "refresh": (None, None),
- "reference": (None, None),
- "backward": (None, None),
- "forward": (None, None),
- "settings": (None, None),
+ "delete", "close", "done", "clear", "save", "add", "remove",
+ "search", "search_replace", "edit", "check", "cross", "hash",
+ "maximise", "minimise", "refresh", "reference", "backward",
+ "forward", "settings",
# Switches
- "sticky-on": (None, None),
- "sticky-off": (None, None),
- "bullet-on": (None, None),
- "bullet-off": (None, None),
+ "sticky-on", "sticky-off",
+ "bullet-on", "bullet-off",
}
DECO_MAP = {
"wiz-back": "wizard-back.jpg",
}
- def __init__(self, theParent):
+ def __init__(self, theTheme):
- self.mainConf = novelwriter.CONFIG
- self.theParent = theParent
+ self.mainConf = novelwriter.CONFIG
+ self.theTheme = theTheme
# Storage
- self.qIcons = {}
- self.themeMap = {}
- self.themeList = []
- self.fbackName = "fallback"
- self.confName = "icons.conf"
+ self._qIcons = {}
+ self._themeMap = {}
+ self._themeList = []
+ self._confName = "icons.conf"
# Icon Theme Path
- self.iconPath = None
- self.confFile = None
+ self._iconPath = os.path.join(self.mainConf.assetPath, "icons")
+ self._themePath = os.path.join(self._iconPath, "system")
# Icon Theme Meta
self.themeName = ""
@@ -629,24 +514,23 @@ class GuiIcons:
the GUI icons cannot really be replaced without writing specific
update functions for the classes where they're used.
"""
- logger.debug("Loading icon theme files")
-
- self.themeMap = {}
- checkPath = os.path.join(self.mainConf.iconPath, self.mainConf.guiIcons)
- if os.path.isdir(checkPath):
- logger.debug("Loading icon theme '%s'", self.mainConf.guiIcons)
- self.iconPath = checkPath
- self.confFile = os.path.join(checkPath, self.confName)
- else:
+ self._themeMap = {}
+ themePath = self._getThemePath()
+ if themePath is None:
+ logger.warning("No icons loaded")
return False
+ self._themePath = themePath
+ themeConf = os.path.join(themePath, self._confName)
+ logger.info("Loading icon theme '%s'", self.mainConf.guiIcons)
+
# Config File
confParser = NWConfigParser()
try:
- with open(self.confFile, mode="r", encoding="utf-8") as inFile:
+ with open(themeConf, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile)
except Exception:
- logger.error("Could not load icon theme settings from: %s", self.confFile)
+ logger.error("Could not load icon theme settings from: %s", themeConf)
novelwriter.logException()
return False
@@ -661,21 +545,28 @@ class GuiIcons:
self.themeLicense = confParser.rdStr(cnfSec, "license", "N/A")
self.themeLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")
- # Palette
+ # Populate Icon Map
cnfSec = "Map"
if confParser.has_section(cnfSec):
for iconName, iconFile in confParser.items(cnfSec):
- if iconName not in self.ICON_MAP:
+ if iconName not in self.ICON_KEYS:
logger.error("Unknown icon name '%s' in config file", iconName)
else:
- iconPath = os.path.join(self.iconPath, iconFile)
+ iconPath = os.path.join(self._themePath, iconFile)
if os.path.isfile(iconPath):
- self.themeMap[iconName] = iconPath
+ self._themeMap[iconName] = iconPath
logger.verbose("Icon slot '%s' using file '%s'", iconName, iconFile)
else:
logger.error("Icon file '%s' not in theme folder", iconFile)
- logger.info("Loaded icon theme '%s'", self.mainConf.guiIcons)
+ # Check that icons have been defined
+ logger.debug("Scanning theme icons")
+ for iconKey in self.ICON_KEYS:
+ if iconKey in ("novelwriter", "proj_nwx"):
+ # These are not part of the theme itself
+ continue
+ if iconKey not in self._themeMap:
+ logger.error("No icon file specified for '%s'", iconKey)
return True
@@ -713,11 +604,11 @@ class GuiIcons:
return, load it, and if it still doesn't exist, return an empty
icon.
"""
- if iconKey in self.qIcons:
- return self.qIcons[iconKey]
+ if iconKey in self._qIcons:
+ return self._qIcons[iconKey]
else:
qIcon = self._loadIcon(iconKey)
- self.qIcons[iconKey] = qIcon
+ self._qIcons[iconKey] = qIcon
return qIcon
def getPixmap(self, iconKey, iconSize):
@@ -758,89 +649,87 @@ class GuiIcons:
def listThemes(self):
"""Scan the icons themes folder and list all themes.
"""
- if self.themeList:
- return self.themeList
+ if self._themeList:
+ return self._themeList
confParser = NWConfigParser()
- for themeDir in os.listdir(self.mainConf.iconPath):
- themePath = os.path.join(self.mainConf.iconPath, themeDir)
- if not os.path.isdir(themePath) or themeDir == self.fbackName:
+ for themeDir in os.listdir(self._iconPath):
+ themePath = os.path.join(self._iconPath, themeDir)
+ if not os.path.isdir(themePath):
continue
- themeConf = os.path.join(themePath, self.confName)
+
logger.verbose("Checking icon theme config for '%s'", themeDir)
- try:
- with open(themeConf, mode="r", encoding="utf-8") as inFile:
- confParser.read_file(inFile)
- except Exception as e:
- self.theParent.makeAlert([
- self.tr("Could not load theme config file."), str(e)
- ], nwAlert.ERROR)
- continue
- themeName = ""
- if confParser.has_section("Main"):
- if confParser.has_option("Main", "name"):
- themeName = confParser.get("Main", "name")
- logger.verbose("Theme name is '%s'", themeName)
- if themeName != "":
- self.themeList.append((themeDir, themeName))
+ themeConf = os.path.join(themePath, self._confName)
+ themeName = _loadInternalName(confParser, themeConf)
+ if themeName:
+ self._themeList.append((themeDir, themeName))
- self.themeList = sorted(self.themeList, key=lambda x: x[1])
+ self._themeList = sorted(self._themeList, key=lambda x: x[1])
- return self.themeList
+ return self._themeList
##
# Internal Functions
##
- def _loadIcon(self, iconKey):
- """Load an icon from the assets or themes folder, with a
- preference for dark/light icons depending on theme type, if such
- an icon exists. Prefer svg files over png files. Always returns
- a QIcon.
+ def _getThemePath(self):
+ """Get a valid theme path. Returns None if it fails.
"""
- if iconKey not in self.ICON_MAP:
+ themePath = os.path.join(self.mainConf.assetPath, "icons", self.mainConf.guiIcons)
+ if not os.path.isdir(themePath):
+ logger.warning(
+ "Icon theme '%s' not found, resetting to default", self.mainConf.guiIcons
+ )
+ self.mainConf.setDefaultIconTheme()
+
+ themePath = os.path.join(self.mainConf.assetPath, "icons", self.mainConf.guiIcons)
+ if not os.path.isdir(themePath):
+ logger.error("Default icon theme not found")
+ return None
+
+ return themePath
+
+ def _loadIcon(self, iconKey):
+ """Load an icon from the assets themes folder. Is guaranteed to
+ return a QIcon.
+ """
+ if iconKey not in self.ICON_KEYS:
logger.error("Requested unknown icon name '%s'", iconKey)
return QIcon()
- # If we just want the app icon, return it right away
+ # If we just want the app icons, return right away
if iconKey == "novelwriter":
- return QIcon(os.path.join(self.mainConf.iconPath, "novelwriter.svg"))
+ return QIcon(os.path.join(self._iconPath, "novelwriter.svg"))
+ elif iconKey == "proj_nwx":
+ return QIcon(os.path.join(self._iconPath, "x-novelwriter-project.svg"))
- # Otherwise, we start looking for it
- # First in the theme folder
- if iconKey in self.themeMap:
- relPath = os.path.relpath(self.themeMap[iconKey], self.mainConf.iconPath)
+ # Otherwise, we load from the theme folder
+ if iconKey in self._themeMap:
+ relPath = os.path.relpath(self._themeMap[iconKey], self._iconPath)
logger.verbose("Loading: %s", relPath)
- return QIcon(self.themeMap[iconKey])
+ return QIcon(self._themeMap[iconKey])
- # Next, we try to load the Qt style icons
- if self.ICON_MAP[iconKey][0] is not None:
- logger.verbose("Loading icon '%s' from Qt QStyle.standardIcon", iconKey)
- return qApp.style().standardIcon(self.ICON_MAP[iconKey][0])
-
- # If we're still here, try to set from system theme
- if self.ICON_MAP[iconKey][1] is not None:
- logger.verbose("Loading icon '%s' from system theme", iconKey)
- if QIcon().hasThemeIcon(self.ICON_MAP[iconKey][1]):
- return QIcon().fromTheme(self.ICON_MAP[iconKey][1])
-
- # Finally. we check if we have a fallback icon
- if self.mainConf.guiDark:
- fbackIcon = os.path.join(
- self.mainConf.iconPath, self.fbackName, "%s-dark.svg" % iconKey
- )
- if os.path.isfile(fbackIcon):
- logger.verbose("Loading icon '%s' from fallback theme (dark mode)", iconKey)
- return QIcon(fbackIcon)
-
- fbackIcon = os.path.join(self.mainConf.iconPath, self.fbackName, "%s.svg" % iconKey)
- if os.path.isfile(fbackIcon):
- logger.verbose("Loading icon '%s' from fallback theme (light mode)", iconKey)
- return QIcon(fbackIcon)
-
- # Give up and return an empty icon
+ # If we didn't find one, give up and return an empty icon
logger.warning("Did not load an icon for '%s'", iconKey)
return QIcon()
# END Class GuiIcons
+
+
+# =============================================================================================== #
+# Module Functions
+# =============================================================================================== #
+
+def _loadInternalName(confParser, confFile):
+ """Open a conf file and read the 'name' setting.
+ """
+ try:
+ with open(confFile, mode="r", encoding="utf-8") as inFile:
+ confParser.read_file(inFile)
+ except Exception:
+ logger.error("Could not load file: %s", confFile)
+ novelwriter.logException()
+ return ""
+
+ return confParser.rdStr("Main", "name", "")
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index a2f30297..1b32718f 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -83,7 +83,7 @@ class GuiMain(QMainWindow):
# ============
# Core Classes and Settings
- self.theTheme = GuiTheme(self)
+ self.theTheme = GuiTheme()
self.theProject = NWProject(self)
self.theIndex = NWIndex(self.theProject)
self.hasProject = False
diff --git a/setup/debian/copyright b/setup/debian/copyright
index 5d7e2a98..a60ea0d4 100644
--- a/setup/debian/copyright
+++ b/setup/debian/copyright
@@ -19,12 +19,6 @@ License: GPL-3.0-or-later
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-Files: novelwriter/assets/fonts/Cantarell/*
-Copyright: 2009-2010, Understanding Limited
-License: OFL-1.1
- This Font Software is licensed under the SIL Open Font License, Version 1.1.
- This license is available with a FAQ at: http://scripts.sil.org/OFL
-
Files: novelwriter/assets/icons/typicons_*.svg
Copyright: 2019, Stephen Hutchings
License: CC-BY-SA-4.0
diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf
index 2b966644..8859e538 100644
--- a/tests/reference/baseConfig_novelwriter.conf
+++ b/tests/reference/baseConfig_novelwriter.conf
@@ -1,9 +1,8 @@
[Main]
-timestamp = 2021-09-10 00:23:50
+timestamp = 2021-09-15 14:39:52
theme = default
syntax = default_light
icons = typicons_light
-guidark = False
guifont =
guifontsize = 11
lastnotes = 0x0
diff --git a/tests/reference/guiPreferences_novelwriter.conf b/tests/reference/guiPreferences_novelwriter.conf
index e498b229..f6c342ea 100644
--- a/tests/reference/guiPreferences_novelwriter.conf
+++ b/tests/reference/guiPreferences_novelwriter.conf
@@ -1,9 +1,8 @@
[Main]
-timestamp = 2021-09-10 00:23:52
+timestamp = 2021-09-15 14:29:30
theme = default
syntax = default_light
icons = typicons_light
-guidark = True
guifont = Sans
guifontsize = 12
lastnotes = 0x0
diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py
index 087cd5c1..25527513 100644
--- a/tests/test_base/test_base_common.py
+++ b/tests/test_base/test_base_common.py
@@ -29,12 +29,13 @@ from datetime import datetime
from mock import causeOSError
from tools import writeFile
+from novelwriter.guimain import GuiMain
from novelwriter.common import (
checkString, checkInt, checkBool, checkHandle, isHandle, isTitleTag,
isItemClass, isItemType, isItemLayout, hexToInt, formatInt,
formatTimeStamp, formatTime, parseTimeStamp, splitVersionNumber,
- transferCase, fuzzyTime, numberToRoman, jsonEncode, makeFileNameSafe,
- sha256sum, NWConfigParser
+ transferCase, fuzzyTime, numberToRoman, jsonEncode, readTextFile,
+ makeFileNameSafe, sha256sum, getGuiItem, NWConfigParser
)
@@ -456,6 +457,24 @@ def testBaseCommon_JsonEncode():
# END Test testBaseCommon_JsonEncode
+@pytest.mark.base
+def testBaseCommon_ReadTextFile(monkeypatch, fncDir, ipsumText):
+ """Test the readTextFile function.
+ """
+ testText = "\n\n".join(ipsumText) + "\n"
+ testFile = os.path.join(fncDir, "ipsum.txt")
+ writeFile(testFile, testText)
+
+ assert readTextFile(os.path.join(fncDir, "not_a_file.txt")) == ""
+ assert readTextFile(testFile) == testText
+
+ with monkeypatch.context() as mp:
+ mp.setattr("builtins.open", causeOSError)
+ assert readTextFile(testFile) == ""
+
+# END Test testBaseCommon_ReadTextFile
+
+
@pytest.mark.base
def testBaseCommon_MakeFileNameSafe():
"""Test the makeFileNameSafe function.
@@ -508,6 +527,16 @@ def testBaseCommon_Sha256Sum(monkeypatch, fncDir, ipsumText):
# END Test testBaseCommon_Sha256Sum
+@pytest.mark.base
+def testBaseCommon_GetGuiItem(nwGUI):
+ """Check the GUI item function.
+ """
+ assert getGuiItem("gibberish") is None
+ assert isinstance(getGuiItem("GuiMain"), GuiMain)
+
+# END Test testBaseCommon_GetGuiItem
+
+
@pytest.mark.base
def testBaseCommon_NWConfigParser(fncDir):
"""Test the NWConfigParser subclass.
diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py
index 996d11c5..ae9301bb 100644
--- a/tests/test_base/test_base_config.py
+++ b/tests/test_base/test_base_config.py
@@ -139,7 +139,7 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
assert os.path.isfile(confFile)
copyfile(confFile, testFile)
- assert cmpFiles(testFile, compFile, [2, 9, 10])
+ assert cmpFiles(testFile, compFile, ignoreStart=("timestamp", "lastnotes", "guilang"))
# Load and save with OSError
with monkeypatch.context() as mp:
@@ -255,7 +255,7 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
assert theList == [("en_GB", "British English"), ("fr", "Français")]
copyfile(confFile, testFile)
- assert cmpFiles(testFile, compFile, [2, 9, 10])
+ assert cmpFiles(testFile, compFile, ignoreStart=("timestamp", "lastnotes", "guilang"))
# END Test testBaseConfig_Init
@@ -539,7 +539,7 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
assert tmpConf.confChanged is False
copyfile(confFile, testFile)
- assert cmpFiles(testFile, compFile, [2, 9, 10])
+ assert cmpFiles(testFile, compFile, ignoreStart=("timestamp", "lastnotes", "guilang"))
# END Test testBaseConfig_SettersGetters
diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py
index 4b3cf847..52d2448a 100644
--- a/tests/test_dialogs/test_dlg_preferences.py
+++ b/tests/test_dialogs/test_dlg_preferences.py
@@ -84,11 +84,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
tabGeneral = nwPrefs.tabGeneral
nwPrefs._tabBox.setCurrentWidget(tabGeneral)
- qtbot.wait(keyDelay)
- assert not tabGeneral.guiDark.isChecked()
- qtbot.mouseClick(tabGeneral.guiDark, Qt.LeftButton)
- assert tabGeneral.guiDark.isChecked()
-
qtbot.wait(keyDelay)
assert tabGeneral.showFullPath.isChecked()
qtbot.mouseClick(tabGeneral.showFullPath, Qt.LeftButton)
@@ -249,8 +244,12 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
testFile = os.path.join(outDir, "guiPreferences_novelwriter.conf")
compFile = os.path.join(refDir, "guiPreferences_novelwriter.conf")
copyfile(projFile, testFile)
- ignoreLines = [2, 7, 9, 10, 15, 16, 17, 18, 19, 20, 21, 22, 23, 32, 33]
- assert cmpFiles(testFile, compFile, ignoreLines)
+ ignTuple = (
+ "timestamp", "guifont", "lastnotes", "guilang", "geometry",
+ "preferences", "treecols", "novelcols", "projcols", "mainpane",
+ "docpane", "viewpane", "outlinepane", "textfont", "textsize"
+ )
+ assert cmpFiles(testFile, compFile, ignoreStart=ignTuple)
# Clean up
novelwriter.CONFIG = origConf
diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py
index 150b3af6..b35ffa36 100644
--- a/tests/test_gui/test_gui_theme.py
+++ b/tests/test_gui/test_gui_theme.py
@@ -23,7 +23,7 @@ import pytest
import novelwriter
from PyQt5.QtGui import QColor, QPixmap, QIcon
-from PyQt5.QtWidgets import QStyle, QMessageBox
+from PyQt5.QtWidgets import QMessageBox
keyDelay = 2
typeDelay = 1
@@ -50,7 +50,6 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
novelwriter.CONFIG.guiTheme = "default_dark"
novelwriter.CONFIG.guiSyntax = "tomorrow_night_eighties"
novelwriter.CONFIG.guiIcons = "typicons_colour_dark"
- novelwriter.CONFIG.guiDark = True
novelwriter.CONFIG.guiFont = "Cantarell"
novelwriter.CONFIG.guiFontSize = 11
novelwriter.CONFIG.confChanged = True
@@ -73,9 +72,8 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
assert novelwriter.CONFIG.guiTheme == "default_dark"
assert novelwriter.CONFIG.guiSyntax == "tomorrow_night_eighties"
assert novelwriter.CONFIG.guiIcons == "typicons_dark"
- assert novelwriter.CONFIG.guiDark is True
- assert novelwriter.CONFIG.guiFont == "Cantarell"
- assert novelwriter.CONFIG.guiFontSize == 11
+ assert novelwriter.CONFIG.guiFont != ""
+ assert novelwriter.CONFIG.guiFontSize > 0
# Check GUI Colours
thePalette = nwGUI.palette()
@@ -119,9 +117,8 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
# Test Icon class
theIcons = nwGUI.theTheme.theIcons
novelwriter.CONFIG.guiIcons = "invalid"
- assert not theIcons.updateTheme()
- novelwriter.CONFIG.guiIcons = "typicons_dark"
- assert theIcons.updateTheme()
+ assert theIcons.updateTheme() is True
+ assert novelwriter.CONFIG.guiIcons == "typicons_light"
# Ask for a non-existent key
anImg = theIcons.loadDecoration("nonsense", 20, 20)
@@ -168,17 +165,8 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
assert isinstance(anIcon, QIcon)
assert not anIcon.isNull()
- # Add test icons and test alternative load paths
- theIcons.ICON_MAP["testicon1"] = (QStyle.SP_DriveHDIcon, None)
- anIcon = theIcons.getIcon("testicon1")
- assert isinstance(anIcon, QIcon)
- assert not anIcon.isNull()
-
- theIcons.ICON_MAP["testicon2"] = (None, "folder")
- anIcon = theIcons.getIcon("testicon2")
- assert isinstance(anIcon, QIcon)
-
- theIcons.ICON_MAP["testicon3"] = (None, None)
+ # Check return empty icon if file not found
+ theIcons.ICON_KEYS.add("testicon3")
anIcon = theIcons.getIcon("testicon3")
assert isinstance(anIcon, QIcon)
assert anIcon.isNull()
diff --git a/tests/tools.py b/tests/tools.py
index 03f23d34..450c4fed 100644
--- a/tests/tools.py
+++ b/tests/tools.py
@@ -25,7 +25,7 @@ import shutil
from PyQt5.QtWidgets import qApp
-def cmpFiles(fileOne, fileTwo, ignoreLines=None):
+def cmpFiles(fileOne, fileTwo, ignoreLines=None, ignoreStart=None):
"""Compare two files, but optionally ignore lines given by a list.
"""
if ignoreLines is None:
@@ -59,6 +59,11 @@ def cmpFiles(fileOne, fileTwo, ignoreLines=None):
print("Ignoring line %d" % (n+1))
continue
+ if ignoreStart is not None:
+ if lnOne.startswith(ignoreStart):
+ print("Ignoring line %d" % (n+1))
+ continue
+
if lnOne != lnTwo:
print("Diff on line %d:" % (n+1))
print(" << '%s'" % lnOne)