From 97aebc5b84e0df963559aee54b7fe37b3fcc7859 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sun, 15 Aug 2021 17:55:57 +0200
Subject: [PATCH] Update Project Tree (#847)
* Rename index textCounts to fileMeta and add main header level
* Add new icons for document layouts
* Add function in NWItem to describe the item
* Use the icons and description in the project tree and details panel
* Remove flags column and add status column with optional full text
* Update handling and display of header level in editor
* Fix potential bug in project tree
* Update tests
---
.../typicons_colour_dark/document-chapter.svg | 47 ++++++++++++
.../typicons_colour_dark/document-note.svg | 47 ++++++++++++
.../typicons_colour_dark/document-scene.svg | 47 ++++++++++++
.../typicons_colour_dark/document-text.svg | 12 ++--
.../typicons_colour_dark/document-title.svg | 47 ++++++++++++
.../icons/typicons_colour_dark/icons.conf | 6 +-
.../document-chapter.svg | 47 ++++++++++++
.../typicons_colour_light/document-note.svg | 47 ++++++++++++
.../typicons_colour_light/document-scene.svg | 47 ++++++++++++
.../typicons_colour_light/document-text.svg | 12 ++--
.../typicons_colour_light/document-title.svg | 47 ++++++++++++
.../icons/typicons_colour_light/icons.conf | 6 +-
.../typicons_grey_dark/document-text.svg | 12 ++--
.../typicons_grey_dark/document-title.svg | 47 ++++++++++++
nw/assets/icons/typicons_grey_dark/icons.conf | 4 ++
.../typicons_grey_light/document-text.svg | 22 +++---
.../typicons_grey_light/document-title.svg | 47 ++++++++++++
.../icons/typicons_grey_light/icons.conf | 4 ++
nw/config.py | 11 +--
nw/constants.py | 26 +++----
nw/core/index.py | 72 +++++++++++--------
nw/core/item.py | 29 +++++++-
nw/dialogs/preferences.py | 11 +++
nw/gui/doceditor.py | 21 +++---
nw/gui/itemdetails.py | 63 ++++++++++------
nw/gui/projtree.py | 48 ++++++-------
nw/gui/theme.py | 37 +++++++++-
sample/nwProject.nwx | 10 +--
tests/reference/baseConfig_novelwriter.conf | 7 +-
.../coreIndex_LoadSave_tagsIndex.json | 32 ++++-----
.../reference/guiPreferences_novelwriter.conf | 7 +-
tests/test_core/test_core_index.py | 72 ++++++++++---------
tests/test_core/test_core_item.py | 33 +++++++++
tests/test_dialogs/test_dlg_preferences.py | 2 +-
34 files changed, 833 insertions(+), 196 deletions(-)
create mode 100644 nw/assets/icons/typicons_colour_dark/document-chapter.svg
create mode 100644 nw/assets/icons/typicons_colour_dark/document-note.svg
create mode 100644 nw/assets/icons/typicons_colour_dark/document-scene.svg
create mode 100644 nw/assets/icons/typicons_colour_dark/document-title.svg
create mode 100644 nw/assets/icons/typicons_colour_light/document-chapter.svg
create mode 100644 nw/assets/icons/typicons_colour_light/document-note.svg
create mode 100644 nw/assets/icons/typicons_colour_light/document-scene.svg
create mode 100644 nw/assets/icons/typicons_colour_light/document-title.svg
create mode 100644 nw/assets/icons/typicons_grey_dark/document-title.svg
create mode 100644 nw/assets/icons/typicons_grey_light/document-title.svg
diff --git a/nw/assets/icons/typicons_colour_dark/document-chapter.svg b/nw/assets/icons/typicons_colour_dark/document-chapter.svg
new file mode 100644
index 00000000..e33bb775
--- /dev/null
+++ b/nw/assets/icons/typicons_colour_dark/document-chapter.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/nw/assets/icons/typicons_colour_dark/document-note.svg b/nw/assets/icons/typicons_colour_dark/document-note.svg
new file mode 100644
index 00000000..c7ac4ae6
--- /dev/null
+++ b/nw/assets/icons/typicons_colour_dark/document-note.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/nw/assets/icons/typicons_colour_dark/document-scene.svg b/nw/assets/icons/typicons_colour_dark/document-scene.svg
new file mode 100644
index 00000000..f5fe21e6
--- /dev/null
+++ b/nw/assets/icons/typicons_colour_dark/document-scene.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/nw/assets/icons/typicons_colour_dark/document-text.svg b/nw/assets/icons/typicons_colour_dark/document-text.svg
index d363691e..0d3df7c2 100644
--- a/nw/assets/icons/typicons_colour_dark/document-text.svg
+++ b/nw/assets/icons/typicons_colour_dark/document-text.svg
@@ -26,10 +26,14 @@
id="defs2165" />
+ d="m 5.8888883,3.4444441 c -0.6734444,0 -1.2222222,0.5487778 -1.2222222,1.2222222 V 19.333334 c 0,0.673444 0.5487778,1.222222 1.2222222,1.222222 H 18.11111 c 0.673446,0 1.222224,-0.548778 1.222224,-1.222222 V 4.6666663 c 0,-0.6734444 -0.548778,-1.2222222 -1.222224,-1.2222222 z m 1.2222222,2.4444444 h 9.7777775 c 0.337334,0 0.611112,0.2737778 0.611112,0.6111112 0,0.3373334 -0.273778,0.6111113 -0.611112,0.6111113 H 7.1111105 c -0.3373332,0 -0.611111,-0.2737779 -0.611111,-0.6111112 0,-0.3373335 0.2737778,-0.6111112 0.611111,-0.6111113 z m 0,3.6666664 h 9.7777775 c 0.337334,0 0.611112,0.2737789 0.611112,0.6111111 0,0.337334 -0.273778,0.611112 -0.611112,0.611112 H 7.1111105 c -0.3373332,0 -0.611111,-0.273778 -0.611111,-0.611112 0,-0.3373322 0.2737778,-0.6111111 0.611111,-0.6111111 z m 0,3.6666671 h 9.7777775 c 0.337334,0 0.611112,0.273778 0.611112,0.611112 0,0.337332 -0.273778,0.61111 -0.611112,0.61111 H 7.1111105 c -0.3373332,0 -0.611111,-0.273778 -0.611111,-0.61111 0,-0.337334 0.2737778,-0.611112 0.611111,-0.611112 z m 0,3.666666 h 9.7777775 c 0.337334,0 0.611112,0.273778 0.611112,0.611112 0,0.337334 -0.273778,0.611112 -0.611112,0.611112 H 7.1111105 c -0.3373332,0 -0.611111,-0.273778 -0.611111,-0.611112 0,-0.337334 0.2737778,-0.611112 0.611111,-0.611112 z"
+ style="display:inline;opacity:0.95;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.88635;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ style="display:inline;fill:#595959;fill-opacity:1;stroke-width:1.22222"
+ d="M 7.1113281 5.8886719 C 6.7739949 5.8886719 6.5 6.1626666 6.5 6.5 C 6.5 6.8373334 6.7739949 7.1113281 7.1113281 7.1113281 L 16.888672 7.1113281 C 17.226006 7.1113281 17.5 6.8373334 17.5 6.5 C 17.5 6.1626666 17.226006 5.8886719 16.888672 5.8886719 L 7.1113281 5.8886719 z M 7.1113281 9.5546875 C 6.7739949 9.5546875 6.5 9.8286836 6.5 10.166016 C 6.5 10.50335 6.7739949 10.777344 7.1113281 10.777344 L 16.888672 10.777344 C 17.226006 10.777344 17.5 10.50335 17.5 10.166016 C 17.5 9.8286836 17.226006 9.5546875 16.888672 9.5546875 L 7.1113281 9.5546875 z M 7.1113281 13.222656 C 6.7739949 13.222656 6.5 13.49665 6.5 13.833984 C 6.5 14.171316 6.7739949 14.445312 7.1113281 14.445312 L 16.888672 14.445312 C 17.226006 14.445313 17.5 14.171316 17.5 13.833984 C 17.5 13.49665 17.226006 13.222656 16.888672 13.222656 L 7.1113281 13.222656 z M 7.1113281 16.888672 C 6.7739949 16.888672 6.5 17.162666 6.5 17.5 C 6.5 17.837334 6.7739949 18.111328 7.1113281 18.111328 L 16.888672 18.111328 C 17.226006 18.111328 17.5 17.837334 17.5 17.5 C 17.5 17.162666 17.226006 16.888672 16.888672 16.888672 L 7.1113281 16.888672 z " />
+
diff --git a/nw/assets/icons/typicons_colour_dark/document-title.svg b/nw/assets/icons/typicons_colour_dark/document-title.svg
new file mode 100644
index 00000000..6ecf2d5a
--- /dev/null
+++ b/nw/assets/icons/typicons_colour_dark/document-title.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/nw/assets/icons/typicons_colour_dark/icons.conf b/nw/assets/icons/typicons_colour_dark/icons.conf
index 5d4c0f2e..bbfe8468 100644
--- a/nw/assets/icons/typicons_colour_dark/icons.conf
+++ b/nw/assets/icons/typicons_colour_dark/icons.conf
@@ -8,7 +8,7 @@
[Main]
name = Typicons Colour Dark
-description = Coulorised icons for dark GUI theme based on Typicons.
+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
@@ -28,6 +28,10 @@ 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
diff --git a/nw/assets/icons/typicons_colour_light/document-chapter.svg b/nw/assets/icons/typicons_colour_light/document-chapter.svg
new file mode 100644
index 00000000..edcc83fc
--- /dev/null
+++ b/nw/assets/icons/typicons_colour_light/document-chapter.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/nw/assets/icons/typicons_colour_light/document-note.svg b/nw/assets/icons/typicons_colour_light/document-note.svg
new file mode 100644
index 00000000..2751929a
--- /dev/null
+++ b/nw/assets/icons/typicons_colour_light/document-note.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/nw/assets/icons/typicons_colour_light/document-scene.svg b/nw/assets/icons/typicons_colour_light/document-scene.svg
new file mode 100644
index 00000000..f2c1c576
--- /dev/null
+++ b/nw/assets/icons/typicons_colour_light/document-scene.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/nw/assets/icons/typicons_colour_light/document-text.svg b/nw/assets/icons/typicons_colour_light/document-text.svg
index 08c65fdb..551239c3 100644
--- a/nw/assets/icons/typicons_colour_light/document-text.svg
+++ b/nw/assets/icons/typicons_colour_light/document-text.svg
@@ -26,10 +26,14 @@
id="defs2165" />
+ d="m 5.8888883,3.4444441 c -0.6734444,0 -1.2222222,0.5487778 -1.2222222,1.2222222 V 19.333334 c 0,0.673444 0.5487778,1.222222 1.2222222,1.222222 H 18.11111 c 0.673446,0 1.222224,-0.548778 1.222224,-1.222222 V 4.6666663 c 0,-0.6734444 -0.548778,-1.2222222 -1.222224,-1.2222222 z m 1.2222222,2.4444444 h 9.7777775 c 0.337334,0 0.611112,0.2737778 0.611112,0.6111112 0,0.3373334 -0.273778,0.6111113 -0.611112,0.6111113 H 7.1111105 c -0.3373332,0 -0.611111,-0.2737779 -0.611111,-0.6111112 0,-0.3373335 0.2737778,-0.6111112 0.611111,-0.6111113 z m 0,3.6666664 h 9.7777775 c 0.337334,0 0.611112,0.2737789 0.611112,0.6111111 0,0.337334 -0.273778,0.611112 -0.611112,0.611112 H 7.1111105 c -0.3373332,0 -0.611111,-0.273778 -0.611111,-0.611112 0,-0.3373322 0.2737778,-0.6111111 0.611111,-0.6111111 z m 0,3.6666671 h 9.7777775 c 0.337334,0 0.611112,0.273778 0.611112,0.611112 0,0.337332 -0.273778,0.61111 -0.611112,0.61111 H 7.1111105 c -0.3373332,0 -0.611111,-0.273778 -0.611111,-0.61111 0,-0.337334 0.2737778,-0.611112 0.611111,-0.611112 z m 0,3.666666 h 9.7777775 c 0.337334,0 0.611112,0.273778 0.611112,0.611112 0,0.337334 -0.273778,0.611112 -0.611112,0.611112 H 7.1111105 c -0.3373332,0 -0.611111,-0.273778 -0.611111,-0.611112 0,-0.337334 0.2737778,-0.611112 0.611111,-0.611112 z"
+ style="display:inline;opacity:0.95;fill:#ededed;fill-opacity:1;stroke:none;stroke-width:1.88635;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ style="display:inline;fill:#333333;fill-opacity:1;stroke-width:1.22222"
+ d="M 7.1113281 5.8886719 C 6.7739949 5.8886719 6.5 6.1626666 6.5 6.5 C 6.5 6.8373334 6.7739949 7.1113281 7.1113281 7.1113281 L 16.888672 7.1113281 C 17.226006 7.1113281 17.5 6.8373334 17.5 6.5 C 17.5 6.1626666 17.226006 5.8886719 16.888672 5.8886719 L 7.1113281 5.8886719 z M 7.1113281 9.5546875 C 6.7739949 9.5546875 6.5 9.8286836 6.5 10.166016 C 6.5 10.50335 6.7739949 10.777344 7.1113281 10.777344 L 16.888672 10.777344 C 17.226006 10.777344 17.5 10.50335 17.5 10.166016 C 17.5 9.8286836 17.226006 9.5546875 16.888672 9.5546875 L 7.1113281 9.5546875 z M 7.1113281 13.222656 C 6.7739949 13.222656 6.5 13.49665 6.5 13.833984 C 6.5 14.171316 6.7739949 14.445312 7.1113281 14.445312 L 16.888672 14.445312 C 17.226006 14.445313 17.5 14.171316 17.5 13.833984 C 17.5 13.49665 17.226006 13.222656 16.888672 13.222656 L 7.1113281 13.222656 z M 7.1113281 16.888672 C 6.7739949 16.888672 6.5 17.162666 6.5 17.5 C 6.5 17.837334 6.7739949 18.111328 7.1113281 18.111328 L 16.888672 18.111328 C 17.226006 18.111328 17.5 17.837334 17.5 17.5 C 17.5 17.162666 17.226006 16.888672 16.888672 16.888672 L 7.1113281 16.888672 z " />
+
diff --git a/nw/assets/icons/typicons_colour_light/document-title.svg b/nw/assets/icons/typicons_colour_light/document-title.svg
new file mode 100644
index 00000000..2051ff58
--- /dev/null
+++ b/nw/assets/icons/typicons_colour_light/document-title.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/nw/assets/icons/typicons_colour_light/icons.conf b/nw/assets/icons/typicons_colour_light/icons.conf
index 775aa4d5..003809a6 100644
--- a/nw/assets/icons/typicons_colour_light/icons.conf
+++ b/nw/assets/icons/typicons_colour_light/icons.conf
@@ -8,7 +8,7 @@
[Main]
name = Typicons Colour Light
-description = Coulorised icons for light GUI theme based on Typicons.
+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
@@ -28,6 +28,10 @@ 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
diff --git a/nw/assets/icons/typicons_grey_dark/document-text.svg b/nw/assets/icons/typicons_grey_dark/document-text.svg
index d363691e..551239c3 100644
--- a/nw/assets/icons/typicons_grey_dark/document-text.svg
+++ b/nw/assets/icons/typicons_grey_dark/document-text.svg
@@ -26,10 +26,14 @@
id="defs2165" />
+ d="m 5.8888883,3.4444441 c -0.6734444,0 -1.2222222,0.5487778 -1.2222222,1.2222222 V 19.333334 c 0,0.673444 0.5487778,1.222222 1.2222222,1.222222 H 18.11111 c 0.673446,0 1.222224,-0.548778 1.222224,-1.222222 V 4.6666663 c 0,-0.6734444 -0.548778,-1.2222222 -1.222224,-1.2222222 z m 1.2222222,2.4444444 h 9.7777775 c 0.337334,0 0.611112,0.2737778 0.611112,0.6111112 0,0.3373334 -0.273778,0.6111113 -0.611112,0.6111113 H 7.1111105 c -0.3373332,0 -0.611111,-0.2737779 -0.611111,-0.6111112 0,-0.3373335 0.2737778,-0.6111112 0.611111,-0.6111113 z m 0,3.6666664 h 9.7777775 c 0.337334,0 0.611112,0.2737789 0.611112,0.6111111 0,0.337334 -0.273778,0.611112 -0.611112,0.611112 H 7.1111105 c -0.3373332,0 -0.611111,-0.273778 -0.611111,-0.611112 0,-0.3373322 0.2737778,-0.6111111 0.611111,-0.6111111 z m 0,3.6666671 h 9.7777775 c 0.337334,0 0.611112,0.273778 0.611112,0.611112 0,0.337332 -0.273778,0.61111 -0.611112,0.61111 H 7.1111105 c -0.3373332,0 -0.611111,-0.273778 -0.611111,-0.61111 0,-0.337334 0.2737778,-0.611112 0.611111,-0.611112 z m 0,3.666666 h 9.7777775 c 0.337334,0 0.611112,0.273778 0.611112,0.611112 0,0.337334 -0.273778,0.611112 -0.611112,0.611112 H 7.1111105 c -0.3373332,0 -0.611111,-0.273778 -0.611111,-0.611112 0,-0.337334 0.2737778,-0.611112 0.611111,-0.611112 z"
+ style="display:inline;opacity:0.95;fill:#ededed;fill-opacity:1;stroke:none;stroke-width:1.88635;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ style="display:inline;fill:#333333;fill-opacity:1;stroke-width:1.22222"
+ d="M 7.1113281 5.8886719 C 6.7739949 5.8886719 6.5 6.1626666 6.5 6.5 C 6.5 6.8373334 6.7739949 7.1113281 7.1113281 7.1113281 L 16.888672 7.1113281 C 17.226006 7.1113281 17.5 6.8373334 17.5 6.5 C 17.5 6.1626666 17.226006 5.8886719 16.888672 5.8886719 L 7.1113281 5.8886719 z M 7.1113281 9.5546875 C 6.7739949 9.5546875 6.5 9.8286836 6.5 10.166016 C 6.5 10.50335 6.7739949 10.777344 7.1113281 10.777344 L 16.888672 10.777344 C 17.226006 10.777344 17.5 10.50335 17.5 10.166016 C 17.5 9.8286836 17.226006 9.5546875 16.888672 9.5546875 L 7.1113281 9.5546875 z M 7.1113281 13.222656 C 6.7739949 13.222656 6.5 13.49665 6.5 13.833984 C 6.5 14.171316 6.7739949 14.445312 7.1113281 14.445312 L 16.888672 14.445312 C 17.226006 14.445313 17.5 14.171316 17.5 13.833984 C 17.5 13.49665 17.226006 13.222656 16.888672 13.222656 L 7.1113281 13.222656 z M 7.1113281 16.888672 C 6.7739949 16.888672 6.5 17.162666 6.5 17.5 C 6.5 17.837334 6.7739949 18.111328 7.1113281 18.111328 L 16.888672 18.111328 C 17.226006 18.111328 17.5 17.837334 17.5 17.5 C 17.5 17.162666 17.226006 16.888672 16.888672 16.888672 L 7.1113281 16.888672 z " />
+
diff --git a/nw/assets/icons/typicons_grey_dark/document-title.svg b/nw/assets/icons/typicons_grey_dark/document-title.svg
new file mode 100644
index 00000000..9dbdfb81
--- /dev/null
+++ b/nw/assets/icons/typicons_grey_dark/document-title.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/nw/assets/icons/typicons_grey_dark/icons.conf b/nw/assets/icons/typicons_grey_dark/icons.conf
index 7dc56936..e66d3cf5 100644
--- a/nw/assets/icons/typicons_grey_dark/icons.conf
+++ b/nw/assets/icons/typicons_grey_dark/icons.conf
@@ -28,6 +28,10 @@ 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-title.svg
+proj_scene = document-title.svg
+proj_note = document-text.svg
proj_folder = folder.svg
doc_h0 = heading0.svg
doc_h1 = heading1.svg
diff --git a/nw/assets/icons/typicons_grey_light/document-text.svg b/nw/assets/icons/typicons_grey_light/document-text.svg
index 6b4722ec..551239c3 100644
--- a/nw/assets/icons/typicons_grey_light/document-text.svg
+++ b/nw/assets/icons/typicons_grey_light/document-text.svg
@@ -5,11 +5,11 @@
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
- id="svg2161"
- viewBox="0 0 24 24"
- height="24"
+ version="1.2"
width="24"
- version="1.2">
+ height="24"
+ viewBox="0 0 24 24"
+ id="svg2161">
@@ -25,11 +25,15 @@
+ id="rect12986"
+ d="m 5.8888883,3.4444441 c -0.6734444,0 -1.2222222,0.5487778 -1.2222222,1.2222222 V 19.333334 c 0,0.673444 0.5487778,1.222222 1.2222222,1.222222 H 18.11111 c 0.673446,0 1.222224,-0.548778 1.222224,-1.222222 V 4.6666663 c 0,-0.6734444 -0.548778,-1.2222222 -1.222224,-1.2222222 z m 1.2222222,2.4444444 h 9.7777775 c 0.337334,0 0.611112,0.2737778 0.611112,0.6111112 0,0.3373334 -0.273778,0.6111113 -0.611112,0.6111113 H 7.1111105 c -0.3373332,0 -0.611111,-0.2737779 -0.611111,-0.6111112 0,-0.3373335 0.2737778,-0.6111112 0.611111,-0.6111113 z m 0,3.6666664 h 9.7777775 c 0.337334,0 0.611112,0.2737789 0.611112,0.6111111 0,0.337334 -0.273778,0.611112 -0.611112,0.611112 H 7.1111105 c -0.3373332,0 -0.611111,-0.273778 -0.611111,-0.611112 0,-0.3373322 0.2737778,-0.6111111 0.611111,-0.6111111 z m 0,3.6666671 h 9.7777775 c 0.337334,0 0.611112,0.273778 0.611112,0.611112 0,0.337332 -0.273778,0.61111 -0.611112,0.61111 H 7.1111105 c -0.3373332,0 -0.611111,-0.273778 -0.611111,-0.61111 0,-0.337334 0.2737778,-0.611112 0.611111,-0.611112 z m 0,3.666666 h 9.7777775 c 0.337334,0 0.611112,0.273778 0.611112,0.611112 0,0.337334 -0.273778,0.611112 -0.611112,0.611112 H 7.1111105 c -0.3373332,0 -0.611111,-0.273778 -0.611111,-0.611112 0,-0.337334 0.2737778,-0.611112 0.611111,-0.611112 z"
+ style="display:inline;opacity:0.95;fill:#ededed;fill-opacity:1;stroke:none;stroke-width:1.88635;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ style="display:inline;fill:#333333;fill-opacity:1;stroke-width:1.22222"
+ d="M 7.1113281 5.8886719 C 6.7739949 5.8886719 6.5 6.1626666 6.5 6.5 C 6.5 6.8373334 6.7739949 7.1113281 7.1113281 7.1113281 L 16.888672 7.1113281 C 17.226006 7.1113281 17.5 6.8373334 17.5 6.5 C 17.5 6.1626666 17.226006 5.8886719 16.888672 5.8886719 L 7.1113281 5.8886719 z M 7.1113281 9.5546875 C 6.7739949 9.5546875 6.5 9.8286836 6.5 10.166016 C 6.5 10.50335 6.7739949 10.777344 7.1113281 10.777344 L 16.888672 10.777344 C 17.226006 10.777344 17.5 10.50335 17.5 10.166016 C 17.5 9.8286836 17.226006 9.5546875 16.888672 9.5546875 L 7.1113281 9.5546875 z M 7.1113281 13.222656 C 6.7739949 13.222656 6.5 13.49665 6.5 13.833984 C 6.5 14.171316 6.7739949 14.445312 7.1113281 14.445312 L 16.888672 14.445312 C 17.226006 14.445313 17.5 14.171316 17.5 13.833984 C 17.5 13.49665 17.226006 13.222656 16.888672 13.222656 L 7.1113281 13.222656 z M 7.1113281 16.888672 C 6.7739949 16.888672 6.5 17.162666 6.5 17.5 C 6.5 17.837334 6.7739949 18.111328 7.1113281 18.111328 L 16.888672 18.111328 C 17.226006 18.111328 17.5 17.837334 17.5 17.5 C 17.5 17.162666 17.226006 16.888672 16.888672 16.888672 L 7.1113281 16.888672 z " />
+
diff --git a/nw/assets/icons/typicons_grey_light/document-title.svg b/nw/assets/icons/typicons_grey_light/document-title.svg
new file mode 100644
index 00000000..4d746749
--- /dev/null
+++ b/nw/assets/icons/typicons_grey_light/document-title.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/nw/assets/icons/typicons_grey_light/icons.conf b/nw/assets/icons/typicons_grey_light/icons.conf
index 7c47ba7d..a0afad76 100644
--- a/nw/assets/icons/typicons_grey_light/icons.conf
+++ b/nw/assets/icons/typicons_grey_light/icons.conf
@@ -28,6 +28,10 @@ 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-title.svg
+proj_scene = document-title.svg
+proj_note = document-text.svg
proj_folder = folder.svg
doc_h0 = heading0.svg
doc_h1 = heading1.svg
diff --git a/nw/config.py b/nw/config.py
index b4b8d1b2..84d2ddff 100644
--- a/nw/config.py
+++ b/nw/config.py
@@ -110,6 +110,7 @@ class Config:
# Features
self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
+ self.fullStatus = True # Show the full status text in the project tree
# Project
self.autoSaveProj = 60 # Interval for auto-saving project in seconds
@@ -448,6 +449,9 @@ class Config:
self.guiFontSize = theConf.rdInt(cnfSec, "guifontsize", self.guiFontSize)
self.lastNotes = theConf.rdStr(cnfSec, "lastnotes", self.lastNotes)
self.guiLang = theConf.rdStr(cnfSec, "guilang", self.guiLang)
+ self.hideVScroll = theConf.rdBool(cnfSec, "hidevscroll", self.hideVScroll)
+ self.hideHScroll = theConf.rdBool(cnfSec, "hidehscroll", self.hideHScroll)
+ self.fullStatus = theConf.rdBool(cnfSec, "fullstatus", self.fullStatus)
# Sizes
cnfSec = "Sizes"
@@ -461,8 +465,6 @@ class Config:
self.viewPanePos = theConf.rdIntList(cnfSec, "viewpane", self.viewPanePos)
self.outlnPanePos = theConf.rdIntList(cnfSec, "outlinepane", self.outlnPanePos)
self.isFullScreen = theConf.rdBool(cnfSec, "fullscreen", self.isFullScreen)
- self.hideVScroll = theConf.rdBool(cnfSec, "hidevscroll", self.hideVScroll)
- self.hideHScroll = theConf.rdBool(cnfSec, "hidehscroll", self.hideHScroll)
# Project
cnfSec = "Project"
@@ -563,6 +565,9 @@ class Config:
"guifontsize": str(self.guiFontSize),
"lastnotes": str(self.lastNotes),
"guilang": str(self.guiLang),
+ "hidevscroll": str(self.hideVScroll),
+ "hidehscroll": str(self.hideHScroll),
+ "fullstatus": str(self.fullStatus),
}
theConf["Sizes"] = {
@@ -576,8 +581,6 @@ class Config:
"viewpane": self._packList(self.viewPanePos),
"outlinepane": self._packList(self.outlnPanePos),
"fullscreen": str(self.isFullScreen),
- "hidevscroll": str(self.hideVScroll),
- "hidehscroll": str(self.hideHScroll),
}
theConf["Project"] = {
diff --git a/nw/constants.py b/nw/constants.py
index 8500ae9b..c0c978da 100644
--- a/nw/constants.py
+++ b/nw/constants.py
@@ -145,19 +145,6 @@ class nwLabels():
nwItemClass.ARCHIVE: QT_TRANSLATE_NOOP("Constant", "Outtakes"),
nwItemClass.TRASH: QT_TRANSLATE_NOOP("Constant", "Trash"),
}
- CLASS_FLAG = {
- nwItemClass.NO_CLASS: "0",
- nwItemClass.NOVEL: "N",
- nwItemClass.PLOT: "P",
- nwItemClass.CHARACTER: "C",
- nwItemClass.WORLD: "L",
- nwItemClass.TIMELINE: "T",
- nwItemClass.OBJECT: "O",
- nwItemClass.ENTITY: "E",
- nwItemClass.CUSTOM: "X",
- nwItemClass.ARCHIVE: "U",
- nwItemClass.TRASH: "R",
- }
CLASS_ICON = {
nwItemClass.NO_CLASS: "cls_none",
nwItemClass.NOVEL: "cls_novel",
@@ -176,10 +163,15 @@ class nwLabels():
nwItemLayout.DOCUMENT: QT_TRANSLATE_NOOP("Constant", "Novel Document"),
nwItemLayout.NOTE: QT_TRANSLATE_NOOP("Constant", "Project Note"),
}
- LAYOUT_FLAG = {
- nwItemLayout.NO_LAYOUT: "Xo",
- nwItemLayout.DOCUMENT: "Dc",
- nwItemLayout.NOTE: "Nt",
+ ITEM_DESCRIPTION = {
+ "none": QT_TRANSLATE_NOOP("Constant", "None"),
+ "root": QT_TRANSLATE_NOOP("Constant", "Root Folder"),
+ "folder": QT_TRANSLATE_NOOP("Constant", "Folder"),
+ "document": QT_TRANSLATE_NOOP("Constant", "Novel Document"),
+ "doc_h1": QT_TRANSLATE_NOOP("Constant", "Novel Title Page"),
+ "doc_h2": QT_TRANSLATE_NOOP("Constant", "Novel Chapter"),
+ "doc_h3": QT_TRANSLATE_NOOP("Constant", "Novel Scene"),
+ "note": QT_TRANSLATE_NOOP("Constant", "Project Note"),
}
KEY_NAME = {
nwKeyWords.TAG_KEY: QT_TRANSLATE_NOOP("Constant", "Tag"),
diff --git a/nw/core/index.py b/nw/core/index.py
index 997ed33d..1a49cb6b 100644
--- a/nw/core/index.py
+++ b/nw/core/index.py
@@ -54,10 +54,10 @@ class NWIndex():
self.indexBroken = False
# Indices
- self._tagIndex = {}
- self._refIndex = {}
- self._fileIndex = {}
- self._textCounts = {}
+ self._tagIndex = {}
+ self._refIndex = {}
+ self._fileIndex = {}
+ self._fileMeta = {}
# TimeStamps
self._timeNovel = 0
@@ -73,13 +73,13 @@ class NWIndex():
def clearIndex(self):
"""Clear the index dictionaries and time stamps.
"""
- self._tagIndex = {}
- self._refIndex = {}
- self._fileIndex = {}
- self._textCounts = {}
- self._timeNovel = 0
- self._timeNotes = 0
- self._timeIndex = 0
+ self._tagIndex = {}
+ self._refIndex = {}
+ self._fileIndex = {}
+ self._fileMeta = {}
+ self._timeNovel = 0
+ self._timeNotes = 0
+ self._timeIndex = 0
return
def deleteHandle(self, tHandle):
@@ -97,7 +97,7 @@ class NWIndex():
self._refIndex.pop(tHandle, None)
self._fileIndex.pop(tHandle, None)
- self._textCounts.pop(tHandle, None)
+ self._fileMeta.pop(tHandle, None)
return
@@ -162,7 +162,7 @@ class NWIndex():
self._tagIndex = theData.get("tagIndex", {})
self._refIndex = theData.get("refIndex", {})
self._fileIndex = theData.get("fileIndex", {})
- self._textCounts = theData.get("textCounts", {})
+ self._fileMeta = theData.get("fileMeta", {})
nowTime = round(time())
self._timeNovel = nowTime
@@ -189,7 +189,7 @@ class NWIndex():
outFile.write(f'"tagIndex": {jsonEncode(self._tagIndex, nmax=1)},\n')
outFile.write(f'"refIndex": {jsonEncode(self._refIndex, nmax=2)},\n')
outFile.write(f'"fileIndex": {jsonEncode(self._fileIndex, nmax=2)},\n')
- outFile.write(f'"textCounts": {jsonEncode(self._textCounts, nmax=1)}\n')
+ outFile.write(f'"fileMeta": {jsonEncode(self._fileMeta, nmax=1)}\n')
outFile.write("}\n")
except Exception:
@@ -212,7 +212,7 @@ class NWIndex():
self._checkTagIndex()
self._checkRefIndex()
self._checkFileIndex()
- self._checkTextCounts()
+ self._checkFileMeta()
self.indexBroken = False
except Exception:
@@ -256,7 +256,7 @@ class NWIndex():
# Run word counter for the whole text
cC, wC, pC = countWords(theText)
- self._textCounts[tHandle] = [cC, wC, pC]
+ self._fileMeta[tHandle] = ["H0", cC, wC, pC]
# If the file is archived or trashed, we don't index the file itself
if self.theProject.projTree.isTrashRoot(theItem.itemParent):
@@ -374,6 +374,9 @@ class NWIndex():
"synopsis": "",
}
+ if self._fileMeta[tHandle][0] == "H0":
+ self._fileMeta[tHandle][0] = hDepth
+
return True
def _indexPage(self, tHandle, itemLayout):
@@ -573,6 +576,13 @@ class NWIndex():
return theHeaders
+ def getHandleHeaderLevel(self, tHandle):
+ """Get the header level of the first header of a handle.
+ """
+ if tHandle in self._fileMeta:
+ return self._fileMeta[tHandle][0]
+ return "H0"
+
def getTableOfContents(self, maxDepth, skipExcluded=True):
"""Generate a table of contents up to a maxiumum depth.
"""
@@ -617,10 +627,10 @@ class NWIndex():
pC = 0
if sTitle is None:
- if tHandle in self._textCounts:
- cC = self._textCounts[tHandle][0]
- wC = self._textCounts[tHandle][1]
- pC = self._textCounts[tHandle][2]
+ if tHandle in self._fileMeta:
+ cC = self._fileMeta[tHandle][1]
+ wC = self._fileMeta[tHandle][2]
+ pC = self._fileMeta[tHandle][3]
else:
if tHandle in self._fileIndex:
if sTitle in self._fileIndex[tHandle]:
@@ -809,23 +819,25 @@ class NWIndex():
return
- def _checkTextCounts(self):
+ def _checkFileMeta(self):
"""Scan the text counts index for errors.
Warning: This function raises exceptions.
"""
- for tHandle in self._textCounts:
+ for tHandle in self._fileMeta:
if not isHandle(tHandle):
- raise KeyError("textCounts key is not a handle")
+ raise KeyError("fileMeta key is not a handle")
- tEntry = self._textCounts[tHandle]
- if len(tEntry) != 3:
- raise IndexError("textCounts[a] expected 3 values")
- if not isinstance(tEntry[0], int):
- raise ValueError("textCounts[a][0] is not an integer")
+ tEntry = self._fileMeta[tHandle]
+ if len(tEntry) != 4:
+ raise IndexError("fileMeta[a] expected 4 values")
+ if not tEntry[0] in H_VALID:
+ raise ValueError("fileMeta[a][0] is not a header level")
if not isinstance(tEntry[1], int):
- raise ValueError("textCounts[a][1] is not an integer")
+ raise ValueError("fileMeta[a][1] is not an integer")
if not isinstance(tEntry[2], int):
- raise ValueError("textCounts[a][2] is not an integer")
+ raise ValueError("fileMeta[a][2] is not an integer")
+ if not isinstance(tEntry[3], int):
+ raise ValueError("fileMeta[a][3] is not an integer")
return
diff --git a/nw/core/item.py b/nw/core/item.py
index 74cd833f..81f4f5ee 100644
--- a/nw/core/item.py
+++ b/nw/core/item.py
@@ -29,7 +29,7 @@ from lxml import etree
from nw.enum import nwItemType, nwItemClass, nwItemLayout
from nw.common import checkInt, isHandle, isItemClass, isItemLayout, isItemType
-from nw.constants import nwLists
+from nw.constants import nwLabels, nwLists, trConst
logger = logging.getLogger(__name__)
@@ -151,6 +151,33 @@ class NWItem():
return
+ ##
+ # Methods
+ ##
+
+ def describeMe(self, hLevel=None):
+ """Return a string description of the item.
+ """
+ descKey = "none"
+ if self.itemType == nwItemType.ROOT:
+ descKey = "root"
+ elif self.itemType == nwItemType.FOLDER:
+ descKey = "folder"
+ elif self.itemType == nwItemType.FILE:
+ if self.itemLayout == nwItemLayout.DOCUMENT:
+ if hLevel == "H1":
+ descKey = "doc_h1"
+ elif hLevel == "H2":
+ descKey = "doc_h2"
+ elif hLevel == "H3":
+ descKey = "doc_h3"
+ else:
+ descKey = "document"
+ elif self.itemLayout == nwItemLayout.NOTE:
+ descKey = "note"
+
+ return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
+
##
# Set Item Values
##
diff --git a/nw/dialogs/preferences.py b/nw/dialogs/preferences.py
index c9a24c11..7eef6ac2 100644
--- a/nw/dialogs/preferences.py
+++ b/nw/dialogs/preferences.py
@@ -243,6 +243,14 @@ class GuiPreferencesGeneral(QWidget):
# ============
self.mainForm.addGroupLabel(self.tr("GUI Settings"))
+ self.fullStatus = QSwitch()
+ self.fullStatus.setChecked(self.mainConf.fullStatus)
+ self.mainForm.addRow(
+ self.tr("Show status text in project tree"),
+ self.fullStatus,
+ self.tr("Changing this requires restarting novelWriter."),
+ )
+
self.showFullPath = QSwitch()
self.showFullPath.setChecked(self.mainConf.showFullPath)
self.mainForm.addRow(
@@ -278,6 +286,7 @@ class GuiPreferencesGeneral(QWidget):
guiDark = self.guiDark.isChecked()
guiFont = self.guiFont.text()
guiFontSize = self.guiFontSize.value()
+ fullStatus = self.fullStatus.isChecked()
# Check if restart is needed
needsRestart = False
@@ -287,6 +296,7 @@ class GuiPreferencesGeneral(QWidget):
needsRestart |= self.mainConf.guiDark != guiDark
needsRestart |= self.mainConf.guiFont != guiFont
needsRestart |= self.mainConf.guiFontSize != guiFontSize
+ needsRestart |= self.mainConf.fullStatus != fullStatus
self.mainConf.guiLang = guiLang
self.mainConf.guiTheme = guiTheme
@@ -294,6 +304,7 @@ class GuiPreferencesGeneral(QWidget):
self.mainConf.guiDark = guiDark
self.mainConf.guiFont = guiFont
self.mainConf.guiFontSize = guiFontSize
+ self.mainConf.fullStatus = fullStatus
self.mainConf.showFullPath = self.showFullPath.isChecked()
self.mainConf.hideVScroll = self.hideVScroll.isChecked()
self.mainConf.hideHScroll = self.hideHScroll.isChecked()
diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py
index dea21b8e..0b4ff215 100644
--- a/nw/gui/doceditor.py
+++ b/nw/gui/doceditor.py
@@ -51,7 +51,7 @@ from PyQt5.QtWidgets import (
from nw.core import NWDoc, NWSpellSimple, countWords
from nw.enum import nwAlert, nwDocAction, nwDocInsert, nwItemClass
from nw.common import transferCase
-from nw.constants import nwConst, trConst, nwKeyWords, nwLabels, nwUnicode
+from nw.constants import nwConst, nwKeyWords, nwUnicode
from nw.gui.dochighlight import GuiDocHighlighter
logger = logging.getLogger(__name__)
@@ -461,21 +461,19 @@ class GuiDocEditor(QTextEdit):
self.setDocumentChanged(False)
+ oldHeader = self.theIndex.getHandleHeaderLevel(tHandle)
self.theIndex.scanText(tHandle, docText)
+ newHeader = self.theIndex.getHandleHeaderLevel(tHandle)
if self._updateHeaders(checkLevel=True):
self.theParent.requestNovelTreeRefresh()
else:
self.theParent.novelView.updateWordCounts(tHandle)
- # hLevel = "H0"
- # if self._docHeaders:
- # hLevel = self._docHeaders[0][1]
-
- # if self.theProject.projTree.updateItemLayout(tHandle, hLevel):
- # self.theParent.treeView.setTreeItemValues(tHandle)
- # self._nwDocument.writeDocument(docText)
- # self.docFooter.updateInfo()
+ if oldHeader != newHeader:
+ self.theParent.treeView.setTreeItemValues(tHandle)
+ self.theParent.treeMeta.updateViewBox(tHandle)
+ self.docFooter.updateInfo()
# Update the status bar
self.theParent.setStatus(
@@ -2753,9 +2751,8 @@ class GuiDocEditFooter(QWidget):
theIcon = self.theParent.importIcons[iStatus]
sIcon = theIcon.pixmap(self.sPx, self.sPx)
- sClass = trConst(nwLabels.CLASS_NAME[self._theItem.itemClass])
- sLayout = trConst(nwLabels.LAYOUT_NAME[self._theItem.itemLayout])
- sText = f"{self._theItem.itemStatus} / {sClass} / {sLayout}"
+ hLevel = self.theParent.theIndex.getHandleHeaderLevel(self._docHandle)
+ sText = f"{self._theItem.itemStatus} / {self._theItem.describeMe(hLevel)}"
self.statusIcon.setPixmap(sIcon)
self.statusText.setText(sText)
diff --git a/nw/gui/itemdetails.py b/nw/gui/itemdetails.py
index 9a2b1d30..134f7d2c 100644
--- a/nw/gui/itemdetails.py
+++ b/nw/gui/itemdetails.py
@@ -30,7 +30,7 @@ from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QFont, QPixmap
from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel
-from nw.enum import nwItemClass, nwItemType, nwItemLayout
+from nw.enum import nwItemClass, nwItemType
from nw.constants import trConst, nwLabels
logger = logging.getLogger(__name__)
@@ -98,8 +98,7 @@ class GuiItemDetails(QWidget):
self.className.setAlignment(Qt.AlignLeft)
self.classFlag = QLabel("")
- self.classFlag.setFont(fntValue)
- self.classFlag.setAlignment(Qt.AlignRight)
+ self.classFlag.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.classData = QLabel("")
self.classData.setFont(fntValue)
@@ -111,8 +110,7 @@ class GuiItemDetails(QWidget):
self.layoutName.setAlignment(Qt.AlignLeft)
self.layoutFlag = QLabel("")
- self.layoutFlag.setFont(fntValue)
- self.layoutFlag.setAlignment(Qt.AlignRight)
+ self.layoutFlag.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.layoutData = QLabel("")
self.layoutData.setFont(fntValue)
@@ -229,18 +227,15 @@ class GuiItemDetails(QWidget):
return
self._itemHandle = tHandle
+ iPx = int(round(0.8*self.theTheme.baseIconSize))
+
+ # Label
+ # =====
+
theLabel = nwItem.itemName
if len(theLabel) > 100:
theLabel = theLabel[:96].rstrip()+" ..."
- itStatus = nwItem.itemStatus
- if nwItem.itemClass == nwItemClass.NOVEL:
- itStatus = self.theProject.statusItems.checkEntry(itStatus) # Make sure it's valid
- flagIcon = self.theParent.statusIcons[itStatus]
- else:
- itStatus = self.theProject.importItems.checkEntry(itStatus) # Make sure it's valid
- flagIcon = self.theParent.importIcons[itStatus]
-
if nwItem.itemType == nwItemType.FILE:
if nwItem.isExported:
self.labelFlag.setPixmap(self._expCheck)
@@ -249,19 +244,41 @@ class GuiItemDetails(QWidget):
else:
self.labelFlag.setPixmap(QPixmap(1, 1))
- iPx = int(round(0.8*self.theTheme.baseIconSize))
- self.statusFlag.setPixmap(flagIcon.pixmap(iPx, iPx))
- self.classFlag.setText(nwLabels.CLASS_FLAG[nwItem.itemClass]) # NO-I18N
-
- if nwItem.itemLayout == nwItemLayout.NO_LAYOUT:
- self.layoutFlag.setText("-")
- else:
- self.layoutFlag.setText(nwLabels.LAYOUT_FLAG[nwItem.itemLayout]) # NO-I18N
-
self.labelData.setText(theLabel)
+
+ # Status
+ # ======
+
+ itStatus = nwItem.itemStatus
+ if nwItem.itemClass == nwItemClass.NOVEL:
+ itStatus = self.theProject.statusItems.checkEntry(itStatus) # Make sure it's valid
+ flagIcon = self.theParent.statusIcons[itStatus]
+ else:
+ itStatus = self.theProject.importItems.checkEntry(itStatus) # Make sure it's valid
+ flagIcon = self.theParent.importIcons[itStatus]
+
+ self.statusFlag.setPixmap(flagIcon.pixmap(iPx, iPx))
self.statusData.setText(nwItem.itemStatus)
+
+ # Class
+ # =====
+
+ classIcon = self.theTheme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass])
+ self.classFlag.setPixmap(classIcon.pixmap(iPx, iPx))
self.classData.setText(trConst(nwLabels.CLASS_NAME[nwItem.itemClass]))
- self.layoutData.setText(trConst(nwLabels.LAYOUT_NAME[nwItem.itemLayout]))
+
+ # Layout
+ # ======
+
+ hLevel = self.theParent.theIndex.getHandleHeaderLevel(tHandle)
+ layoutIcon = self.theTheme.getItemIcon(
+ nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
+ )
+ self.layoutFlag.setPixmap(layoutIcon.pixmap(iPx, iPx))
+ self.layoutData.setText(nwItem.describeMe(hLevel))
+
+ # Counts
+ # ======
if nwItem.itemType == nwItemType.FILE:
self.cCountData.setText(f"{nwItem.charCount:n}")
diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py
index 3d386dc3..40f6a7a7 100644
--- a/nw/gui/projtree.py
+++ b/nw/gui/projtree.py
@@ -47,7 +47,7 @@ class GuiProjectTree(QTreeWidget):
C_NAME = 0
C_COUNT = 1
C_EXPORT = 2
- C_FLAGS = 3
+ C_STATUS = 3
novelItemChanged = pyqtSignal()
noteItemChanged = pyqtSignal()
@@ -86,10 +86,8 @@ class GuiProjectTree(QTreeWidget):
self.setIndentation(iPx)
self.setColumnCount(4)
self.setHeaderLabels([
- self.tr("Label"),
- self.tr("Words"),
- self.tr("Inc"),
- self.tr("Flags")
+ self.tr("Label"), self.tr("Words"), "",
+ self.tr("Status") if self.mainConf.fullStatus else ""
])
treeHeadItem = self.headerItem()
@@ -97,7 +95,7 @@ class GuiProjectTree(QTreeWidget):
treeHeadItem.setToolTip(self.C_NAME, self.tr("Item label"))
treeHeadItem.setToolTip(self.C_COUNT, self.tr("Word count"))
treeHeadItem.setToolTip(self.C_EXPORT, self.tr("Include in build"))
- treeHeadItem.setToolTip(self.C_FLAGS, self.tr("Status, class, and layout flags"))
+ treeHeadItem.setToolTip(self.C_STATUS, self.tr("Item status"))
# Let the last column stretch, and set the minimum size to the
# size of the icon as the default Qt font metrics approach fails
@@ -126,7 +124,7 @@ class GuiProjectTree(QTreeWidget):
self.setColumnWidth(colN, colW)
# The last column should just auto-scale
- self.resizeColumnToContents(self.C_FLAGS)
+ self.resizeColumnToContents(self.C_STATUS)
# Set custom settings
self.initTree()
@@ -605,11 +603,11 @@ class GuiProjectTree(QTreeWidget):
"""
trItem = self._getTreeItem(tHandle)
nwItem = self.theProject.projTree[tHandle]
+ if trItem is None or nwItem is None:
+ return
expIcon = QIcon()
- stFlags = nwLabels.CLASS_FLAG[nwItem.itemClass]
if nwItem.itemType == nwItemType.FILE:
- stFlags += "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout]
if nwItem.isExported:
expIcon = self.theTheme.getIcon("check")
else:
@@ -618,15 +616,25 @@ class GuiProjectTree(QTreeWidget):
iStatus = nwItem.itemStatus
if nwItem.itemClass == nwItemClass.NOVEL:
iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's valid
- flagIcon = self.theParent.statusIcons[iStatus]
+ statIcon = self.theParent.statusIcons[iStatus]
else:
iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's valid
- flagIcon = self.theParent.importIcons[iStatus]
+ statIcon = self.theParent.importIcons[iStatus]
+ hLevel = self.theIndex.getHandleHeaderLevel(tHandle)
+ itemIcon = self.theTheme.getItemIcon(
+ nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
+ )
+
+ trItem.setIcon(self.C_NAME, itemIcon)
trItem.setText(self.C_NAME, nwItem.itemName)
trItem.setIcon(self.C_EXPORT, expIcon)
- trItem.setIcon(self.C_FLAGS, flagIcon)
- trItem.setText(self.C_FLAGS, stFlags)
+ trItem.setIcon(self.C_STATUS, statIcon)
+
+ if self.mainConf.fullStatus:
+ trItem.setText(self.C_STATUS, nwItem.itemStatus)
+ else:
+ trItem.setToolTip(self.C_STATUS, nwItem.itemStatus)
return
@@ -962,18 +970,17 @@ class GuiProjectTree(QTreeWidget):
"""
tHandle = nwItem.itemHandle
pHandle = nwItem.itemParent
- tClass = nwItem.itemClass
newItem = QTreeWidgetItem([""]*4)
newItem.setText(self.C_NAME, "")
newItem.setText(self.C_COUNT, "0")
newItem.setText(self.C_EXPORT, "")
- newItem.setText(self.C_FLAGS, "")
+ newItem.setText(self.C_STATUS, "")
newItem.setTextAlignment(self.C_NAME, Qt.AlignLeft)
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
newItem.setTextAlignment(self.C_EXPORT, Qt.AlignLeft)
- newItem.setTextAlignment(self.C_FLAGS, Qt.AlignLeft)
+ newItem.setTextAlignment(self.C_STATUS, Qt.AlignLeft)
newItem.setData(self.C_NAME, Qt.UserRole, tHandle)
newItem.setData(self.C_COUNT, Qt.UserRole, 0)
@@ -1008,15 +1015,6 @@ class GuiProjectTree(QTreeWidget):
self.setTreeItemValues(tHandle)
newItem.setExpanded(nwItem.isExpanded)
- if nwItem.itemType == nwItemType.ROOT:
- newItem.setIcon(self.C_NAME, self.theTheme.getIcon(nwLabels.CLASS_ICON[tClass]))
- elif nwItem.itemType == nwItemType.FOLDER:
- newItem.setIcon(self.C_NAME, self.theTheme.getIcon("proj_folder"))
- elif nwItem.itemType == nwItemType.FILE:
- newItem.setIcon(self.C_NAME, self.theTheme.getIcon("proj_document"))
- elif nwItem.itemType == nwItemType.TRASH:
- newItem.setIcon(self.C_NAME, self.theTheme.getIcon(nwLabels.CLASS_ICON[tClass]))
-
self._setTreeChanged(True)
return newItem
diff --git a/nw/gui/theme.py b/nw/gui/theme.py
index d493aae9..5932d171 100644
--- a/nw/gui/theme.py
+++ b/nw/gui/theme.py
@@ -37,8 +37,9 @@ from PyQt5.QtGui import (
QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase, QPixmap
)
-from nw.enum import nwAlert
+from nw.enum import nwAlert, nwItemLayout, nwItemType
from nw.common import NWConfigParser
+from nw.constants import nwLabels
logger = logging.getLogger(__name__)
@@ -126,8 +127,10 @@ class GuiTheme:
self.updateTheme()
self.theIcons.updateTheme()
+ # Icon Functions
self.getIcon = self.theIcons.getIcon
self.getPixmap = self.theIcons.getPixmap
+ self.getItemIcon = self.theIcons.getItemIcon
self.loadDecoration = self.theIcons.loadDecoration
# Extract Other Info
@@ -533,6 +536,10 @@ class GuiIcons:
"cls_archive": (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_trash": (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"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),
@@ -721,6 +728,34 @@ class GuiIcons:
qIcon = self.getIcon(iconKey)
return qIcon.pixmap(iconSize[0], iconSize[1], QIcon.Normal)
+ def getItemIcon(self, tType, tClass, tLayout, hLevel="H0"):
+ """Get the correct icon for a project item based on type, class
+ and header level
+ """
+ iconName = None
+ if tType == nwItemType.ROOT:
+ iconName = nwLabels.CLASS_ICON[tClass]
+ elif tType == nwItemType.FOLDER:
+ iconName = "proj_folder"
+ elif tType == nwItemType.FILE:
+ iconName = "proj_document"
+ if tLayout == nwItemLayout.DOCUMENT:
+ if hLevel == "H1":
+ iconName = "proj_title"
+ elif hLevel == "H2":
+ iconName = "proj_chapter"
+ elif hLevel == "H3":
+ iconName = "proj_scene"
+ elif tLayout == nwItemLayout.NOTE:
+ iconName = "proj_note"
+ elif tType == nwItemType.TRASH:
+ iconName = nwLabels.CLASS_ICON[tClass]
+
+ if iconName is None:
+ return QIcon()
+
+ return self.getIcon(iconName)
+
def listThemes(self):
"""Scan the icons themes folder and list all themes.
"""
diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx
index 54bd9f17..1cfb4a20 100644
--- a/sample/nwProject.nwx
+++ b/sample/nwProject.nwx
@@ -1,13 +1,13 @@
-
+
Sample Project
Sample Project
Jane Smith
Jay Doh
- 1141
- 195
- 54687
+ 1195
+ 196
+ 57776
False
@@ -142,7 +142,7 @@
New
True
DOCUMENT
- 618
+ 617
101
3
4
diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf
index 1f8ba2d7..4f8b7807 100644
--- a/tests/reference/baseConfig_novelwriter.conf
+++ b/tests/reference/baseConfig_novelwriter.conf
@@ -7,7 +7,10 @@ guidark = False
guifont =
guifontsize = 11
lastnotes = 0x0
-guilang = en-GB
+guilang = en_GB
+hidevscroll = False
+hidehscroll = False
+fullstatus = True
[Sizes]
geometry = 1200, 650
@@ -20,8 +23,6 @@ docpane = 400, 400
viewpane = 500, 150
outlinepane = 500, 150
fullscreen = False
-hidevscroll = False
-hidehscroll = False
[Project]
autosaveproject = 60
diff --git a/tests/reference/coreIndex_LoadSave_tagsIndex.json b/tests/reference/coreIndex_LoadSave_tagsIndex.json
index d1745d20..fb4d9acd 100644
--- a/tests/reference/coreIndex_LoadSave_tagsIndex.json
+++ b/tests/reference/coreIndex_LoadSave_tagsIndex.json
@@ -79,21 +79,21 @@
"T000001": {"level": "H1", "title": "Ancient Europe", "layout": "NOTE", "cCount": 1770, "wCount": 259, "pCount": 3, "synopsis": ""}
}
},
-"textCounts": {
- "7a992350f3eb6": [230, 40, 3],
- "8c58a65414c23": [1058, 176, 2],
- "88d59a277361b": [584, 92, 1],
- "db7e733775d4d": [35, 6, 1],
- "fb609cd8319dc": [419, 67, 1],
- "88243afbe5ed8": [2758, 404, 4],
- "f96ec11c6a3da": [4043, 600, 6],
- "846352075de7d": [631, 109, 3],
- "441420a886d82": [477, 70, 1],
- "eb103bc70c90c": [3006, 439, 4],
- "f8c0562e50f1b": [3839, 563, 6],
- "47666c91c7ccf": [3644, 543, 5],
- "4c4f28287af27": [1864, 284, 3],
- "2426c6f0ca922": [1369, 195, 2],
- "04468803b92e1": [1770, 259, 3]
+"fileMeta": {
+ "7a992350f3eb6": ["H1", 230, 40, 3],
+ "8c58a65414c23": ["H0", 1058, 176, 2],
+ "88d59a277361b": ["H2", 584, 92, 1],
+ "db7e733775d4d": ["H1", 35, 6, 1],
+ "fb609cd8319dc": ["H2", 419, 67, 1],
+ "88243afbe5ed8": ["H3", 2758, 404, 4],
+ "f96ec11c6a3da": ["H3", 4043, 600, 6],
+ "846352075de7d": ["H2", 631, 109, 3],
+ "441420a886d82": ["H2", 477, 70, 1],
+ "eb103bc70c90c": ["H3", 3006, 439, 4],
+ "f8c0562e50f1b": ["H3", 3839, 563, 6],
+ "47666c91c7ccf": ["H3", 3644, 543, 5],
+ "4c4f28287af27": ["H1", 1864, 284, 3],
+ "2426c6f0ca922": ["H1", 1369, 195, 2],
+ "04468803b92e1": ["H1", 1770, 259, 3]
}
}
diff --git a/tests/reference/guiPreferences_novelwriter.conf b/tests/reference/guiPreferences_novelwriter.conf
index 1be38593..8ce23589 100644
--- a/tests/reference/guiPreferences_novelwriter.conf
+++ b/tests/reference/guiPreferences_novelwriter.conf
@@ -7,7 +7,10 @@ guidark = True
guifont = Sans
guifontsize = 12
lastnotes = 0x0
-guilang = en-GB
+guilang = en_GB
+hidevscroll = True
+hidehscroll = True
+fullstatus = True
[Sizes]
geometry = 1200, 650
@@ -20,8 +23,6 @@ docpane = 400, 400
viewpane = 500, 150
outlinepane = 500, 150
fullscreen = False
-hidevscroll = True
-hidehscroll = True
[Project]
autosaveproject = 40
diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py
index 95f410b6..41a66230 100644
--- a/tests/test_core/test_core_index.py
+++ b/tests/test_core/test_core_index.py
@@ -72,25 +72,25 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir):
tagIndex = str(theIndex._tagIndex)
refIndex = str(theIndex._refIndex)
fileIndex = str(theIndex._fileIndex)
- textCounts = str(theIndex._textCounts)
+ textCounts = str(theIndex._fileMeta)
# Delete a handle
assert theIndex._tagIndex.get("Bod", None) is not None
assert theIndex._refIndex.get("4c4f28287af27", None) is not None
assert theIndex._fileIndex.get("4c4f28287af27", None) is not None
- assert theIndex._textCounts.get("4c4f28287af27", None) is not None
+ assert theIndex._fileMeta.get("4c4f28287af27", None) is not None
theIndex.deleteHandle("4c4f28287af27")
assert theIndex._tagIndex.get("Bod", None) is None
assert theIndex._refIndex.get("4c4f28287af27", None) is None
assert theIndex._fileIndex.get("4c4f28287af27", None) is None
- assert theIndex._textCounts.get("4c4f28287af27", None) is None
+ assert theIndex._fileMeta.get("4c4f28287af27", None) is None
# Clear the index
theIndex.clearIndex()
assert theIndex._tagIndex == {}
assert theIndex._refIndex == {}
assert theIndex._fileIndex == {}
- assert theIndex._textCounts == {}
+ assert theIndex._fileMeta == {}
# Make the load fail
with monkeypatch.context() as mp:
@@ -103,7 +103,7 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir):
assert str(theIndex._tagIndex) == tagIndex
assert str(theIndex._refIndex) == refIndex
assert str(theIndex._fileIndex) == fileIndex
- assert str(theIndex._textCounts) == textCounts
+ assert str(theIndex._fileMeta) == textCounts
# Break the index and check that we notice
assert theIndex.indexBroken is False
@@ -1162,58 +1162,66 @@ def testCoreIndex_CheckFileIndex(mockGUI):
@pytest.mark.core
-def testCoreIndex_CheckTextCounts(mockGUI):
- """Test the text counts checker.
+def testCoreIndex_CheckFileMeta(mockGUI):
+ """Test the file meta checker.
"""
theProject = NWProject(mockGUI)
theIndex = NWIndex(theProject)
# Valid Index
- theIndex._textCounts = {
- "53b69b83cdafc": [72, 15, 2],
- "974e400180a99": [210, 40, 2],
+ theIndex._fileMeta = {
+ "53b69b83cdafc": ["H0", 72, 15, 2],
+ "974e400180a99": ["H0", 210, 40, 2],
}
- assert theIndex._checkTextCounts() is None
+ assert theIndex._checkFileMeta() is None
# Invalid Handle
- theIndex._textCounts = {
- "53b69b83cdafc": [72, 15, 2],
- "h74e400180a99": [210, 40, 2],
+ theIndex._fileMeta = {
+ "53b69b83cdafc": ["H0", 72, 15, 2],
+ "h74e400180a99": ["H0", 210, 40, 2],
}
with pytest.raises(KeyError):
- theIndex._checkTextCounts()
+ theIndex._checkFileMeta()
# Wrong Length
- theIndex._textCounts = {
- "53b69b83cdafc": [72, 15, 2],
- "974e400180a99": [210, 40, 2, 8],
+ theIndex._fileMeta = {
+ "53b69b83cdafc": ["H0", 72, 15, 2],
+ "974e400180a99": ["H0", 210, 40, 2, 8],
}
with pytest.raises(IndexError):
- theIndex._checkTextCounts()
+ theIndex._checkFileMeta()
- # Type of Entry 0
- theIndex._textCounts = {
- "53b69b83cdafc": [72, 15, 2],
- "974e400180a99": ["210", 40, 2],
+ # Content of Entry 0
+ theIndex._fileMeta = {
+ "53b69b83cdafc": ["H0", 72, 15, 2],
+ "974e400180a99": ["XXX", 210, 40, 2],
}
with pytest.raises(ValueError):
- theIndex._checkTextCounts()
+ theIndex._checkFileMeta()
# Type of Entry 1
- theIndex._textCounts = {
- "53b69b83cdafc": [72, 15, 2],
- "974e400180a99": [210, "40", 2],
+ theIndex._fileMeta = {
+ "53b69b83cdafc": ["H0", 72, 15, 2],
+ "974e400180a99": ["H0", "210", 40, 2],
}
with pytest.raises(ValueError):
- theIndex._checkTextCounts()
+ theIndex._checkFileMeta()
# Type of Entry 2
- theIndex._textCounts = {
- "53b69b83cdafc": [72, 15, 2],
- "974e400180a99": [210, 40, "2"],
+ theIndex._fileMeta = {
+ "53b69b83cdafc": ["H0", 72, 15, 2],
+ "974e400180a99": ["H0", 210, "40", 2],
}
with pytest.raises(ValueError):
- theIndex._checkTextCounts()
+ theIndex._checkFileMeta()
+
+ # Type of Entry 3
+ theIndex._fileMeta = {
+ "53b69b83cdafc": ["H0", 72, 15, 2],
+ "974e400180a99": ["H0", 210, 40, "2"],
+ }
+ with pytest.raises(ValueError):
+ theIndex._checkFileMeta()
# END Test testCoreIndex_CheckTextCounts
diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py
index 8b10c109..fb12e719 100644
--- a/tests/test_core/test_core_item.py
+++ b/tests/test_core/test_core_item.py
@@ -166,6 +166,39 @@ def testCoreItem_Setters(mockGUI):
# END Test testCoreItem_Setters
+@pytest.mark.core
+def testCoreItem_Methods(mockGUI):
+ """Test the simple methods of the NWItem class.
+ """
+ theProject = NWProject(mockGUI)
+ theItem = NWItem(theProject)
+
+ # Describe Me
+ # ===========
+
+ assert theItem.describeMe() == "None"
+
+ theItem.setType("ROOT")
+ assert theItem.describeMe() == "Root Folder"
+
+ theItem.setType("FOLDER")
+ assert theItem.describeMe() == "Folder"
+
+ theItem.setType("FILE")
+ theItem.setLayout("DOCUMENT")
+ assert theItem.describeMe() == "Novel Document"
+ assert theItem.describeMe("H0") == "Novel Document"
+ assert theItem.describeMe("H1") == "Novel Title Page"
+ assert theItem.describeMe("H2") == "Novel Chapter"
+ assert theItem.describeMe("H3") == "Novel Scene"
+ assert theItem.describeMe("H4") == "Novel Document"
+
+ theItem.setLayout("NOTE")
+ assert theItem.describeMe() == "Project Note"
+
+# END Test testCoreItem_Methods
+
+
@pytest.mark.core
def testCoreItem_TypeSetter(mockGUI):
"""Test the setter for all the nwItemType values for the NWItem
diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py
index ee30a0bc..186472dd 100644
--- a/tests/test_dialogs/test_dlg_preferences.py
+++ b/tests/test_dialogs/test_dlg_preferences.py
@@ -248,7 +248,7 @@ 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, 9, 10, 13, 14, 15, 16, 17, 18, 19, 20, 7, 30, 31]
+ ignoreLines = [2, 7, 9, 10, 16, 17, 18, 19, 20, 21, 22, 23, 24, 32, 33]
assert cmpFiles(testFile, compFile, ignoreLines)
# Clean up