novelWriter 2.6.2__py3-none-any.whl → 2.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- novelwriter/__init__.py +98 -75
- novelwriter/assets/i18n/nw_cs_CZ.qm +0 -0
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_ru_RU.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_de_DE.json +3 -1
- novelwriter/assets/i18n/project_en_GB.json +2 -0
- novelwriter/assets/i18n/project_en_US.json +2 -0
- novelwriter/assets/i18n/project_it_IT.json +2 -0
- novelwriter/assets/i18n/project_ja_JP.json +2 -0
- novelwriter/assets/i18n/project_nb_NO.json +2 -0
- novelwriter/assets/i18n/project_nn_NO.json +5 -0
- novelwriter/assets/i18n/project_pl_PL.json +2 -0
- novelwriter/assets/i18n/project_pt_BR.json +2 -0
- novelwriter/assets/i18n/project_ru_RU.json +2 -0
- novelwriter/assets/i18n/project_zh_CN.json +2 -0
- novelwriter/assets/icons/font_awesome.icons +109 -0
- novelwriter/assets/icons/material_filled_bold.icons +109 -0
- novelwriter/assets/icons/material_filled_normal.icons +109 -0
- novelwriter/assets/icons/material_filled_thin.icons +109 -0
- novelwriter/assets/icons/material_rounded_bold.icons +109 -0
- novelwriter/assets/icons/material_rounded_normal.icons +109 -0
- novelwriter/assets/icons/material_rounded_thin.icons +109 -0
- novelwriter/assets/icons/remix_filled.icons +109 -0
- novelwriter/assets/icons/remix_outline.icons +109 -0
- novelwriter/assets/images/splash.png +0 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/{manual_fr_FR.pdf → manual_fr.pdf} +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +1 -1
- novelwriter/assets/syntax/snazzy.conf +3 -3
- novelwriter/assets/text/credits_en.htm +12 -6
- novelwriter/assets/themes/cyberpunk_night.conf +23 -7
- novelwriter/assets/themes/default_dark.conf +20 -4
- novelwriter/assets/themes/default_light.conf +21 -5
- novelwriter/assets/themes/dracula.conf +20 -4
- novelwriter/assets/themes/snazzy.conf +48 -0
- novelwriter/assets/themes/solarized_dark.conf +24 -8
- novelwriter/assets/themes/solarized_light.conf +22 -6
- novelwriter/common.py +43 -27
- novelwriter/config.py +201 -139
- novelwriter/constants.py +92 -56
- novelwriter/core/buildsettings.py +26 -17
- novelwriter/core/coretools.py +52 -41
- novelwriter/core/docbuild.py +20 -13
- novelwriter/core/document.py +2 -2
- novelwriter/core/index.py +166 -432
- novelwriter/core/indexdata.py +406 -0
- novelwriter/core/item.py +50 -32
- novelwriter/core/itemmodel.py +43 -38
- novelwriter/core/novelmodel.py +225 -0
- novelwriter/core/options.py +1 -1
- novelwriter/core/project.py +24 -25
- novelwriter/core/projectdata.py +47 -29
- novelwriter/core/projectxml.py +18 -8
- novelwriter/core/sessions.py +32 -15
- novelwriter/core/spellcheck.py +4 -3
- novelwriter/core/status.py +12 -15
- novelwriter/core/storage.py +1 -1
- novelwriter/core/tree.py +55 -13
- novelwriter/dialogs/about.py +19 -22
- novelwriter/dialogs/docmerge.py +23 -24
- novelwriter/dialogs/docsplit.py +26 -26
- novelwriter/dialogs/editlabel.py +19 -20
- novelwriter/dialogs/preferences.py +143 -69
- novelwriter/dialogs/projectsettings.py +51 -54
- novelwriter/dialogs/quotes.py +14 -19
- novelwriter/dialogs/wordlist.py +25 -30
- novelwriter/enum.py +8 -0
- novelwriter/error.py +16 -16
- novelwriter/extensions/configlayout.py +24 -20
- novelwriter/extensions/eventfilters.py +9 -5
- novelwriter/extensions/modified.py +34 -15
- novelwriter/extensions/novelselector.py +18 -5
- novelwriter/extensions/pagedsidebar.py +39 -49
- novelwriter/extensions/progressbars.py +10 -8
- novelwriter/extensions/statusled.py +6 -13
- novelwriter/extensions/switch.py +31 -41
- novelwriter/extensions/switchbox.py +8 -3
- novelwriter/extensions/versioninfo.py +4 -4
- novelwriter/formats/shared.py +1 -1
- novelwriter/formats/todocx.py +14 -10
- novelwriter/formats/tohtml.py +7 -5
- novelwriter/formats/tokenizer.py +37 -33
- novelwriter/formats/tomarkdown.py +6 -2
- novelwriter/formats/toodt.py +27 -22
- novelwriter/formats/toqdoc.py +19 -14
- novelwriter/formats/toraw.py +6 -2
- novelwriter/gui/doceditor.py +314 -305
- novelwriter/gui/dochighlight.py +46 -45
- novelwriter/gui/docviewer.py +102 -104
- novelwriter/gui/docviewerpanel.py +47 -51
- novelwriter/gui/editordocument.py +8 -5
- novelwriter/gui/itemdetails.py +15 -18
- novelwriter/gui/mainmenu.py +147 -146
- novelwriter/gui/noveltree.py +246 -406
- novelwriter/gui/outline.py +145 -76
- novelwriter/gui/projtree.py +139 -132
- novelwriter/gui/search.py +34 -31
- novelwriter/gui/sidebar.py +13 -18
- novelwriter/gui/statusbar.py +27 -21
- novelwriter/gui/theme.py +554 -436
- novelwriter/guimain.py +56 -38
- novelwriter/shared.py +32 -23
- novelwriter/splash.py +74 -0
- novelwriter/text/comments.py +70 -0
- novelwriter/text/patterns.py +4 -4
- novelwriter/tools/dictionaries.py +20 -29
- novelwriter/tools/lipsum.py +18 -18
- novelwriter/tools/manusbuild.py +39 -42
- novelwriter/tools/manuscript.py +114 -127
- novelwriter/tools/manussettings.py +129 -102
- novelwriter/tools/noveldetails.py +60 -72
- novelwriter/tools/welcome.py +57 -75
- novelwriter/tools/writingstats.py +112 -102
- novelwriter/types.py +5 -7
- {novelWriter-2.6.2.dist-info → novelwriter-2.7.dist-info}/METADATA +6 -6
- novelwriter-2.7.dist-info/RECORD +162 -0
- {novelWriter-2.6.2.dist-info → novelwriter-2.7.dist-info}/WHEEL +1 -1
- novelWriter-2.6.2.dist-info/RECORD +0 -363
- novelwriter/assets/icons/typicons_dark/README.md +0 -29
- novelwriter/assets/icons/typicons_dark/icons.conf +0 -134
- novelwriter/assets/icons/typicons_dark/mixed_copy.svg +0 -4
- novelwriter/assets/icons/typicons_dark/mixed_document-chapter.svg +0 -12
- novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_document-note.svg +0 -12
- novelwriter/assets/icons/typicons_dark/mixed_document-scene.svg +0 -12
- novelwriter/assets/icons/typicons_dark/mixed_document-section.svg +0 -12
- novelwriter/assets/icons/typicons_dark/mixed_document-title.svg +0 -12
- novelwriter/assets/icons/typicons_dark/mixed_edit.svg +0 -4
- novelwriter/assets/icons/typicons_dark/mixed_import.svg +0 -5
- novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg +0 -5
- novelwriter/assets/icons/typicons_dark/mixed_input-none.svg +0 -5
- novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg +0 -5
- novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_search-replace.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +0 -6
- novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +0 -6
- novelwriter/assets/icons/typicons_dark/nw_deco-h0.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h1.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h2.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h3.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-h4.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_deco-noveltree-more.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_font.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_panel.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_quote.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_search-case.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_search-preserve.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_search-regex.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_search-word.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +0 -6
- novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +0 -6
- novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +0 -7
- novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +0 -4
- novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +0 -6
- novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +0 -7
- novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +0 -7
- novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +0 -7
- novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +0 -5
- novelwriter/assets/icons/typicons_dark/typ_arrow-down-thick-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_arrow-forward.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_arrow-maximise.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_arrow-minimise.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_arrow-repeat-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_book-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_book.svg +0 -6
- novelwriter/assets/icons/typicons_dark/typ_bookmark.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_calendar.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_cancel-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_cancel.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_chart-bar-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_chevron-left.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_chevron-right.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_cog.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_delete-full.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_delete.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_directions-full.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_document-add.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_document-text.svg +0 -8
- novelwriter/assets/icons/typicons_dark/typ_document.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_export-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_export.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_eye.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_flag.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_folder-open.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_folder.svg +0 -5
- novelwriter/assets/icons/typicons_dark/typ_globe-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_key.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_location.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_media-pause-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_media-record-outline.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_media-record.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_minus.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_pencil.svg +0 -5
- novelwriter/assets/icons/typicons_dark/typ_pin-outline.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_pin.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_plus.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_puzzle-outline.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_puzzle.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_refresh.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_search.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_star.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_stopwatch-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_th-list-grey.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_th-list.svg +0 -9
- novelwriter/assets/icons/typicons_dark/typ_times.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_trash.svg +0 -5
- novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_user.svg +0 -5
- novelwriter/assets/icons/typicons_dark/typ_warning-full.svg +0 -4
- novelwriter/assets/icons/typicons_light/README.md +0 -29
- novelwriter/assets/icons/typicons_light/icons.conf +0 -134
- novelwriter/assets/icons/typicons_light/mixed_copy.svg +0 -4
- novelwriter/assets/icons/typicons_light/mixed_document-chapter.svg +0 -12
- novelwriter/assets/icons/typicons_light/mixed_document-new.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_document-note.svg +0 -12
- novelwriter/assets/icons/typicons_light/mixed_document-scene.svg +0 -12
- novelwriter/assets/icons/typicons_light/mixed_document-section.svg +0 -12
- novelwriter/assets/icons/typicons_light/mixed_document-title.svg +0 -12
- novelwriter/assets/icons/typicons_light/mixed_edit.svg +0 -4
- novelwriter/assets/icons/typicons_light/mixed_import.svg +0 -5
- novelwriter/assets/icons/typicons_light/mixed_input-checked.svg +0 -5
- novelwriter/assets/icons/typicons_light/mixed_input-none.svg +0 -5
- novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg +0 -5
- novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_search-replace.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_size-height.svg +0 -6
- novelwriter/assets/icons/typicons_light/mixed_size-width.svg +0 -6
- novelwriter/assets/icons/typicons_light/nw_deco-h0.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h1.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h2.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h3.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-h4.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-note.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_deco-noveltree-more.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_font.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_panel.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_quote.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_search-case.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_search-preserve.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_search-regex.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_search-word.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +0 -6
- novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +0 -6
- novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +0 -7
- novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +0 -6
- novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +0 -7
- novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +0 -7
- novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +0 -7
- novelwriter/assets/icons/typicons_light/nw_toolbar.svg +0 -5
- novelwriter/assets/icons/typicons_light/typ_arrow-down-thick-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_arrow-forward.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_arrow-maximise.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_arrow-minimise.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_arrow-repeat-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_book-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_book.svg +0 -6
- novelwriter/assets/icons/typicons_light/typ_bookmark.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_calendar.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_cancel-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_cancel.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_chart-bar-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_chevron-down.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_chevron-left.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_chevron-right.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_chevron-up.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_cog.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_delete-full.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_delete.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_directions-full.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_document-add.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_document-text.svg +0 -5
- novelwriter/assets/icons/typicons_light/typ_document.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_export-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_export.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_eye.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_flag.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_folder-open.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_folder.svg +0 -5
- novelwriter/assets/icons/typicons_light/typ_globe-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_key.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_location.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_media-pause-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_media-record-outline.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_media-record.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_minus.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_pencil.svg +0 -5
- novelwriter/assets/icons/typicons_light/typ_pin-outline.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_pin.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_plus.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_puzzle-outline.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_puzzle.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_refresh.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_search-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_search.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_star.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_stopwatch-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_th-list-grey.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_th-list.svg +0 -9
- novelwriter/assets/icons/typicons_light/typ_times.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_trash.svg +0 -5
- novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_user.svg +0 -5
- novelwriter/assets/icons/typicons_light/typ_warning-full.svg +0 -4
- {novelWriter-2.6.2.dist-info → novelwriter-2.7.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.6.2.dist-info → novelwriter-2.7.dist-info/licenses}/LICENSE.md +0 -0
- {novelWriter-2.6.2.dist-info → novelwriter-2.7.dist-info}/top_level.txt +0 -0
novelwriter/gui/noveltree.py
CHANGED
@@ -3,9 +3,11 @@ novelWriter – GUI Novel Tree
|
|
3
3
|
============================
|
4
4
|
|
5
5
|
File History:
|
6
|
-
Created:
|
7
|
-
Created:
|
8
|
-
Created:
|
6
|
+
Created: 2020-12-20 [1.1rc1] GuiNovelTree
|
7
|
+
Created: 2022-06-12 [2.0rc1] GuiNovelView
|
8
|
+
Created: 2022-06-12 [2.0rc1] GuiNovelToolBar
|
9
|
+
Rewritten: 2025-02-22 [2.7b1] GuiNovelView
|
10
|
+
Rewritten: 2025-02-22 [2.7b1] GuiNovelToolBar
|
9
11
|
|
10
12
|
This file is a part of novelWriter
|
11
13
|
Copyright (C) 2020 Veronica Berglyd Olsen and novelWriter contributors
|
@@ -28,40 +30,30 @@ from __future__ import annotations
|
|
28
30
|
import logging
|
29
31
|
|
30
32
|
from enum import Enum
|
31
|
-
from time import time
|
32
33
|
|
33
|
-
from
|
34
|
-
from
|
35
|
-
from
|
36
|
-
QAbstractItemView,
|
37
|
-
|
34
|
+
from PyQt6.QtCore import QModelIndex, QPoint, pyqtSignal, pyqtSlot
|
35
|
+
from PyQt6.QtGui import QActionGroup, QFont, QPainter, QPalette, QResizeEvent
|
36
|
+
from PyQt6.QtWidgets import (
|
37
|
+
QAbstractItemView, QFrame, QHBoxLayout, QInputDialog, QMenu,
|
38
|
+
QStyleOptionViewItem, QToolTip, QVBoxLayout, QWidget
|
38
39
|
)
|
39
40
|
|
40
41
|
from novelwriter import CONFIG, SHARED
|
41
|
-
from novelwriter.common import minmax, qtLambda
|
42
|
-
from novelwriter.constants import nwKeyWords, nwLabels,
|
43
|
-
from novelwriter.core.
|
44
|
-
from novelwriter.enum import nwChange, nwDocMode,
|
45
|
-
from novelwriter.extensions.modified import NIconToolButton
|
42
|
+
from novelwriter.common import minmax, qtAddAction, qtAddMenu, qtLambda
|
43
|
+
from novelwriter.constants import nwKeyWords, nwLabels, trConst
|
44
|
+
from novelwriter.core.novelmodel import NovelModel
|
45
|
+
from novelwriter.enum import nwChange, nwDocMode, nwNovelExtra, nwOutline
|
46
|
+
from novelwriter.extensions.modified import NIconToolButton, NTreeView
|
46
47
|
from novelwriter.extensions.novelselector import NovelSelector
|
47
48
|
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
48
49
|
from novelwriter.types import (
|
49
|
-
|
50
|
-
|
51
|
-
QtSizeExpanding, QtUserRole
|
50
|
+
QtHeaderStretch, QtHeaderToContents, QtScrollAlwaysOff, QtScrollAsNeeded,
|
51
|
+
QtSizeExpanding
|
52
52
|
)
|
53
53
|
|
54
54
|
logger = logging.getLogger(__name__)
|
55
55
|
|
56
56
|
|
57
|
-
class NovelTreeColumn(Enum):
|
58
|
-
|
59
|
-
HIDDEN = 0
|
60
|
-
POV = 1
|
61
|
-
FOCUS = 2
|
62
|
-
PLOT = 3
|
63
|
-
|
64
|
-
|
65
57
|
class GuiNovelView(QWidget):
|
66
58
|
|
67
59
|
# Signals for user interaction with the novel tree
|
@@ -86,7 +78,9 @@ class GuiNovelView(QWidget):
|
|
86
78
|
self.setLayout(self.outerBox)
|
87
79
|
|
88
80
|
# Function Mappings
|
81
|
+
self.setActive = self.novelBar.setActive
|
89
82
|
self.getSelectedHandle = self.novelTree.getSelectedHandle
|
83
|
+
self.refreshCurrentTree = self.novelBar.forceRefreshNovelTree
|
90
84
|
|
91
85
|
return
|
92
86
|
|
@@ -97,8 +91,6 @@ class GuiNovelView(QWidget):
|
|
97
91
|
def updateTheme(self) -> None:
|
98
92
|
"""Update theme elements."""
|
99
93
|
self.novelBar.updateTheme()
|
100
|
-
self.novelTree.updateTheme()
|
101
|
-
self.refreshTree()
|
102
94
|
return
|
103
95
|
|
104
96
|
def initSettings(self) -> None:
|
@@ -108,21 +100,18 @@ class GuiNovelView(QWidget):
|
|
108
100
|
|
109
101
|
def clearNovelView(self) -> None:
|
110
102
|
"""Clear project-related GUI content."""
|
111
|
-
self.novelTree.clearContent()
|
112
103
|
self.novelBar.clearContent()
|
113
104
|
self.novelBar.setEnabled(False)
|
105
|
+
self.novelTree.clearContent()
|
114
106
|
return
|
115
107
|
|
116
108
|
def openProjectTasks(self) -> None:
|
117
109
|
"""Run open project tasks."""
|
118
|
-
lastNovel = SHARED.project.data.getLastHandle("
|
119
|
-
if lastNovel and lastNovel not in SHARED.project.tree:
|
120
|
-
lastNovel = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
|
121
|
-
|
110
|
+
lastNovel = SHARED.project.data.getLastHandle("novel")
|
122
111
|
logger.debug("Setting novel tree to root item '%s'", lastNovel)
|
123
112
|
|
124
113
|
lastCol = SHARED.project.options.getEnum(
|
125
|
-
"GuiNovelView", "lastCol",
|
114
|
+
"GuiNovelView", "lastCol", nwNovelExtra, nwNovelExtra.HIDDEN
|
126
115
|
)
|
127
116
|
lastColSize = SHARED.project.options.getInt(
|
128
117
|
"GuiNovelView", "lastColSize", 25
|
@@ -140,13 +129,17 @@ class GuiNovelView(QWidget):
|
|
140
129
|
|
141
130
|
def closeProjectTasks(self) -> None:
|
142
131
|
"""Run closing project tasks."""
|
132
|
+
logger.debug("Saving State: GuiNovelView")
|
133
|
+
|
143
134
|
lastColType = self.novelTree.lastColType
|
144
135
|
lastColSize = self.novelTree.lastColSize
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
136
|
+
|
137
|
+
options = SHARED.project.options
|
138
|
+
options.setValue("GuiNovelView", "lastCol", lastColType)
|
139
|
+
options.setValue("GuiNovelView", "lastColSize", lastColSize)
|
140
|
+
|
149
141
|
self.clearNovelView()
|
142
|
+
|
150
143
|
return
|
151
144
|
|
152
145
|
def setTreeFocus(self) -> None:
|
@@ -162,32 +155,24 @@ class GuiNovelView(QWidget):
|
|
162
155
|
# Public Slots
|
163
156
|
##
|
164
157
|
|
158
|
+
@pyqtSlot(str)
|
159
|
+
def setCurrentNovel(self, rootHandle: str | None) -> None:
|
160
|
+
"""Set the current novel to display."""
|
161
|
+
self.novelTree.setNovelModel(rootHandle)
|
162
|
+
return
|
163
|
+
|
165
164
|
@pyqtSlot(str)
|
166
165
|
def setActiveHandle(self, tHandle: str) -> None:
|
167
166
|
"""Highlight the rows associated with a given handle."""
|
168
167
|
self.novelTree.setActiveHandle(tHandle)
|
169
168
|
return
|
170
169
|
|
171
|
-
@pyqtSlot()
|
172
|
-
def refreshTree(self) -> None:
|
173
|
-
"""Refresh the current tree."""
|
174
|
-
self.novelTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("novelTree"))
|
175
|
-
return
|
176
|
-
|
177
170
|
@pyqtSlot(str, Enum)
|
178
171
|
def updateRootItem(self, tHandle: str, change: nwChange) -> None:
|
179
172
|
"""If any root item changes, rebuild the novel root menu."""
|
180
173
|
self.novelBar.buildNovelRootMenu()
|
181
174
|
return
|
182
175
|
|
183
|
-
@pyqtSlot(str)
|
184
|
-
def updateNovelItemMeta(self, tHandle: str) -> None:
|
185
|
-
"""The meta data of a novel item has changed, and the tree item
|
186
|
-
needs to be refreshed.
|
187
|
-
"""
|
188
|
-
self.novelTree.refreshHandle(tHandle)
|
189
|
-
return
|
190
|
-
|
191
176
|
|
192
177
|
class GuiNovelToolBar(QWidget):
|
193
178
|
|
@@ -198,10 +183,13 @@ class GuiNovelToolBar(QWidget):
|
|
198
183
|
|
199
184
|
self.novelView = novelView
|
200
185
|
|
186
|
+
self._active = False
|
187
|
+
self._refresh: dict[str, bool] = {}
|
188
|
+
|
201
189
|
iSz = SHARED.theme.baseIconSize
|
202
|
-
mPx = CONFIG.pxInt(2)
|
203
190
|
|
204
191
|
self.setContentsMargins(0, 0, 0, 0)
|
192
|
+
self.setBackgroundRole(QPalette.ColorRole.Base)
|
205
193
|
self.setAutoFillBackground(True)
|
206
194
|
|
207
195
|
# Novel Selector
|
@@ -211,7 +199,7 @@ class GuiNovelToolBar(QWidget):
|
|
211
199
|
self.novelValue = NovelSelector(self)
|
212
200
|
self.novelValue.setFont(selFont)
|
213
201
|
self.novelValue.setListFormat(self.tr("Outline of {0}"))
|
214
|
-
self.novelValue.setMinimumWidth(
|
202
|
+
self.novelValue.setMinimumWidth(150)
|
215
203
|
self.novelValue.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
|
216
204
|
self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot)
|
217
205
|
|
@@ -222,21 +210,21 @@ class GuiNovelToolBar(QWidget):
|
|
222
210
|
# Refresh Button
|
223
211
|
self.tbRefresh = NIconToolButton(self, iSz)
|
224
212
|
self.tbRefresh.setToolTip(self.tr("Refresh"))
|
225
|
-
self.tbRefresh.clicked.connect(self.
|
213
|
+
self.tbRefresh.clicked.connect(self.forceRefreshNovelTree)
|
226
214
|
|
227
215
|
# More Options Menu
|
228
216
|
self.mMore = QMenu(self)
|
229
217
|
|
230
|
-
self.mLastCol = self.mMore
|
218
|
+
self.mLastCol = qtAddMenu(self.mMore, self.tr("Last Column"))
|
231
219
|
self.gLastCol = QActionGroup(self.mMore)
|
232
220
|
self.aLastCol = {}
|
233
|
-
self._addLastColAction(
|
234
|
-
self._addLastColAction(
|
235
|
-
self._addLastColAction(
|
236
|
-
self._addLastColAction(
|
221
|
+
self._addLastColAction(nwNovelExtra.HIDDEN, self.tr("Hidden"))
|
222
|
+
self._addLastColAction(nwNovelExtra.POV, self.tr("Point of View Character"))
|
223
|
+
self._addLastColAction(nwNovelExtra.FOCUS, self.tr("Focus Character"))
|
224
|
+
self._addLastColAction(nwNovelExtra.PLOT, self.tr("Novel Plot"))
|
237
225
|
|
238
226
|
self.mLastCol.addSeparator()
|
239
|
-
self.aLastColSize = self.mLastCol
|
227
|
+
self.aLastColSize = qtAddAction(self.mLastCol, self.tr("Column Size"))
|
240
228
|
self.aLastColSize.triggered.connect(self._selectLastColumnSize)
|
241
229
|
|
242
230
|
self.tbMore = NIconToolButton(self, iSz)
|
@@ -249,13 +237,16 @@ class GuiNovelToolBar(QWidget):
|
|
249
237
|
self.outerBox.addWidget(self.tbNovel)
|
250
238
|
self.outerBox.addWidget(self.tbRefresh)
|
251
239
|
self.outerBox.addWidget(self.tbMore)
|
252
|
-
self.outerBox.setContentsMargins(
|
240
|
+
self.outerBox.setContentsMargins(4, 2, 0, 2)
|
253
241
|
self.outerBox.setSpacing(0)
|
254
242
|
|
255
243
|
self.setLayout(self.outerBox)
|
256
244
|
|
257
245
|
self.updateTheme()
|
258
246
|
|
247
|
+
# Connect Signals
|
248
|
+
SHARED.novelStructureChanged.connect(self._refreshNovelTree)
|
249
|
+
|
259
250
|
logger.debug("Ready: GuiNovelToolBar")
|
260
251
|
|
261
252
|
return
|
@@ -267,13 +258,9 @@ class GuiNovelToolBar(QWidget):
|
|
267
258
|
def updateTheme(self) -> None:
|
268
259
|
"""Update theme elements."""
|
269
260
|
# Icons
|
270
|
-
self.tbNovel.setThemeIcon("cls_novel")
|
271
|
-
self.tbRefresh.setThemeIcon("refresh")
|
272
|
-
self.tbMore.setThemeIcon("
|
273
|
-
|
274
|
-
qPalette = self.palette()
|
275
|
-
qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base())
|
276
|
-
self.setPalette(qPalette)
|
261
|
+
self.tbNovel.setThemeIcon("cls_novel", "red")
|
262
|
+
self.tbRefresh.setThemeIcon("refresh", "green")
|
263
|
+
self.tbMore.setThemeIcon("more_vertical")
|
277
264
|
|
278
265
|
# StyleSheets
|
279
266
|
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
@@ -285,9 +272,11 @@ class GuiNovelToolBar(QWidget):
|
|
285
272
|
"QComboBox {border-style: none; padding-left: 0;} "
|
286
273
|
"QComboBox::drop-down {border-style: none}"
|
287
274
|
)
|
288
|
-
self.novelValue.
|
275
|
+
self.novelValue.updateTheme()
|
289
276
|
self.tbNovel.setVisible(self.novelValue.count() > 1)
|
290
277
|
|
278
|
+
self.forceRefreshNovelTree()
|
279
|
+
|
291
280
|
return
|
292
281
|
|
293
282
|
def clearContent(self) -> None:
|
@@ -299,30 +288,67 @@ class GuiNovelToolBar(QWidget):
|
|
299
288
|
def buildNovelRootMenu(self) -> None:
|
300
289
|
"""Build the novel root menu."""
|
301
290
|
self.novelValue.refreshNovelList()
|
291
|
+
self.novelView.setCurrentNovel(self.novelValue.handle)
|
302
292
|
self.tbNovel.setVisible(self.novelValue.count() > 1)
|
303
293
|
return
|
304
294
|
|
305
295
|
def setCurrentRoot(self, rootHandle: str | None) -> None:
|
306
296
|
"""Set the current active root handle."""
|
297
|
+
if rootHandle is None or rootHandle not in SHARED.project.tree:
|
298
|
+
rootHandle = self.novelValue.firstHandle
|
307
299
|
self.novelValue.setHandle(rootHandle)
|
308
|
-
|
300
|
+
SHARED.project.data.setLastHandle(rootHandle, "novel")
|
301
|
+
self.novelView.setCurrentNovel(rootHandle)
|
302
|
+
self.novelView.novelTree.setAccessibleName(self.novelValue.currentText())
|
309
303
|
return
|
310
304
|
|
311
|
-
def setLastColType(self, colType:
|
305
|
+
def setLastColType(self, colType: nwNovelExtra, doRefresh: bool = True) -> None:
|
312
306
|
"""Set the last column type."""
|
313
307
|
self.aLastCol[colType].setChecked(True)
|
314
|
-
self.novelView.novelTree.setLastColType(colType
|
308
|
+
self.novelView.novelTree.setLastColType(colType)
|
309
|
+
if doRefresh:
|
310
|
+
self.forceRefreshNovelTree()
|
311
|
+
self.novelView.novelTree.resizeColumns()
|
312
|
+
return
|
313
|
+
|
314
|
+
def setActive(self, state: bool) -> None:
|
315
|
+
"""Set the widget active state, which enables automatic tree
|
316
|
+
refresh when content structure changes.
|
317
|
+
"""
|
318
|
+
self._active = state
|
319
|
+
if (
|
320
|
+
self._active
|
321
|
+
and (handle := self.novelValue.handle)
|
322
|
+
and self._refresh.get(handle, False)
|
323
|
+
):
|
324
|
+
self._refreshNovelTree(self.novelValue.handle)
|
315
325
|
return
|
316
326
|
|
317
327
|
##
|
318
|
-
#
|
328
|
+
# Public Slots
|
319
329
|
##
|
320
330
|
|
321
331
|
@pyqtSlot()
|
322
|
-
def
|
332
|
+
def forceRefreshNovelTree(self) -> None:
|
323
333
|
"""Rebuild the current tree."""
|
324
|
-
|
325
|
-
|
334
|
+
if tHandle := self.novelValue.handle:
|
335
|
+
self.novelView.setCurrentNovel(tHandle)
|
336
|
+
SHARED.project.index.refreshNovelModel(tHandle)
|
337
|
+
self._refresh[tHandle] = False
|
338
|
+
return
|
339
|
+
|
340
|
+
##
|
341
|
+
# Private Slots
|
342
|
+
##
|
343
|
+
|
344
|
+
@pyqtSlot(str)
|
345
|
+
def _refreshNovelTree(self, tHandle: str) -> None:
|
346
|
+
"""Refresh or schedule refresh of a novel tree."""
|
347
|
+
if self._active:
|
348
|
+
SHARED.project.index.refreshNovelModel(tHandle)
|
349
|
+
self._refresh[tHandle] = False
|
350
|
+
else:
|
351
|
+
self._refresh[tHandle] = True
|
326
352
|
return
|
327
353
|
|
328
354
|
@pyqtSlot()
|
@@ -334,16 +360,16 @@ class GuiNovelToolBar(QWidget):
|
|
334
360
|
)
|
335
361
|
if isOk:
|
336
362
|
self.novelView.novelTree.setLastColSize(newSize)
|
337
|
-
self.
|
363
|
+
self.novelView.novelTree.resizeColumns()
|
338
364
|
return
|
339
365
|
|
340
366
|
##
|
341
367
|
# Internal Functions
|
342
368
|
##
|
343
369
|
|
344
|
-
def _addLastColAction(self, colType:
|
370
|
+
def _addLastColAction(self, colType: nwNovelExtra, actionLabel: str) -> None:
|
345
371
|
"""Add a column selection entry to the last column menu."""
|
346
|
-
aLast = self.mLastCol
|
372
|
+
aLast = qtAddAction(self.mLastCol, actionLabel)
|
347
373
|
aLast.setCheckable(True)
|
348
374
|
aLast.setActionGroup(self.gLastCol)
|
349
375
|
aLast.triggered.connect(qtLambda(self.setLastColType, colType))
|
@@ -351,18 +377,7 @@ class GuiNovelToolBar(QWidget):
|
|
351
377
|
return
|
352
378
|
|
353
379
|
|
354
|
-
class GuiNovelTree(
|
355
|
-
|
356
|
-
C_DATA = 0
|
357
|
-
C_TITLE = 0
|
358
|
-
C_WORDS = 1
|
359
|
-
C_EXTRA = 2
|
360
|
-
C_MORE = 3
|
361
|
-
|
362
|
-
D_HANDLE = QtUserRole
|
363
|
-
D_TITLE = QtUserRole + 1
|
364
|
-
D_KEY = QtUserRole + 2
|
365
|
-
D_EXTRA = QtUserRole + 3
|
380
|
+
class GuiNovelTree(NTreeView):
|
366
381
|
|
367
382
|
def __init__(self, novelView: GuiNovelView) -> None:
|
368
383
|
super().__init__(parent=novelView)
|
@@ -372,63 +387,30 @@ class GuiNovelTree(QTreeWidget):
|
|
372
387
|
self.novelView = novelView
|
373
388
|
|
374
389
|
# Internal Variables
|
375
|
-
self._lastBuild = 0
|
376
|
-
self._lastCol = NovelTreeColumn.POV
|
377
|
-
self._lastColSize = 0.25
|
378
390
|
self._actHandle = None
|
379
|
-
self.
|
380
|
-
|
381
|
-
# Cached Strings
|
382
|
-
self._povLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
|
383
|
-
self._focLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])
|
384
|
-
self._pltLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])
|
385
|
-
|
386
|
-
# Build GUI
|
387
|
-
# =========
|
388
|
-
|
389
|
-
iPx = SHARED.theme.baseIconHeight
|
390
|
-
iSz = SHARED.theme.baseIconSize
|
391
|
-
cMg = CONFIG.pxInt(6)
|
391
|
+
self._lastColType = nwNovelExtra.POV
|
392
|
+
self._lastColSize = 0.25
|
392
393
|
|
393
|
-
|
394
|
+
# Widget Setup
|
395
|
+
self.setIconSize(SHARED.theme.baseIconSize)
|
394
396
|
self.setFrameStyle(QFrame.Shape.NoFrame)
|
395
397
|
self.setUniformRowHeights(True)
|
396
398
|
self.setAllColumnsShowFocus(True)
|
397
399
|
self.setHeaderHidden(True)
|
398
|
-
self.setIndentation(
|
399
|
-
self.setColumnCount(4)
|
400
|
-
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
401
|
-
self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
402
|
-
self.setExpandsOnDoubleClick(False)
|
400
|
+
self.setIndentation(2)
|
403
401
|
self.setDragEnabled(False)
|
404
402
|
|
405
|
-
#
|
406
|
-
|
407
|
-
|
408
|
-
treeHeader.setMinimumSectionSize(iPx + cMg)
|
409
|
-
treeHeader.setSectionResizeMode(self.C_TITLE, QtHeaderStretch)
|
410
|
-
treeHeader.setSectionResizeMode(self.C_WORDS, QtHeaderToContents)
|
411
|
-
treeHeader.setSectionResizeMode(self.C_EXTRA, QtHeaderToContents)
|
412
|
-
treeHeader.setSectionResizeMode(self.C_MORE, QtHeaderToContents)
|
413
|
-
|
414
|
-
# Pre-Generate Tree Formatting
|
415
|
-
fH1 = self.font()
|
416
|
-
fH1.setBold(True)
|
417
|
-
fH1.setUnderline(True)
|
418
|
-
|
419
|
-
fH2 = self.font()
|
420
|
-
fH2.setBold(True)
|
421
|
-
|
422
|
-
self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()]
|
403
|
+
# Set selection options
|
404
|
+
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
405
|
+
self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
423
406
|
|
424
407
|
# Connect signals
|
425
|
-
self.clicked.connect(self.
|
426
|
-
self.
|
427
|
-
self.
|
408
|
+
self.clicked.connect(self._onSingleClick)
|
409
|
+
self.doubleClicked.connect(self._onDoubleClick)
|
410
|
+
self.middleClicked.connect(self._onMiddleClick)
|
428
411
|
|
429
412
|
# Set custom settings
|
430
413
|
self.initSettings()
|
431
|
-
self.updateTheme()
|
432
414
|
|
433
415
|
logger.debug("Ready: GuiNovelTree")
|
434
416
|
|
@@ -436,23 +418,14 @@ class GuiNovelTree(QTreeWidget):
|
|
436
418
|
|
437
419
|
def initSettings(self) -> None:
|
438
420
|
"""Set or update tree widget settings."""
|
439
|
-
# Scroll bars
|
440
421
|
if CONFIG.hideVScroll:
|
441
422
|
self.setVerticalScrollBarPolicy(QtScrollAlwaysOff)
|
442
423
|
else:
|
443
424
|
self.setVerticalScrollBarPolicy(QtScrollAsNeeded)
|
444
|
-
|
445
425
|
if CONFIG.hideHScroll:
|
446
426
|
self.setHorizontalScrollBarPolicy(QtScrollAlwaysOff)
|
447
427
|
else:
|
448
428
|
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
|
449
|
-
|
450
|
-
return
|
451
|
-
|
452
|
-
def updateTheme(self) -> None:
|
453
|
-
"""Update theme elements."""
|
454
|
-
iPx = SHARED.theme.baseIconHeight
|
455
|
-
self._pMore = SHARED.theme.loadDecoration("deco_doc_more", h=iPx)
|
456
429
|
return
|
457
430
|
|
458
431
|
##
|
@@ -460,313 +433,180 @@ class GuiNovelTree(QTreeWidget):
|
|
460
433
|
##
|
461
434
|
|
462
435
|
@property
|
463
|
-
def lastColType(self) ->
|
464
|
-
|
436
|
+
def lastColType(self) -> nwNovelExtra:
|
437
|
+
"""The data type of the extra column."""
|
438
|
+
return self._lastColType
|
465
439
|
|
466
440
|
@property
|
467
441
|
def lastColSize(self) -> int:
|
442
|
+
"""Return the size of the extra column."""
|
468
443
|
return int(self._lastColSize * 100)
|
469
444
|
|
470
445
|
##
|
471
|
-
#
|
446
|
+
# Getters
|
472
447
|
##
|
473
448
|
|
474
|
-
def clearContent(self) -> None:
|
475
|
-
"""Clear the GUI content and the related maps."""
|
476
|
-
self.clear()
|
477
|
-
self._treeMap = {}
|
478
|
-
self._lastBuild = 0
|
479
|
-
return
|
480
|
-
|
481
|
-
def refreshTree(self, rootHandle: str | None = None, overRide: bool = False) -> None:
|
482
|
-
"""Refresh the tree if it has been changed."""
|
483
|
-
logger.debug("Requesting refresh of the novel tree")
|
484
|
-
if rootHandle is None:
|
485
|
-
rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
|
486
|
-
|
487
|
-
titleKey = None
|
488
|
-
if selItems := self.selectedItems():
|
489
|
-
titleKey = selItems[0].data(self.C_DATA, self.D_KEY)
|
490
|
-
|
491
|
-
self._populateTree(rootHandle)
|
492
|
-
SHARED.project.data.setLastHandle(rootHandle, "novelTree")
|
493
|
-
|
494
|
-
if titleKey is not None and titleKey in self._treeMap:
|
495
|
-
self._treeMap[titleKey].setSelected(True)
|
496
|
-
|
497
|
-
return
|
498
|
-
|
499
|
-
def refreshHandle(self, tHandle: str) -> None:
|
500
|
-
"""Refresh the data for a given handle."""
|
501
|
-
if idxData := SHARED.project.index.getItemData(tHandle):
|
502
|
-
logger.debug("Refreshing meta data for item '%s'", tHandle)
|
503
|
-
for sTitle, tHeading in idxData.items():
|
504
|
-
sKey = f"{tHandle}:{sTitle}"
|
505
|
-
if trItem := self._treeMap.get(sKey, None):
|
506
|
-
self._updateTreeItemValues(trItem, tHeading, tHandle, sTitle)
|
507
|
-
else:
|
508
|
-
logger.debug("Heading '%s' not in novel tree", sKey)
|
509
|
-
self.refreshTree()
|
510
|
-
return
|
511
|
-
return
|
512
|
-
|
513
449
|
def getSelectedHandle(self) -> tuple[str | None, str | None]:
|
514
450
|
"""Get the currently selected or active handle. If multiple
|
515
451
|
items are selected, return the first.
|
516
452
|
"""
|
517
|
-
|
518
|
-
|
519
|
-
if isinstance(trItem, QTreeWidgetItem):
|
520
|
-
tHandle = trItem.data(self.C_DATA, self.D_HANDLE)
|
521
|
-
sTitle = trItem.data(self.C_DATA, self.D_TITLE)
|
522
|
-
return tHandle, sTitle
|
453
|
+
if (model := self._getModel()) and (index := self.currentIndex()).isValid():
|
454
|
+
return model.handle(index), model.key(index)
|
523
455
|
return None, None
|
524
456
|
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
logger.debug("Changing last column to %s", colType.name)
|
529
|
-
self._lastCol = colType
|
530
|
-
self.setColumnHidden(self.C_EXTRA, colType == NovelTreeColumn.HIDDEN)
|
531
|
-
if doRefresh:
|
532
|
-
lastNovel = SHARED.project.data.getLastHandle("novelTree")
|
533
|
-
self.refreshTree(rootHandle=lastNovel, overRide=True)
|
534
|
-
return
|
457
|
+
##
|
458
|
+
# Setters
|
459
|
+
##
|
535
460
|
|
536
|
-
def
|
537
|
-
"""Set the
|
538
|
-
|
461
|
+
def setNovelModel(self, tHandle: str | None) -> None:
|
462
|
+
"""Set the current novel model."""
|
463
|
+
if tHandle and (model := SHARED.project.index.getNovelModel(tHandle)):
|
464
|
+
if model is not self.model():
|
465
|
+
self.setModel(model)
|
466
|
+
self.resizeColumns()
|
467
|
+
else:
|
468
|
+
self.clearContent()
|
539
469
|
return
|
540
470
|
|
541
471
|
def setActiveHandle(self, tHandle: str | None) -> None:
|
542
|
-
"""
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
if pHandle := self._actHandle:
|
547
|
-
for key, item in self._treeMap.items():
|
548
|
-
if key.startswith(pHandle):
|
549
|
-
for i in range(self.columnCount()):
|
550
|
-
item.setBackground(i, brushOff)
|
551
|
-
if tHandle:
|
552
|
-
for key, item in self._treeMap.items():
|
553
|
-
if key.startswith(tHandle):
|
554
|
-
for i in range(self.columnCount()):
|
555
|
-
item.setBackground(i, brushOn)
|
556
|
-
if not didScroll:
|
557
|
-
self.scrollToItem(item, QAbstractItemView.ScrollHint.PositionAtCenter)
|
558
|
-
didScroll = True
|
559
|
-
self._actHandle = tHandle or None
|
472
|
+
"""Set the handle to be highlighted."""
|
473
|
+
self._actHandle = tHandle
|
474
|
+
if viewport := self.viewport():
|
475
|
+
viewport.repaint()
|
560
476
|
return
|
561
477
|
|
562
|
-
|
563
|
-
|
564
|
-
|
478
|
+
def setLastColType(self, colType: nwNovelExtra) -> None:
|
479
|
+
"""Set the extra column type."""
|
480
|
+
self._lastColType = colType
|
481
|
+
SHARED.project.index.setNovelModelExtraColumn(colType)
|
482
|
+
return
|
565
483
|
|
566
|
-
def
|
567
|
-
"""
|
568
|
-
|
569
|
-
|
570
|
-
"""
|
571
|
-
super().mousePressEvent(event)
|
484
|
+
def setLastColSize(self, colSize: int) -> None:
|
485
|
+
"""Set the extra column size between 15% and 75%."""
|
486
|
+
self._lastColSize = minmax(colSize, 15, 75)/100.0
|
487
|
+
return
|
572
488
|
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
self.clearSelection()
|
489
|
+
##
|
490
|
+
# Class Methods
|
491
|
+
##
|
577
492
|
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
493
|
+
def clearContent(self) -> None:
|
494
|
+
"""Clear the tree view."""
|
495
|
+
self.setModel(None)
|
496
|
+
return
|
582
497
|
|
583
|
-
|
584
|
-
|
585
|
-
|
498
|
+
def resizeColumns(self) -> None:
|
499
|
+
"""Set the correct column sizes."""
|
500
|
+
if (header := self.header()) and (model := self._getModel()) and (vp := self.viewport()):
|
501
|
+
header.setStretchLastSection(False)
|
502
|
+
header.setMinimumSectionSize(SHARED.theme.baseIconHeight + 6)
|
503
|
+
header.setSectionResizeMode(0, QtHeaderStretch)
|
504
|
+
header.setSectionResizeMode(1, QtHeaderToContents)
|
505
|
+
header.setSectionResizeMode(2, QtHeaderToContents)
|
506
|
+
if model.columns == 4:
|
507
|
+
header.setSectionResizeMode(3, QtHeaderToContents)
|
508
|
+
header.setMaximumSectionSize(int(self._lastColSize * vp.width()))
|
509
|
+
return
|
586
510
|
|
587
|
-
|
511
|
+
##
|
512
|
+
# Overloads
|
513
|
+
##
|
588
514
|
|
515
|
+
def drawRow(self, painter: QPainter, opt: QStyleOptionViewItem, index: QModelIndex) -> None:
|
516
|
+
"""Draw a box on the active row."""
|
517
|
+
if (model := self._getModel()) and model.handle(index) == self._actHandle:
|
518
|
+
painter.fillRect(opt.rect, self.palette().alternateBase())
|
519
|
+
super().drawRow(painter, opt, index)
|
589
520
|
return
|
590
521
|
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
self.clearSelection()
|
595
|
-
return
|
522
|
+
##
|
523
|
+
# Events
|
524
|
+
##
|
596
525
|
|
597
526
|
def resizeEvent(self, event: QResizeEvent) -> None:
|
598
|
-
"""
|
527
|
+
"""Process size changed."""
|
599
528
|
super().resizeEvent(event)
|
600
|
-
|
601
|
-
oldW = event.oldSize().width()
|
602
|
-
if newW != oldW:
|
603
|
-
eliW = int(self._lastColSize * newW)
|
604
|
-
fMetric = self.fontMetrics()
|
605
|
-
for i in range(self.topLevelItemCount()):
|
606
|
-
trItem = self.topLevelItem(i)
|
607
|
-
if isinstance(trItem, QTreeWidgetItem):
|
608
|
-
lastText = trItem.data(self.C_DATA, self.D_EXTRA)
|
609
|
-
trItem.setText(
|
610
|
-
self.C_EXTRA,
|
611
|
-
fMetric.elidedText(lastText, Qt.TextElideMode.ElideRight, eliW)
|
612
|
-
)
|
529
|
+
self.resizeColumns()
|
613
530
|
return
|
614
531
|
|
615
532
|
##
|
616
533
|
# Private Slots
|
617
534
|
##
|
618
535
|
|
619
|
-
@pyqtSlot(
|
620
|
-
def
|
621
|
-
"""The user clicked
|
622
|
-
if index.
|
623
|
-
tHandle
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
536
|
+
@pyqtSlot(QModelIndex)
|
537
|
+
def _onSingleClick(self, index: QModelIndex) -> None:
|
538
|
+
"""The user single-clicked an index."""
|
539
|
+
if index.isValid() and (model := self._getModel()):
|
540
|
+
if (tHandle := model.handle(index)) and (sTitle := model.key(index)):
|
541
|
+
self.novelView.selectedItemChanged.emit(tHandle)
|
542
|
+
if index.column() == model.columnCount(index) - 1:
|
543
|
+
pos = self.mapToGlobal(self.visualRect(index).topRight())
|
544
|
+
self._popMetaBox(pos, tHandle, sTitle)
|
545
|
+
return
|
546
|
+
|
547
|
+
@pyqtSlot(QModelIndex)
|
548
|
+
def _onDoubleClick(self, index: QModelIndex) -> None:
|
549
|
+
"""The user double-clicked an index."""
|
550
|
+
if (
|
551
|
+
(model := self._getModel())
|
552
|
+
and (tHandle := model.handle(index))
|
553
|
+
and (sTitle := model.key(index))
|
554
|
+
):
|
555
|
+
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle, False)
|
556
|
+
return
|
557
|
+
|
558
|
+
@pyqtSlot(QModelIndex)
|
559
|
+
def _onMiddleClick(self, index: QModelIndex) -> None:
|
560
|
+
"""The user middle-clicked an index."""
|
561
|
+
if (
|
562
|
+
(model := self._getModel())
|
563
|
+
and (tHandle := model.handle(index))
|
564
|
+
and (sTitle := model.key(index))
|
565
|
+
):
|
566
|
+
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, sTitle, False)
|
647
567
|
return
|
648
568
|
|
649
569
|
##
|
650
570
|
# Internal Functions
|
651
571
|
##
|
652
572
|
|
653
|
-
def
|
654
|
-
"""
|
655
|
-
self.
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
novStruct = SHARED.project.index.novelStructure(rootHandle=rootHandle, activeOnly=True)
|
660
|
-
for tKey, tHandle, sTitle, novIdx in novStruct:
|
661
|
-
if novIdx.level == "H0":
|
662
|
-
continue
|
663
|
-
|
664
|
-
newItem = QTreeWidgetItem()
|
665
|
-
newItem.setData(self.C_DATA, self.D_HANDLE, tHandle)
|
666
|
-
newItem.setData(self.C_DATA, self.D_TITLE, sTitle)
|
667
|
-
newItem.setData(self.C_DATA, self.D_KEY, tKey)
|
668
|
-
newItem.setTextAlignment(self.C_WORDS, QtAlignRight)
|
669
|
-
|
670
|
-
self._updateTreeItemValues(newItem, novIdx, tHandle, sTitle)
|
671
|
-
self._treeMap[tKey] = newItem
|
672
|
-
self.addTopLevelItem(newItem)
|
673
|
-
|
674
|
-
self.setActiveHandle(self._actHandle)
|
675
|
-
|
676
|
-
logger.debug("Novel Tree built in %.3f ms", (time() - tStart)*1000)
|
677
|
-
self._lastBuild = time()
|
678
|
-
|
679
|
-
return
|
680
|
-
|
681
|
-
def _updateTreeItemValues(self, trItem: QTreeWidgetItem, idxItem: IndexHeading,
|
682
|
-
tHandle: str, sTitle: str) -> None:
|
683
|
-
"""Set the tree item values from the index entry."""
|
684
|
-
iLevel = nwStyles.H_LEVEL.get(idxItem.level, 0)
|
685
|
-
hDec = SHARED.theme.getHeaderDecoration(iLevel)
|
686
|
-
|
687
|
-
trItem.setData(self.C_TITLE, QtDecoration, hDec)
|
688
|
-
trItem.setText(self.C_TITLE, idxItem.title)
|
689
|
-
trItem.setFont(self.C_TITLE, self._hFonts[iLevel])
|
690
|
-
trItem.setText(self.C_WORDS, f"{idxItem.wordCount:n}")
|
691
|
-
trItem.setData(self.C_MORE, QtDecoration, self._pMore)
|
692
|
-
|
693
|
-
# Custom column
|
694
|
-
mW = int(self._lastColSize * self.viewport().width())
|
695
|
-
lastText, toolTip = self._getLastColumnText(tHandle, sTitle)
|
696
|
-
elideText = self.fontMetrics().elidedText(lastText, Qt.TextElideMode.ElideRight, mW)
|
697
|
-
trItem.setText(self.C_EXTRA, elideText)
|
698
|
-
trItem.setData(self.C_DATA, self.D_EXTRA, lastText)
|
699
|
-
trItem.setToolTip(self.C_EXTRA, toolTip)
|
700
|
-
|
701
|
-
return
|
702
|
-
|
703
|
-
def _getLastColumnText(self, tHandle: str, sTitle: str) -> tuple[str, str]:
|
704
|
-
"""Generate text for the last column based on user settings."""
|
705
|
-
if self._lastCol == NovelTreeColumn.HIDDEN:
|
706
|
-
return "", ""
|
707
|
-
|
708
|
-
refData = []
|
709
|
-
refName = ""
|
710
|
-
refs = SHARED.project.index.getReferences(tHandle, sTitle)
|
711
|
-
if self._lastCol == NovelTreeColumn.POV:
|
712
|
-
refData = refs[nwKeyWords.POV_KEY]
|
713
|
-
refName = self._povLabel
|
714
|
-
|
715
|
-
elif self._lastCol == NovelTreeColumn.FOCUS:
|
716
|
-
refData = refs[nwKeyWords.FOCUS_KEY]
|
717
|
-
refName = self._focLabel
|
718
|
-
|
719
|
-
elif self._lastCol == NovelTreeColumn.PLOT:
|
720
|
-
refData = refs[nwKeyWords.PLOT_KEY]
|
721
|
-
refName = self._pltLabel
|
722
|
-
|
723
|
-
if refData:
|
724
|
-
toolText = ", ".join(refData)
|
725
|
-
return toolText, f"{refName}: {toolText}"
|
726
|
-
|
727
|
-
return "", ""
|
573
|
+
def _getModel(self) -> NovelModel | None:
|
574
|
+
"""Return the model, if it exists."""
|
575
|
+
if isinstance(model := self.model(), NovelModel):
|
576
|
+
return model
|
577
|
+
return None
|
728
578
|
|
729
579
|
def _popMetaBox(self, qPos: QPoint, tHandle: str, sTitle: str) -> None:
|
730
580
|
"""Show the novel meta data box."""
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
refTags = pIndex.getReferences(tHandle, sTitle)
|
736
|
-
if not novIdx:
|
581
|
+
def appendTags(refs: dict, key: str, lines: list[str]) -> None:
|
582
|
+
"""Generate a reference list for a given reference key."""
|
583
|
+
if tags := ", ".join(refs.get(key, [])):
|
584
|
+
lines.append(f"<b>{trConst(nwLabels.KEY_NAME[key])}:</b> {tags}")
|
737
585
|
return
|
738
586
|
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
return
|
765
|
-
|
766
|
-
@staticmethod
|
767
|
-
def _appendMetaTag(refs: dict, key: str, lines: list[str]) -> list[str]:
|
768
|
-
"""Generate a reference list for a given reference key."""
|
769
|
-
tags = ", ".join(refs.get(key, []))
|
770
|
-
if tags:
|
771
|
-
lines.append(f"<b>{trConst(nwLabels.KEY_NAME[key])}</b>: {tags}")
|
772
|
-
return lines
|
587
|
+
if head := SHARED.project.index.getItemHeading(tHandle, sTitle):
|
588
|
+
logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
|
589
|
+
if synopsis := head.synopsis:
|
590
|
+
label = trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP])
|
591
|
+
synopsis = f"<p><b>{label}:</b> {synopsis}</p>"
|
592
|
+
|
593
|
+
lines = []
|
594
|
+
if head := SHARED.project.index.getItemHeading(tHandle, sTitle):
|
595
|
+
tags = head.getReferences()
|
596
|
+
appendTags(tags, nwKeyWords.POV_KEY, lines)
|
597
|
+
appendTags(tags, nwKeyWords.FOCUS_KEY, lines)
|
598
|
+
appendTags(tags, nwKeyWords.CHAR_KEY, lines)
|
599
|
+
appendTags(tags, nwKeyWords.PLOT_KEY, lines)
|
600
|
+
appendTags(tags, nwKeyWords.TIME_KEY, lines)
|
601
|
+
appendTags(tags, nwKeyWords.WORLD_KEY, lines)
|
602
|
+
appendTags(tags, nwKeyWords.OBJECT_KEY, lines)
|
603
|
+
appendTags(tags, nwKeyWords.ENTITY_KEY, lines)
|
604
|
+
appendTags(tags, nwKeyWords.CUSTOM_KEY, lines)
|
605
|
+
|
606
|
+
text = ""
|
607
|
+
if lines:
|
608
|
+
refs = "<br>".join(lines)
|
609
|
+
text = f"<p>{refs}</p>"
|
610
|
+
if tooltip := (text + synopsis or self.tr("No meta data")):
|
611
|
+
QToolTip.showText(qPos, tooltip)
|
612
|
+
return
|