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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + 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