novelWriter 2.6.3__py3-none-any.whl → 2.7rc1__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/project_en_GB.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 +6 -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 +67 -36
- novelwriter/core/buildsettings.py +25 -16
- 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 +11 -14
- novelwriter/gui/mainmenu.py +146 -145
- novelwriter/gui/noveltree.py +246 -406
- novelwriter/gui/outline.py +141 -72
- 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 +102 -115
- novelwriter/tools/manussettings.py +116 -96
- 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.3.dist-info → novelwriter-2.7rc1.dist-info}/METADATA +6 -6
- novelwriter-2.7rc1.dist-info/RECORD +162 -0
- {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info}/WHEEL +1 -1
- novelWriter-2.6.3.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.3.dist-info → novelwriter-2.7rc1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info/licenses}/LICENSE.md +0 -0
- {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info}/top_level.txt +0 -0
novelwriter/gui/doceditor.py
CHANGED
@@ -3,15 +3,16 @@ novelWriter – GUI Document Editor
|
|
3
3
|
=================================
|
4
4
|
|
5
5
|
File History:
|
6
|
-
Created: 2018-09-29 [0.0.1]
|
7
|
-
Created: 2019-04-22 [0.0.1]
|
8
|
-
Created: 2019-09-29 [0.2.1]
|
9
|
-
Created: 2020-04-25 [0.4.5]
|
10
|
-
Rewritten: 2020-06-15 [0.9]
|
11
|
-
Created: 2020-06-27 [0.10]
|
12
|
-
Rewritten: 2020-10-07 [1.0b3]
|
13
|
-
Created: 2023-11-06 [2.2b1]
|
14
|
-
Created: 2023-11-07 [2.2b1]
|
6
|
+
Created: 2018-09-29 [0.0.1] GuiDocEditor
|
7
|
+
Created: 2019-04-22 [0.0.1] BackgroundWordCounter
|
8
|
+
Created: 2019-09-29 [0.2.1] GuiDocEditSearch
|
9
|
+
Created: 2020-04-25 [0.4.5] GuiDocEditHeader
|
10
|
+
Rewritten: 2020-06-15 [0.9] GuiDocEditSearch
|
11
|
+
Created: 2020-06-27 [0.10] GuiDocEditFooter
|
12
|
+
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
|
13
|
+
Created: 2023-11-06 [2.2b1] MetaCompleter
|
14
|
+
Created: 2023-11-07 [2.2b1] GuiDocToolBar
|
15
|
+
Extended: 2025-05-18 [2.7rc1] CommandCompleter
|
15
16
|
|
16
17
|
This file is a part of novelWriter
|
17
18
|
Copyright (C) 2018 Veronica Berglyd Olsen and novelWriter contributors
|
@@ -37,29 +38,33 @@ import logging
|
|
37
38
|
from enum import Enum, IntFlag
|
38
39
|
from time import time
|
39
40
|
|
40
|
-
from
|
41
|
+
from PyQt6.QtCore import (
|
41
42
|
QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal,
|
42
43
|
pyqtSlot
|
43
44
|
)
|
44
|
-
from
|
45
|
-
|
46
|
-
QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent,
|
47
|
-
QTextCursor, QTextDocument, QTextOption
|
45
|
+
from PyQt6.QtGui import (
|
46
|
+
QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent,
|
47
|
+
QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QShortcut,
|
48
|
+
QTextBlock, QTextCursor, QTextDocument, QTextOption
|
48
49
|
)
|
49
|
-
from
|
50
|
-
|
51
|
-
|
50
|
+
from PyQt6.QtWidgets import (
|
51
|
+
QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
|
52
|
+
QPlainTextEdit, QToolBar, QVBoxLayout, QWidget
|
52
53
|
)
|
53
54
|
|
54
55
|
from novelwriter import CONFIG, SHARED
|
55
|
-
from novelwriter.common import
|
56
|
-
|
56
|
+
from novelwriter.common import (
|
57
|
+
decodeMimeHandles, fontMatcher, minmax, qtAddAction, qtLambda, transferCase
|
58
|
+
)
|
59
|
+
from novelwriter.constants import (
|
60
|
+
nwConst, nwKeyWords, nwLabels, nwShortcode, nwStats, nwUnicode, trStats
|
61
|
+
)
|
57
62
|
from novelwriter.core.document import NWDocument
|
58
63
|
from novelwriter.enum import (
|
59
64
|
nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass,
|
60
65
|
nwItemType
|
61
66
|
)
|
62
|
-
from novelwriter.extensions.configlayout import
|
67
|
+
from novelwriter.extensions.configlayout import NColorLabel
|
63
68
|
from novelwriter.extensions.eventfilters import WheelEventFilter
|
64
69
|
from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton
|
65
70
|
from novelwriter.gui.dochighlight import BLOCK_META, BLOCK_TITLE
|
@@ -95,16 +100,17 @@ class GuiDocEditor(QPlainTextEdit):
|
|
95
100
|
"""Gui Widget: Main Document Editor"""
|
96
101
|
|
97
102
|
__slots__ = (
|
98
|
-
"
|
99
|
-
"
|
100
|
-
"
|
101
|
-
"
|
103
|
+
"_autoReplace", "_completer", "_doReplace", "_docChanged", "_docHandle", "_followTag1",
|
104
|
+
"_followTag2", "_keyContext", "_lastActive", "_lastEdit", "_lastFind", "_nwDocument",
|
105
|
+
"_nwItem", "_qDocument", "_timerDoc", "_timerSel", "_vpMargin", "_wCounterDoc",
|
106
|
+
"_wCounterSel",
|
102
107
|
)
|
103
108
|
|
104
109
|
MOVE_KEYS = (
|
105
110
|
Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, Qt.Key.Key_Down,
|
106
111
|
Qt.Key.Key_PageUp, Qt.Key.Key_PageDown
|
107
112
|
)
|
113
|
+
ENTER_KEYS = (Qt.Key.Key_Return, Qt.Key.Key_Enter)
|
108
114
|
|
109
115
|
# Custom Signals
|
110
116
|
closeEditorRequest = pyqtSignal()
|
@@ -112,8 +118,6 @@ class GuiDocEditor(QPlainTextEdit):
|
|
112
118
|
editedStatusChanged = pyqtSignal(bool)
|
113
119
|
itemHandleChanged = pyqtSignal(str)
|
114
120
|
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
115
|
-
novelItemMetaChanged = pyqtSignal(str)
|
116
|
-
novelStructureChanged = pyqtSignal()
|
117
121
|
openDocumentRequest = pyqtSignal(str, Enum, str, bool)
|
118
122
|
requestNewNoteCreation = pyqtSignal(str, nwItemClass)
|
119
123
|
requestNextDocument = pyqtSignal(str, bool)
|
@@ -146,7 +150,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
146
150
|
self._autoReplace = TextAutoReplace()
|
147
151
|
|
148
152
|
# Completer
|
149
|
-
self._completer =
|
153
|
+
self._completer = CommandCompleter(self)
|
150
154
|
self._completer.complete.connect(self._insertCompletion)
|
151
155
|
|
152
156
|
# Create Custom Document
|
@@ -175,7 +179,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
175
179
|
self.customContextMenuRequested.connect(self._openContextMenu)
|
176
180
|
|
177
181
|
# Editor Settings
|
178
|
-
self.setMinimumWidth(
|
182
|
+
self.setMinimumWidth(300)
|
179
183
|
self.setAutoFillBackground(True)
|
180
184
|
self.setFrameStyle(QFrame.Shape.NoFrame)
|
181
185
|
self.setAcceptDrops(True)
|
@@ -224,7 +228,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
224
228
|
self.changeFocusState = self.docHeader.changeFocusState
|
225
229
|
|
226
230
|
# Finalise
|
227
|
-
self.
|
231
|
+
self.updateSyntaxColors()
|
228
232
|
self.initEditor()
|
229
233
|
|
230
234
|
logger.debug("Ready: GuiDocEditor")
|
@@ -292,21 +296,24 @@ class GuiDocEditor(QPlainTextEdit):
|
|
292
296
|
self.docToolBar.updateTheme()
|
293
297
|
return
|
294
298
|
|
295
|
-
def
|
299
|
+
def updateSyntaxColors(self) -> None:
|
296
300
|
"""Update the syntax highlighting theme."""
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
301
|
+
syntax = SHARED.theme.syntaxTheme
|
302
|
+
|
303
|
+
palette = self.palette()
|
304
|
+
palette.setColor(QPalette.ColorRole.Window, syntax.back)
|
305
|
+
palette.setColor(QPalette.ColorRole.Base, syntax.back)
|
306
|
+
palette.setColor(QPalette.ColorRole.Text, syntax.text)
|
307
|
+
self.setPalette(palette)
|
302
308
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
309
|
+
if viewport := self.viewport():
|
310
|
+
palette = viewport.palette()
|
311
|
+
palette.setColor(QPalette.ColorRole.Base, syntax.back)
|
312
|
+
palette.setColor(QPalette.ColorRole.Text, syntax.text)
|
313
|
+
viewport.setPalette(palette)
|
307
314
|
|
308
|
-
self.docHeader.
|
309
|
-
self.docFooter.
|
315
|
+
self.docHeader.matchColors()
|
316
|
+
self.docFooter.matchColors()
|
310
317
|
|
311
318
|
return
|
312
319
|
|
@@ -317,6 +324,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
317
324
|
"""
|
318
325
|
# Auto-Replace
|
319
326
|
self._autoReplace.initSettings()
|
327
|
+
self.docFooter.initSettings()
|
320
328
|
|
321
329
|
# Reload spell check and dictionaries
|
322
330
|
SHARED.updateSpellCheckLanguage()
|
@@ -336,19 +344,17 @@ class GuiDocEditor(QPlainTextEdit):
|
|
336
344
|
# Due to cursor visibility, a part of the margin must be
|
337
345
|
# allocated to the document itself. See issue #1112.
|
338
346
|
self._qDocument.setDocumentMargin(4)
|
339
|
-
self._vpMargin = max(CONFIG.
|
347
|
+
self._vpMargin = max(CONFIG.textMargin - 4, 0)
|
340
348
|
self.setViewportMargins(self._vpMargin, self._vpMargin, self._vpMargin, self._vpMargin)
|
341
349
|
|
342
350
|
# Also set the document text options for the document text flow
|
343
351
|
options = QTextOption()
|
344
|
-
|
345
352
|
if CONFIG.doJustify:
|
346
353
|
options.setAlignment(QtAlignJustify)
|
347
354
|
if CONFIG.showTabsNSpaces:
|
348
355
|
options.setFlags(options.flags() | QTextOption.Flag.ShowTabsAndSpaces)
|
349
356
|
if CONFIG.showLineEndings:
|
350
357
|
options.setFlags(options.flags() | QTextOption.Flag.ShowLineAndParagraphSeparators)
|
351
|
-
|
352
358
|
self._qDocument.setDefaultTextOption(options)
|
353
359
|
|
354
360
|
# Scrolling
|
@@ -363,8 +369,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
363
369
|
else:
|
364
370
|
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
|
365
371
|
|
366
|
-
# Refresh
|
367
|
-
self.setTabStopDistance(CONFIG.
|
372
|
+
# Refresh sizes
|
373
|
+
self.setTabStopDistance(CONFIG.tabWidth)
|
374
|
+
self.setCursorWidth(CONFIG.cursorWidth)
|
368
375
|
|
369
376
|
# If we have a document open, we should refresh it in case the
|
370
377
|
# font changed, otherwise we just clear the editor entirely,
|
@@ -381,19 +388,19 @@ class GuiDocEditor(QPlainTextEdit):
|
|
381
388
|
"""Load text from a document into the editor. If we have an I/O
|
382
389
|
error, we must handle this and clear the editor so that we don't
|
383
390
|
risk overwriting the file if it exists. This can for instance
|
384
|
-
happen
|
391
|
+
happen if the file contains binary elements or an encoding that
|
385
392
|
novelWriter does not support. If loading is successful, or the
|
386
393
|
document is new (empty string), we set up the editor for editing
|
387
394
|
the file.
|
388
395
|
"""
|
389
396
|
self._nwDocument = SHARED.project.storage.getDocument(tHandle)
|
390
397
|
self._nwItem = self._nwDocument.nwItem
|
391
|
-
if not (
|
398
|
+
if not (self._nwItem and self._nwItem.itemType == nwItemType.FILE):
|
392
399
|
logger.debug("Requested item '%s' is not a document", tHandle)
|
393
400
|
self.clearEditor()
|
394
401
|
return False
|
395
402
|
|
396
|
-
if (
|
403
|
+
if (text := self._nwDocument.readDocument()) is None:
|
397
404
|
# There was an I/O error
|
398
405
|
self.clearEditor()
|
399
406
|
return False
|
@@ -402,7 +409,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
402
409
|
self._docHandle = tHandle
|
403
410
|
|
404
411
|
self._allowAutoReplace(False)
|
405
|
-
self._qDocument.setTextContent(
|
412
|
+
self._qDocument.setTextContent(text, tHandle)
|
406
413
|
self._allowAutoReplace(True)
|
407
414
|
QApplication.processEvents()
|
408
415
|
|
@@ -417,7 +424,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
417
424
|
if isinstance(tLine, int):
|
418
425
|
self.setCursorLine(tLine)
|
419
426
|
else:
|
420
|
-
self.setCursorPosition(
|
427
|
+
self.setCursorPosition(self._nwItem.cursorPos)
|
421
428
|
|
422
429
|
self.docHeader.setHandle(tHandle)
|
423
430
|
self.docFooter.setHandle(tHandle)
|
@@ -440,7 +447,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
440
447
|
# Finalise
|
441
448
|
QApplication.restoreOverrideCursor()
|
442
449
|
self.updateStatusMessage.emit(
|
443
|
-
self.tr("Opened Document: {0}").format(
|
450
|
+
self.tr("Opened Document: {0}").format(self._nwItem.itemName)
|
444
451
|
)
|
445
452
|
|
446
453
|
return True
|
@@ -471,19 +478,17 @@ class GuiDocEditor(QPlainTextEdit):
|
|
471
478
|
)
|
472
479
|
return False
|
473
480
|
|
474
|
-
|
475
|
-
cC, wC, pC = standardCounter(
|
481
|
+
text = self.getText()
|
482
|
+
cC, wC, pC = standardCounter(text)
|
476
483
|
self._updateDocCounts(cC, wC, pC)
|
477
484
|
|
478
|
-
if not self._nwDocument.writeDocument(
|
485
|
+
if not self._nwDocument.writeDocument(text):
|
479
486
|
saveOk = False
|
480
|
-
if self._nwDocument.hashError
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
)
|
485
|
-
if msgYes:
|
486
|
-
saveOk = self._nwDocument.writeDocument(docText, forceWrite=True)
|
487
|
+
if self._nwDocument.hashError and SHARED.question(self.tr(
|
488
|
+
"This document has been changed outside of novelWriter "
|
489
|
+
"while it was open. Overwrite the file on disk?"
|
490
|
+
)):
|
491
|
+
saveOk = self._nwDocument.writeDocument(text, forceWrite=True)
|
487
492
|
|
488
493
|
if not saveOk:
|
489
494
|
SHARED.error(
|
@@ -495,52 +500,35 @@ class GuiDocEditor(QPlainTextEdit):
|
|
495
500
|
|
496
501
|
self.setDocumentChanged(False)
|
497
502
|
self.docTextChanged.emit(self._docHandle, self._lastEdit)
|
503
|
+
SHARED.project.index.scanText(tHandle, text)
|
498
504
|
|
499
|
-
oldHeader = self._nwItem.mainHeading
|
500
|
-
oldCount = SHARED.project.index.getHandleHeaderCount(tHandle)
|
501
|
-
SHARED.project.index.scanText(tHandle, docText)
|
502
|
-
newHeader = self._nwItem.mainHeading
|
503
|
-
newCount = SHARED.project.index.getHandleHeaderCount(tHandle)
|
504
|
-
|
505
|
-
if self._nwItem.itemClass == nwItemClass.NOVEL:
|
506
|
-
if oldCount == newCount:
|
507
|
-
self.novelItemMetaChanged.emit(tHandle)
|
508
|
-
else:
|
509
|
-
self.novelStructureChanged.emit()
|
510
|
-
|
511
|
-
if oldHeader != newHeader:
|
512
|
-
self.docFooter.updateInfo()
|
513
|
-
|
514
|
-
# Update the status bar
|
515
505
|
self.updateStatusMessage.emit(self.tr("Saved Document: {0}").format(self._nwItem.itemName))
|
516
506
|
|
517
507
|
return True
|
518
508
|
|
519
509
|
def cursorIsVisible(self) -> bool:
|
520
510
|
"""Check if the cursor is visible in the editor."""
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
)
|
511
|
+
viewport = self.viewport()
|
512
|
+
height = viewport.height() if viewport else 0
|
513
|
+
return 0 < self.cursorRect().top() and self.cursorRect().bottom() < height
|
525
514
|
|
526
515
|
def ensureCursorVisibleNoCentre(self) -> None:
|
527
516
|
"""Ensure cursor is visible, but don't force it to centre."""
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
QApplication.processEvents()
|
517
|
+
if (viewport := self.viewport()) and (vBar := self.verticalScrollBar()):
|
518
|
+
cT = self.cursorRect().top()
|
519
|
+
cB = self.cursorRect().bottom()
|
520
|
+
vH = viewport.height()
|
521
|
+
if cT < 0:
|
522
|
+
count = 0
|
523
|
+
while self.cursorRect().top() < 0 and count < 100000:
|
524
|
+
vBar.setValue(vBar.value() - 1)
|
525
|
+
count += 1
|
526
|
+
elif cB > vH:
|
527
|
+
count = 0
|
528
|
+
while self.cursorRect().bottom() > vH and count < 100000:
|
529
|
+
vBar.setValue(vBar.value() + 1)
|
530
|
+
count += 1
|
531
|
+
QApplication.processEvents()
|
544
532
|
return
|
545
533
|
|
546
534
|
def updateDocMargins(self) -> None:
|
@@ -552,10 +540,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
552
540
|
wH = self.height()
|
553
541
|
|
554
542
|
vBar = self.verticalScrollBar()
|
555
|
-
sW = vBar.width() if vBar.isVisible() else 0
|
543
|
+
sW = vBar.width() if vBar and vBar.isVisible() else 0
|
556
544
|
|
557
545
|
hBar = self.horizontalScrollBar()
|
558
|
-
sH = hBar.height() if hBar.isVisible() else 0
|
546
|
+
sH = hBar.height() if hBar and hBar.isVisible() else 0
|
559
547
|
|
560
548
|
tM = self._vpMargin
|
561
549
|
if CONFIG.textWidth > 0 or SHARED.focusMode:
|
@@ -593,7 +581,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
593
581
|
QTextDocument->toRawText instead of toPlainText. The former preserves
|
594
582
|
non-breaking spaces, the latter does not. We still want to get rid of
|
595
583
|
paragraph and line separators though.
|
596
|
-
See: https://doc.qt.io/qt-
|
584
|
+
See: https://doc.qt.io/qt-6/qtextdocument.html#toPlainText
|
597
585
|
"""
|
598
586
|
text = self._qDocument.toRawText()
|
599
587
|
text = text.replace(nwUnicode.U_LSEP, "\n") # Line separators
|
@@ -631,10 +619,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
631
619
|
|
632
620
|
def setCursorPosition(self, position: int) -> None:
|
633
621
|
"""Move the cursor to a given position in the document."""
|
634
|
-
|
635
|
-
if nChars > 1 and isinstance(position, int):
|
622
|
+
if (chars := self._qDocument.characterCount()) > 1 and isinstance(position, int):
|
636
623
|
cursor = self.textCursor()
|
637
|
-
cursor.setPosition(minmax(position, 0,
|
624
|
+
cursor.setPosition(minmax(position, 0, chars-1))
|
638
625
|
self.setTextCursor(cursor)
|
639
626
|
self.centerCursor()
|
640
627
|
return
|
@@ -697,12 +684,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
697
684
|
|
698
685
|
def spellCheckDocument(self) -> None:
|
699
686
|
"""Rerun the highlighter to update spell checking status of the
|
700
|
-
currently loaded text.
|
701
|
-
of Qt 5.13, is to clear the text and put it back. This clears
|
702
|
-
the undo stack, so we only do it for big documents.
|
687
|
+
currently loaded text.
|
703
688
|
"""
|
704
|
-
logger.debug("Running spell checker")
|
705
689
|
start = time()
|
690
|
+
logger.debug("Running spell checker")
|
706
691
|
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
707
692
|
self._qDocument.syntaxHighlighter.rehighlight()
|
708
693
|
QApplication.restoreOverrideCursor()
|
@@ -779,7 +764,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
779
764
|
elif action == nwDocAction.REPL_SNG:
|
780
765
|
self._replaceQuotes("'", CONFIG.fmtSQuoteOpen, CONFIG.fmtSQuoteClose)
|
781
766
|
elif action == nwDocAction.REPL_DBL:
|
782
|
-
self._replaceQuotes("
|
767
|
+
self._replaceQuotes('"', CONFIG.fmtDQuoteOpen, CONFIG.fmtDQuoteClose)
|
783
768
|
elif action == nwDocAction.RM_BREAKS:
|
784
769
|
self._removeInParLineBreaks()
|
785
770
|
elif action == nwDocAction.ALIGN_L:
|
@@ -943,9 +928,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
943
928
|
* We also handle automatic scrolling here.
|
944
929
|
"""
|
945
930
|
self._lastActive = time()
|
946
|
-
|
947
|
-
isReturn |= event.key() == Qt.Key.Key_Enter
|
948
|
-
if isReturn and self.docSearch.anyFocus():
|
931
|
+
if self.docSearch.anyFocus() and event.key() in self.ENTER_KEYS:
|
949
932
|
return
|
950
933
|
elif event == QKeySequence.StandardKey.Redo:
|
951
934
|
self.docAction(nwDocAction.REDO)
|
@@ -964,10 +947,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
964
947
|
kMod = event.modifiers()
|
965
948
|
okMod = kMod in (QtModNone, QtModShift)
|
966
949
|
okKey = event.key() not in self.MOVE_KEYS
|
967
|
-
if nPos != cPos and okMod and okKey:
|
968
|
-
mPos = CONFIG.autoScrollPos*0.01 *
|
969
|
-
if cPos > mPos:
|
970
|
-
vBar = self.verticalScrollBar()
|
950
|
+
if nPos != cPos and okMod and okKey and (viewport := self.viewport()):
|
951
|
+
mPos = CONFIG.autoScrollPos*0.01 * viewport.height()
|
952
|
+
if cPos > mPos and (vBar := self.verticalScrollBar()):
|
971
953
|
vBar.setValue(vBar.value() + (1 if nPos > cPos else -1))
|
972
954
|
else:
|
973
955
|
super().keyPressEvent(event)
|
@@ -976,7 +958,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
976
958
|
|
977
959
|
def dragEnterEvent(self, event: QDragEnterEvent) -> None:
|
978
960
|
"""Overload drag enter event to handle dragged items."""
|
979
|
-
if event.mimeData().hasFormat(nwConst.MIME_HANDLE):
|
961
|
+
if (data := event.mimeData()) and data.hasFormat(nwConst.MIME_HANDLE):
|
980
962
|
event.acceptProposedAction()
|
981
963
|
else:
|
982
964
|
super().dragEnterEvent(event)
|
@@ -984,7 +966,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
984
966
|
|
985
967
|
def dragMoveEvent(self, event: QDragMoveEvent) -> None:
|
986
968
|
"""Overload drag move event to handle dragged items."""
|
987
|
-
if event.mimeData().hasFormat(nwConst.MIME_HANDLE):
|
969
|
+
if (data := event.mimeData()) and data.hasFormat(nwConst.MIME_HANDLE):
|
988
970
|
event.acceptProposedAction()
|
989
971
|
else:
|
990
972
|
super().dragMoveEvent(event)
|
@@ -992,15 +974,15 @@ class GuiDocEditor(QPlainTextEdit):
|
|
992
974
|
|
993
975
|
def dropEvent(self, event: QDropEvent) -> None:
|
994
976
|
"""Overload drop event to handle dragged items."""
|
995
|
-
if event.mimeData().hasFormat(nwConst.MIME_HANDLE):
|
996
|
-
if handles := decodeMimeHandles(
|
977
|
+
if (data := event.mimeData()) and data.hasFormat(nwConst.MIME_HANDLE):
|
978
|
+
if handles := decodeMimeHandles(data):
|
997
979
|
if SHARED.project.tree.checkType(handles[0], nwItemType.FILE):
|
998
980
|
self.openDocumentRequest.emit(handles[0], nwDocMode.EDIT, "", True)
|
999
981
|
else:
|
1000
982
|
super().dropEvent(event)
|
1001
983
|
return
|
1002
984
|
|
1003
|
-
def focusNextPrevChild(self,
|
985
|
+
def focusNextPrevChild(self, _next: bool) -> bool:
|
1004
986
|
"""Capture the focus request from the tab key on the text
|
1005
987
|
editor. If the editor has focus, we do not change focus and
|
1006
988
|
allow the editor to insert a tab. If the search bar has focus,
|
@@ -1059,7 +1041,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1059
1041
|
logger.error("Invalid keyword '%s'", keyword)
|
1060
1042
|
return False
|
1061
1043
|
logger.debug("Inserting keyword '%s'", keyword)
|
1062
|
-
state = self.insertNewBlock("
|
1044
|
+
state = self.insertNewBlock(f"{keyword}: ")
|
1063
1045
|
return state
|
1064
1046
|
|
1065
1047
|
@pyqtSlot()
|
@@ -1098,15 +1080,18 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1098
1080
|
|
1099
1081
|
if (block := self._qDocument.findBlock(pos)).isValid():
|
1100
1082
|
text = block.text()
|
1101
|
-
if text
|
1102
|
-
# Only run on single
|
1083
|
+
if text and text[0] in "@%" and added + removed == 1:
|
1084
|
+
# Only run on single character changes, or it will trigger
|
1103
1085
|
# at unwanted times when other changes are made to the document
|
1104
1086
|
cursor = self.textCursor()
|
1105
1087
|
bPos = cursor.positionInBlock()
|
1106
|
-
if bPos > 0:
|
1107
|
-
|
1088
|
+
if bPos > 0 and (viewport := self.viewport()):
|
1089
|
+
if text[0] == "@":
|
1090
|
+
show = self._completer.updateMetaText(text, bPos)
|
1091
|
+
else:
|
1092
|
+
show = self._completer.updateCommentText(text, bPos)
|
1108
1093
|
point = self.cursorRect().bottomRight()
|
1109
|
-
self._completer.move(
|
1094
|
+
self._completer.move(viewport.mapToGlobal(point))
|
1110
1095
|
self._completer.setVisible(show)
|
1111
1096
|
else:
|
1112
1097
|
self._completer.setVisible(False)
|
@@ -1128,11 +1113,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1128
1113
|
def _insertCompletion(self, pos: int, length: int, text: str) -> None:
|
1129
1114
|
"""Insert choice from the completer menu."""
|
1130
1115
|
cursor = self.textCursor()
|
1131
|
-
block
|
1132
|
-
|
1133
|
-
|
1134
|
-
cursor.setPosition(
|
1135
|
-
cursor.setPosition(pos + length, QtKeepAnchor)
|
1116
|
+
if (block := cursor.block()).isValid():
|
1117
|
+
check = pos + block.position()
|
1118
|
+
cursor.setPosition(check, QtMoveAnchor)
|
1119
|
+
cursor.setPosition(check + length, QtKeepAnchor)
|
1136
1120
|
cursor.insertText(text)
|
1137
1121
|
self._completer.hide()
|
1138
1122
|
return
|
@@ -1153,46 +1137,46 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1153
1137
|
ctxMenu = QMenu(self)
|
1154
1138
|
ctxMenu.setObjectName("ContextMenu")
|
1155
1139
|
if pBlock.userState() == BLOCK_TITLE:
|
1156
|
-
action = ctxMenu
|
1140
|
+
action = qtAddAction(ctxMenu, self.tr("Set as Document Name"))
|
1157
1141
|
action.triggered.connect(qtLambda(self._emitRenameItem, pBlock))
|
1158
1142
|
|
1159
1143
|
# URL
|
1160
1144
|
(mData, mType) = self._qDocument.metaDataAtPos(pCursor.position())
|
1161
1145
|
if mData and mType == "url":
|
1162
|
-
action = ctxMenu
|
1146
|
+
action = qtAddAction(ctxMenu, self.tr("Open URL"))
|
1163
1147
|
action.triggered.connect(qtLambda(SHARED.openWebsite, mData))
|
1164
1148
|
ctxMenu.addSeparator()
|
1165
1149
|
|
1166
1150
|
# Follow
|
1167
1151
|
status = self._processTag(cursor=pCursor, follow=False)
|
1168
1152
|
if status & _TagAction.FOLLOW:
|
1169
|
-
action = ctxMenu
|
1153
|
+
action = qtAddAction(ctxMenu, self.tr("Follow Tag"))
|
1170
1154
|
action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, follow=True))
|
1171
1155
|
ctxMenu.addSeparator()
|
1172
1156
|
elif status & _TagAction.CREATE:
|
1173
|
-
action = ctxMenu
|
1157
|
+
action = qtAddAction(ctxMenu, self.tr("Create Note for Tag"))
|
1174
1158
|
action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, create=True))
|
1175
1159
|
ctxMenu.addSeparator()
|
1176
1160
|
|
1177
1161
|
# Cut, Copy and Paste
|
1178
1162
|
if uCursor.hasSelection():
|
1179
|
-
action = ctxMenu
|
1163
|
+
action = qtAddAction(ctxMenu, self.tr("Cut"))
|
1180
1164
|
action.triggered.connect(qtLambda(self.docAction, nwDocAction.CUT))
|
1181
|
-
action = ctxMenu
|
1165
|
+
action = qtAddAction(ctxMenu, self.tr("Copy"))
|
1182
1166
|
action.triggered.connect(qtLambda(self.docAction, nwDocAction.COPY))
|
1183
1167
|
|
1184
|
-
action = ctxMenu
|
1168
|
+
action = qtAddAction(ctxMenu, self.tr("Paste"))
|
1185
1169
|
action.triggered.connect(qtLambda(self.docAction, nwDocAction.PASTE))
|
1186
1170
|
ctxMenu.addSeparator()
|
1187
1171
|
|
1188
1172
|
# Selections
|
1189
|
-
action = ctxMenu
|
1173
|
+
action = qtAddAction(ctxMenu, self.tr("Select All"))
|
1190
1174
|
action.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL))
|
1191
|
-
action = ctxMenu
|
1175
|
+
action = qtAddAction(ctxMenu, self.tr("Select Word"))
|
1192
1176
|
action.triggered.connect(qtLambda(
|
1193
1177
|
self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, pos,
|
1194
1178
|
))
|
1195
|
-
action = ctxMenu
|
1179
|
+
action = qtAddAction(ctxMenu, self.tr("Select Paragraph"))
|
1196
1180
|
action.triggered.connect(qtLambda(
|
1197
1181
|
self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, pos
|
1198
1182
|
))
|
@@ -1208,22 +1192,24 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1208
1192
|
sCursor.movePosition(QtMoveRight, QtKeepAnchor, cLen)
|
1209
1193
|
if suggest:
|
1210
1194
|
ctxMenu.addSeparator()
|
1211
|
-
ctxMenu
|
1195
|
+
qtAddAction(ctxMenu, self.tr("Spelling Suggestion(s)"))
|
1212
1196
|
for option in suggest[:15]:
|
1213
|
-
action = ctxMenu
|
1197
|
+
action = qtAddAction(ctxMenu, f"{nwUnicode.U_ENDASH} {option}")
|
1214
1198
|
action.triggered.connect(qtLambda(self._correctWord, sCursor, option))
|
1215
1199
|
else:
|
1216
1200
|
trNone = self.tr("No Suggestions")
|
1217
|
-
ctxMenu
|
1201
|
+
qtAddAction(ctxMenu, f"{nwUnicode.U_ENDASH} {trNone}")
|
1218
1202
|
|
1219
1203
|
ctxMenu.addSeparator()
|
1220
|
-
action = ctxMenu
|
1204
|
+
action = qtAddAction(ctxMenu, self.tr("Ignore Word"))
|
1221
1205
|
action.triggered.connect(qtLambda(self._addWord, word, block, False))
|
1222
|
-
action = ctxMenu
|
1206
|
+
action = qtAddAction(ctxMenu, self.tr("Add Word to Dictionary"))
|
1223
1207
|
action.triggered.connect(qtLambda(self._addWord, word, block, True))
|
1224
1208
|
|
1225
1209
|
# Execute the context menu
|
1226
|
-
|
1210
|
+
if viewport := self.viewport():
|
1211
|
+
ctxMenu.exec(viewport.mapToGlobal(pos))
|
1212
|
+
|
1227
1213
|
ctxMenu.setParent(None)
|
1228
1214
|
|
1229
1215
|
return
|
@@ -1254,7 +1240,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1254
1240
|
"""Process the word counter's finished signal."""
|
1255
1241
|
if self._docHandle and self._nwItem:
|
1256
1242
|
logger.debug("Updating word count")
|
1257
|
-
|
1243
|
+
mCount = cCount if CONFIG.useCharCount else wCount
|
1244
|
+
needsRefresh = mCount != self._nwItem.mainCount
|
1258
1245
|
self._nwItem.setCharCount(cCount)
|
1259
1246
|
self._nwItem.setWordCount(wCount)
|
1260
1247
|
self._nwItem.setParaCount(pCount)
|
@@ -1262,7 +1249,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1262
1249
|
self._nwItem.notifyToRefresh()
|
1263
1250
|
if not self.textCursor().hasSelection():
|
1264
1251
|
# Selection counter should take precedence (#2155)
|
1265
|
-
self.docFooter.
|
1252
|
+
self.docFooter.updateMainCount(mCount, False)
|
1266
1253
|
return
|
1267
1254
|
|
1268
1255
|
@pyqtSlot()
|
@@ -1275,29 +1262,24 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1275
1262
|
self._timerSel.start()
|
1276
1263
|
else:
|
1277
1264
|
self._timerSel.stop()
|
1278
|
-
self.docFooter.
|
1265
|
+
self.docFooter.updateMainCount(0, False)
|
1279
1266
|
return
|
1280
1267
|
|
1281
1268
|
@pyqtSlot()
|
1282
1269
|
def _runSelCounter(self) -> None:
|
1283
1270
|
"""Update the selection word count."""
|
1284
|
-
if self._docHandle
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
return
|
1290
|
-
|
1291
|
-
SHARED.runInThreadPool(self._wCounterSel)
|
1292
|
-
|
1271
|
+
if self._docHandle:
|
1272
|
+
if self._wCounterSel.isRunning():
|
1273
|
+
logger.debug("Selection word counter is busy")
|
1274
|
+
return
|
1275
|
+
SHARED.runInThreadPool(self._wCounterSel)
|
1293
1276
|
return
|
1294
1277
|
|
1295
1278
|
@pyqtSlot(int, int, int)
|
1296
1279
|
def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
|
1297
1280
|
"""Update the counts on the counter's finished signal."""
|
1298
1281
|
if self._docHandle and self._nwItem:
|
1299
|
-
|
1300
|
-
self.docFooter.updateWordCount(wCount, True)
|
1282
|
+
self.docFooter.updateMainCount(cCount if CONFIG.useCharCount else wCount, True)
|
1301
1283
|
self._timerSel.stop()
|
1302
1284
|
return
|
1303
1285
|
|
@@ -1347,6 +1329,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1347
1329
|
self.beginSearch()
|
1348
1330
|
return
|
1349
1331
|
|
1332
|
+
prevFocus = QApplication.focusWidget() or self
|
1350
1333
|
resS, resE = self.findAllOccurences()
|
1351
1334
|
if len(resS) == 0 and self._docHandle:
|
1352
1335
|
self.docSearch.setResultCount(0, 0)
|
@@ -1355,7 +1338,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1355
1338
|
self.requestNextDocument.emit(self._docHandle, CONFIG.searchLoop)
|
1356
1339
|
QApplication.processEvents()
|
1357
1340
|
self.beginSearch()
|
1358
|
-
|
1341
|
+
prevFocus.setFocus()
|
1359
1342
|
return
|
1360
1343
|
|
1361
1344
|
cursor = self.textCursor()
|
@@ -1375,7 +1358,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1375
1358
|
self.requestNextDocument.emit(self._docHandle, CONFIG.searchLoop)
|
1376
1359
|
QApplication.processEvents()
|
1377
1360
|
self.beginSearch()
|
1378
|
-
|
1361
|
+
prevFocus.setFocus()
|
1379
1362
|
return
|
1380
1363
|
else:
|
1381
1364
|
resIdx = 0 if doLoop else maxIdx
|
@@ -1552,10 +1535,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1552
1535
|
if fLen == min(numA, numB):
|
1553
1536
|
cursor.beginEditBlock()
|
1554
1537
|
cursor.setPosition(posS)
|
1555
|
-
for
|
1538
|
+
for _ in range(fLen):
|
1556
1539
|
cursor.deletePreviousChar()
|
1557
1540
|
cursor.setPosition(posE)
|
1558
|
-
for
|
1541
|
+
for _ in range(fLen):
|
1559
1542
|
cursor.deletePreviousChar()
|
1560
1543
|
cursor.endEditBlock()
|
1561
1544
|
|
@@ -1569,8 +1552,10 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1569
1552
|
|
1570
1553
|
return
|
1571
1554
|
|
1572
|
-
def _wrapSelection(
|
1573
|
-
|
1555
|
+
def _wrapSelection(
|
1556
|
+
self, before: str, after: str | None = None, pos: int | None = None,
|
1557
|
+
select: _SelectAction = _SelectAction.NO_DECISION
|
1558
|
+
) -> None:
|
1574
1559
|
"""Wrap the selected text in whatever is in tBefore and tAfter.
|
1575
1560
|
If there is no selection, the autoSelect setting decides the
|
1576
1561
|
action. AutoSelect will select the word under the cursor before
|
@@ -1965,7 +1950,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
1965
1950
|
exist = False
|
1966
1951
|
cPos = cursor.selectionStart() - block.position()
|
1967
1952
|
tExist = SHARED.project.index.checkThese(tBits, self._docHandle)
|
1968
|
-
for sTag, sPos, sExist in zip(
|
1953
|
+
for sTag, sPos, sExist in zip(
|
1954
|
+
reversed(tBits), reversed(tPos), reversed(tExist), strict=False
|
1955
|
+
):
|
1969
1956
|
if cPos >= sPos:
|
1970
1957
|
# The cursor is between the start of two tags
|
1971
1958
|
if cPos <= sPos + len(sTag):
|
@@ -2021,9 +2008,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2021
2008
|
sPos = cPos
|
2022
2009
|
for i in range(cPos - bPos):
|
2023
2010
|
sPos = cPos - i - 1
|
2024
|
-
cOne = self._qDocument.characterAt(sPos)
|
2025
|
-
cTwo = self._qDocument.characterAt(sPos - 1)
|
2026
|
-
if not (cOne.isalnum() or cOne in apos and cTwo.isalnum()):
|
2011
|
+
cOne = str(self._qDocument.characterAt(sPos))
|
2012
|
+
cTwo = str(self._qDocument.characterAt(sPos - 1))
|
2013
|
+
if not (cOne.isalnum() or (cOne in apos and cTwo.isalnum())):
|
2027
2014
|
sPos += 1
|
2028
2015
|
break
|
2029
2016
|
|
@@ -2031,9 +2018,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2031
2018
|
ePos = cPos
|
2032
2019
|
for i in range(bPos + bLen - cPos):
|
2033
2020
|
ePos = cPos + i
|
2034
|
-
cOne = self._qDocument.characterAt(ePos)
|
2035
|
-
cTwo = self._qDocument.characterAt(ePos + 1)
|
2036
|
-
if not (cOne.isalnum() or cOne in apos and cTwo.isalnum()):
|
2021
|
+
cOne = str(self._qDocument.characterAt(ePos))
|
2022
|
+
cTwo = str(self._qDocument.characterAt(ePos + 1))
|
2023
|
+
if not (cOne.isalnum() or (cOne in apos and cTwo.isalnum())):
|
2037
2024
|
break
|
2038
2025
|
|
2039
2026
|
if ePos - sPos <= 0:
|
@@ -2048,8 +2035,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2048
2035
|
|
2049
2036
|
return cursor
|
2050
2037
|
|
2051
|
-
def _makeSelection(
|
2052
|
-
|
2038
|
+
def _makeSelection(
|
2039
|
+
self, mode: QTextCursor.SelectionType, cursor: QTextCursor | None = None
|
2040
|
+
) -> None:
|
2053
2041
|
"""Select text based on selection mode."""
|
2054
2042
|
if cursor is None:
|
2055
2043
|
cursor = self.textCursor()
|
@@ -2089,13 +2077,13 @@ class GuiDocEditor(QPlainTextEdit):
|
|
2089
2077
|
return
|
2090
2078
|
|
2091
2079
|
|
2092
|
-
class
|
2093
|
-
"""GuiWidget:
|
2080
|
+
class CommandCompleter(QMenu):
|
2081
|
+
"""GuiWidget: Command Completer Menu
|
2094
2082
|
|
2095
2083
|
This is a context menu with options populated from the user's
|
2096
|
-
defined tags. It also helps to type the meta data keyword
|
2097
|
-
line starting with
|
2098
|
-
every keystroke on a line starting with
|
2084
|
+
defined tags and keys. It also helps to type the meta data keyword
|
2085
|
+
on a new line starting with @ or %. The update functions should be
|
2086
|
+
called on every keystroke on a line starting with @ or %.
|
2099
2087
|
"""
|
2100
2088
|
|
2101
2089
|
complete = pyqtSignal(int, int, str)
|
@@ -2104,7 +2092,7 @@ class MetaCompleter(QMenu):
|
|
2104
2092
|
super().__init__(parent=parent)
|
2105
2093
|
return
|
2106
2094
|
|
2107
|
-
def
|
2095
|
+
def updateMetaText(self, text: str, pos: int) -> bool:
|
2108
2096
|
"""Update the menu options based on the line of text."""
|
2109
2097
|
self.clear()
|
2110
2098
|
kw, sep, _ = text.partition(":")
|
@@ -2112,7 +2100,7 @@ class MetaCompleter(QMenu):
|
|
2112
2100
|
offset = 0
|
2113
2101
|
length = len(kw.rstrip())
|
2114
2102
|
suffix = "" if sep else ":"
|
2115
|
-
options =
|
2103
|
+
options = sorted(filter(
|
2116
2104
|
lambda x: x.startswith(kw.rstrip()), nwKeyWords.VALID_KEYS
|
2117
2105
|
))
|
2118
2106
|
else:
|
@@ -2124,7 +2112,7 @@ class MetaCompleter(QMenu):
|
|
2124
2112
|
offset = tPos[index] if lookup else pos
|
2125
2113
|
length = len(lookup)
|
2126
2114
|
suffix = ""
|
2127
|
-
options =
|
2115
|
+
options = sorted(filter(
|
2128
2116
|
lambda x: lookup in x.lower(), SHARED.project.index.getClassTags(
|
2129
2117
|
nwKeyWords.KEY_CLASS.get(kw.strip())
|
2130
2118
|
)
|
@@ -2133,13 +2121,57 @@ class MetaCompleter(QMenu):
|
|
2133
2121
|
if not options:
|
2134
2122
|
return False
|
2135
2123
|
|
2136
|
-
for value in
|
2124
|
+
for value in options:
|
2137
2125
|
rep = value + suffix
|
2138
|
-
action = self
|
2126
|
+
action = qtAddAction(self, value)
|
2139
2127
|
action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
|
2140
2128
|
|
2141
2129
|
return True
|
2142
2130
|
|
2131
|
+
def updateCommentText(self, text: str, pos: int) -> bool:
|
2132
|
+
"""Update the menu options based on the line of text."""
|
2133
|
+
self.clear()
|
2134
|
+
cmd, sep, _ = text.partition(":")
|
2135
|
+
if pos <= len(cmd):
|
2136
|
+
clean = text[1:].lstrip()[:6].lower()
|
2137
|
+
if clean[:6] == "story.":
|
2138
|
+
pre, _, key = cmd.partition(".")
|
2139
|
+
offset = len(pre) + 1
|
2140
|
+
length = len(key)
|
2141
|
+
suffix = "" if sep else ": "
|
2142
|
+
options = sorted(filter(
|
2143
|
+
lambda x: x.startswith(key.rstrip()),
|
2144
|
+
SHARED.project.index.getStoryKeys(),
|
2145
|
+
))
|
2146
|
+
elif clean[:5] == "note.":
|
2147
|
+
pre, _, key = cmd.partition(".")
|
2148
|
+
offset = len(pre) + 1
|
2149
|
+
length = len(key)
|
2150
|
+
suffix = "" if sep else ": "
|
2151
|
+
options = sorted(filter(
|
2152
|
+
lambda x: x.startswith(key.rstrip()),
|
2153
|
+
SHARED.project.index.getNoteKeys(),
|
2154
|
+
))
|
2155
|
+
elif pos < 12:
|
2156
|
+
offset = 0
|
2157
|
+
length = len(cmd.rstrip())
|
2158
|
+
suffix = ""
|
2159
|
+
options = list(filter(
|
2160
|
+
lambda x: x.startswith(cmd.rstrip()),
|
2161
|
+
["%Synopsis: ", "%Short: ", "%Story", "%Note"],
|
2162
|
+
))
|
2163
|
+
else:
|
2164
|
+
return False
|
2165
|
+
|
2166
|
+
if options:
|
2167
|
+
for value in options:
|
2168
|
+
rep = value + suffix
|
2169
|
+
action = qtAddAction(self, rep.rstrip(":. "))
|
2170
|
+
action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
|
2171
|
+
return True
|
2172
|
+
|
2173
|
+
return False
|
2174
|
+
|
2143
2175
|
##
|
2144
2176
|
# Events
|
2145
2177
|
##
|
@@ -2175,13 +2207,10 @@ class BackgroundWordCounter(QRunnable):
|
|
2175
2207
|
|
2176
2208
|
def __init__(self, docEditor: GuiDocEditor, forSelection: bool = False) -> None:
|
2177
2209
|
super().__init__()
|
2178
|
-
|
2179
2210
|
self._docEditor = docEditor
|
2180
2211
|
self._forSelection = forSelection
|
2181
2212
|
self._isRunning = False
|
2182
|
-
|
2183
2213
|
self.signals = BackgroundWordCounterSignals()
|
2184
|
-
|
2185
2214
|
return
|
2186
2215
|
|
2187
2216
|
def isRunning(self) -> bool:
|
@@ -2215,9 +2244,9 @@ class BackgroundWordCounterSignals(QObject):
|
|
2215
2244
|
class TextAutoReplace:
|
2216
2245
|
|
2217
2246
|
__slots__ = (
|
2218
|
-
"
|
2219
|
-
"
|
2220
|
-
"
|
2247
|
+
"_doPadAfter", "_doPadBefore", "_padAfter", "_padBefore", "_padChar",
|
2248
|
+
"_quoteDC", "_quoteDO", "_quoteSC", "_quoteSO", "_replaceDQuote",
|
2249
|
+
"_replaceDash", "_replaceDots", "_replaceSQuote",
|
2221
2250
|
)
|
2222
2251
|
|
2223
2252
|
def __init__(self) -> None:
|
@@ -2352,7 +2381,6 @@ class GuiDocToolBar(QWidget):
|
|
2352
2381
|
logger.debug("Create: GuiDocToolBar")
|
2353
2382
|
|
2354
2383
|
iSz = SHARED.theme.baseIconSize
|
2355
|
-
cM = CONFIG.pxInt(4)
|
2356
2384
|
self.setContentsMargins(0, 0, 0, 0)
|
2357
2385
|
|
2358
2386
|
# General Buttons
|
@@ -2425,7 +2453,7 @@ class GuiDocToolBar(QWidget):
|
|
2425
2453
|
self.outerBox.addWidget(self.tbBoldMD)
|
2426
2454
|
self.outerBox.addWidget(self.tbItalicMD)
|
2427
2455
|
self.outerBox.addWidget(self.tbStrikeMD)
|
2428
|
-
self.outerBox.addSpacing(
|
2456
|
+
self.outerBox.addSpacing(4)
|
2429
2457
|
self.outerBox.addWidget(self.tbBold)
|
2430
2458
|
self.outerBox.addWidget(self.tbItalic)
|
2431
2459
|
self.outerBox.addWidget(self.tbStrike)
|
@@ -2433,8 +2461,8 @@ class GuiDocToolBar(QWidget):
|
|
2433
2461
|
self.outerBox.addWidget(self.tbMark)
|
2434
2462
|
self.outerBox.addWidget(self.tbSuperscript)
|
2435
2463
|
self.outerBox.addWidget(self.tbSubscript)
|
2436
|
-
self.outerBox.setContentsMargins(
|
2437
|
-
self.outerBox.setSpacing(
|
2464
|
+
self.outerBox.setContentsMargins(4, 4, 4, 4)
|
2465
|
+
self.outerBox.setSpacing(4)
|
2438
2466
|
|
2439
2467
|
self.setLayout(self.outerBox)
|
2440
2468
|
self.updateTheme()
|
@@ -2448,15 +2476,17 @@ class GuiDocToolBar(QWidget):
|
|
2448
2476
|
|
2449
2477
|
def updateTheme(self) -> None:
|
2450
2478
|
"""Initialise GUI elements that depend on specific settings."""
|
2451
|
-
|
2452
|
-
|
2453
|
-
palette.
|
2454
|
-
palette.setColor(QPalette.ColorRole.
|
2479
|
+
syntax = SHARED.theme.syntaxTheme
|
2480
|
+
|
2481
|
+
palette = self.palette()
|
2482
|
+
palette.setColor(QPalette.ColorRole.Window, syntax.back)
|
2483
|
+
palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
|
2484
|
+
palette.setColor(QPalette.ColorRole.Text, syntax.text)
|
2455
2485
|
self.setPalette(palette)
|
2456
2486
|
|
2457
|
-
self.tbBoldMD.setThemeIcon("fmt_bold
|
2458
|
-
self.tbItalicMD.setThemeIcon("fmt_italic
|
2459
|
-
self.tbStrikeMD.setThemeIcon("fmt_strike
|
2487
|
+
self.tbBoldMD.setThemeIcon("fmt_bold", "orange")
|
2488
|
+
self.tbItalicMD.setThemeIcon("fmt_italic", "orange")
|
2489
|
+
self.tbStrikeMD.setThemeIcon("fmt_strike", "orange")
|
2460
2490
|
self.tbBold.setThemeIcon("fmt_bold")
|
2461
2491
|
self.tbItalic.setThemeIcon("fmt_italic")
|
2462
2492
|
self.tbStrike.setThemeIcon("fmt_strike")
|
@@ -2483,7 +2513,6 @@ class GuiDocEditSearch(QFrame):
|
|
2483
2513
|
self.docEditor = docEditor
|
2484
2514
|
|
2485
2515
|
iSz = SHARED.theme.baseIconSize
|
2486
|
-
mPx = CONFIG.pxInt(6)
|
2487
2516
|
|
2488
2517
|
self.setContentsMargins(0, 0, 0, 0)
|
2489
2518
|
self.setAutoFillBackground(True)
|
@@ -2509,7 +2538,7 @@ class GuiDocEditSearch(QFrame):
|
|
2509
2538
|
self.searchOpt.setContentsMargins(0, 0, 0, 0)
|
2510
2539
|
|
2511
2540
|
self.searchLabel = QLabel(self.tr("Search"), self)
|
2512
|
-
self.searchLabel.setIndent(
|
2541
|
+
self.searchLabel.setIndent(6)
|
2513
2542
|
|
2514
2543
|
self.resultLabel = QLabel("?/?", self)
|
2515
2544
|
|
@@ -2586,12 +2615,11 @@ class GuiDocEditSearch(QFrame):
|
|
2586
2615
|
self.mainBox.setColumnStretch(3, 0)
|
2587
2616
|
self.mainBox.setColumnStretch(4, 0)
|
2588
2617
|
self.mainBox.setColumnStretch(5, 0)
|
2589
|
-
self.mainBox.setSpacing(
|
2590
|
-
self.mainBox.setContentsMargins(
|
2618
|
+
self.mainBox.setSpacing(2)
|
2619
|
+
self.mainBox.setContentsMargins(6, 6, 6, 6)
|
2591
2620
|
|
2592
|
-
|
2593
|
-
self.
|
2594
|
-
self.replaceBox.setFixedWidth(boxWidth)
|
2621
|
+
self.searchBox.setFixedWidth(200)
|
2622
|
+
self.replaceBox.setFixedWidth(200)
|
2595
2623
|
self.replaceBox.setVisible(False)
|
2596
2624
|
self.replaceButton.setVisible(False)
|
2597
2625
|
self.adjustSize()
|
@@ -2693,10 +2721,11 @@ class GuiDocEditSearch(QFrame):
|
|
2693
2721
|
|
2694
2722
|
def updateTheme(self) -> None:
|
2695
2723
|
"""Update theme elements."""
|
2696
|
-
|
2697
|
-
|
2698
|
-
self.
|
2699
|
-
self.
|
2724
|
+
palette = QApplication.palette()
|
2725
|
+
|
2726
|
+
self.setPalette(palette)
|
2727
|
+
self.searchBox.setPalette(palette)
|
2728
|
+
self.replaceBox.setPalette(palette)
|
2700
2729
|
|
2701
2730
|
# Set icons
|
2702
2731
|
self.toggleCase.setIcon(SHARED.theme.getIcon("search_case"))
|
@@ -2706,31 +2735,13 @@ class GuiDocEditSearch(QFrame):
|
|
2706
2735
|
self.toggleProject.setIcon(SHARED.theme.getIcon("search_project"))
|
2707
2736
|
self.toggleMatchCap.setIcon(SHARED.theme.getIcon("search_preserve"))
|
2708
2737
|
self.cancelSearch.setIcon(SHARED.theme.getIcon("search_cancel"))
|
2709
|
-
self.searchButton.setThemeIcon("search")
|
2710
|
-
self.replaceButton.setThemeIcon("search_replace")
|
2738
|
+
self.searchButton.setThemeIcon("search", "green")
|
2739
|
+
self.replaceButton.setThemeIcon("search_replace", "green")
|
2711
2740
|
|
2712
2741
|
# Set stylesheets
|
2713
2742
|
self.searchOpt.setStyleSheet("QToolBar {padding: 0;}")
|
2714
2743
|
self.showReplace.setStyleSheet("QToolButton {border: none; background: transparent;}")
|
2715
2744
|
|
2716
|
-
# Construct Box Colours
|
2717
|
-
qPalette = self.searchBox.palette()
|
2718
|
-
baseCol = qPalette.base().color()
|
2719
|
-
rCol = baseCol.redF() + 0.1
|
2720
|
-
gCol = baseCol.greenF() - 0.1
|
2721
|
-
bCol = baseCol.blueF() - 0.1
|
2722
|
-
|
2723
|
-
mCol = max(rCol, gCol, bCol, 1.0)
|
2724
|
-
errCol = QColor()
|
2725
|
-
errCol.setRedF(rCol/mCol)
|
2726
|
-
errCol.setGreenF(gCol/mCol)
|
2727
|
-
errCol.setBlueF(bCol/mCol)
|
2728
|
-
|
2729
|
-
self.rxCol = {
|
2730
|
-
True: baseCol,
|
2731
|
-
False: errCol
|
2732
|
-
}
|
2733
|
-
|
2734
2745
|
return
|
2735
2746
|
|
2736
2747
|
def cycleFocus(self) -> bool:
|
@@ -2747,7 +2758,7 @@ class GuiDocEditSearch(QFrame):
|
|
2747
2758
|
|
2748
2759
|
def anyFocus(self) -> bool:
|
2749
2760
|
"""Return True if any of the input boxes have focus."""
|
2750
|
-
return self.
|
2761
|
+
return self.hasFocus() or self.isAncestorOf(QApplication.focusWidget())
|
2751
2762
|
|
2752
2763
|
##
|
2753
2764
|
# Public Slots
|
@@ -2831,9 +2842,12 @@ class GuiDocEditSearch(QFrame):
|
|
2831
2842
|
"""Highlight the search box to indicate the search string is or
|
2832
2843
|
isn't valid. Take the colour from the replace box.
|
2833
2844
|
"""
|
2834
|
-
|
2835
|
-
|
2836
|
-
|
2845
|
+
palette = self.replaceBox.palette()
|
2846
|
+
palette.setColor(
|
2847
|
+
QPalette.ColorRole.Text,
|
2848
|
+
palette.text().color() if isValid else SHARED.theme.errorText
|
2849
|
+
)
|
2850
|
+
self.searchBox.setPalette(palette)
|
2837
2851
|
return
|
2838
2852
|
|
2839
2853
|
|
@@ -2859,13 +2873,12 @@ class GuiDocEditHeader(QWidget):
|
|
2859
2873
|
|
2860
2874
|
iPx = SHARED.theme.baseIconHeight
|
2861
2875
|
iSz = SHARED.theme.baseIconSize
|
2862
|
-
mPx = CONFIG.pxInt(4)
|
2863
2876
|
|
2864
2877
|
# Main Widget Settings
|
2865
2878
|
self.setAutoFillBackground(True)
|
2866
2879
|
|
2867
2880
|
# Title Label
|
2868
|
-
self.itemTitle =
|
2881
|
+
self.itemTitle = NColorLabel("", self, faded=SHARED.theme.fadedText)
|
2869
2882
|
self.itemTitle.setMargin(0)
|
2870
2883
|
self.itemTitle.setContentsMargins(0, 0, 0, 0)
|
2871
2884
|
self.itemTitle.setAutoFillBackground(True)
|
@@ -2906,13 +2919,13 @@ class GuiDocEditHeader(QWidget):
|
|
2906
2919
|
self.outerBox.addWidget(self.tbButton, 0)
|
2907
2920
|
self.outerBox.addWidget(self.outlineButton, 0)
|
2908
2921
|
self.outerBox.addWidget(self.searchButton, 0)
|
2909
|
-
self.outerBox.addSpacing(
|
2922
|
+
self.outerBox.addSpacing(4)
|
2910
2923
|
self.outerBox.addWidget(self.itemTitle, 1)
|
2911
|
-
self.outerBox.addSpacing(
|
2924
|
+
self.outerBox.addSpacing(4)
|
2912
2925
|
self.outerBox.addSpacing(iPx)
|
2913
2926
|
self.outerBox.addWidget(self.minmaxButton, 0)
|
2914
2927
|
self.outerBox.addWidget(self.closeButton, 0)
|
2915
|
-
self.outerBox.setContentsMargins(
|
2928
|
+
self.outerBox.setContentsMargins(4, 4, 4, 4)
|
2916
2929
|
self.outerBox.setSpacing(0)
|
2917
2930
|
|
2918
2931
|
self.setLayout(self.outerBox)
|
@@ -2923,7 +2936,7 @@ class GuiDocEditHeader(QWidget):
|
|
2923
2936
|
# Fix Margins and Size
|
2924
2937
|
# This is needed for high DPI systems. See issue #499.
|
2925
2938
|
self.setContentsMargins(0, 0, 0, 0)
|
2926
|
-
self.setMinimumHeight(iPx +
|
2939
|
+
self.setMinimumHeight(iPx + 8)
|
2927
2940
|
|
2928
2941
|
self.updateFont()
|
2929
2942
|
self.updateTheme()
|
@@ -2956,7 +2969,7 @@ class GuiDocEditHeader(QWidget):
|
|
2956
2969
|
tStart = time()
|
2957
2970
|
self.outlineMenu.clear()
|
2958
2971
|
for number, text in data.items():
|
2959
|
-
action = self.outlineMenu
|
2972
|
+
action = qtAddAction(self.outlineMenu, text)
|
2960
2973
|
action.triggered.connect(qtLambda(self._gotoBlock, number))
|
2961
2974
|
self._docOutline = data
|
2962
2975
|
logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart))
|
@@ -2970,11 +2983,11 @@ class GuiDocEditHeader(QWidget):
|
|
2970
2983
|
|
2971
2984
|
def updateTheme(self) -> None:
|
2972
2985
|
"""Update theme elements."""
|
2973
|
-
self.tbButton.setThemeIcon("
|
2974
|
-
self.outlineButton.setThemeIcon("list")
|
2975
|
-
self.searchButton.setThemeIcon("search")
|
2976
|
-
self.minmaxButton.setThemeIcon("maximise")
|
2977
|
-
self.closeButton.setThemeIcon("close")
|
2986
|
+
self.tbButton.setThemeIcon("fmt_toolbar", "blue")
|
2987
|
+
self.outlineButton.setThemeIcon("list", "blue")
|
2988
|
+
self.searchButton.setThemeIcon("search", "blue")
|
2989
|
+
self.minmaxButton.setThemeIcon("maximise", "blue")
|
2990
|
+
self.closeButton.setThemeIcon("close", "red")
|
2978
2991
|
|
2979
2992
|
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
2980
2993
|
self.tbButton.setStyleSheet(buttonStyle)
|
@@ -2983,18 +2996,19 @@ class GuiDocEditHeader(QWidget):
|
|
2983
2996
|
self.minmaxButton.setStyleSheet(buttonStyle)
|
2984
2997
|
self.closeButton.setStyleSheet(buttonStyle)
|
2985
2998
|
|
2986
|
-
self.
|
2999
|
+
self.matchColors()
|
2987
3000
|
|
2988
3001
|
return
|
2989
3002
|
|
2990
|
-
def
|
3003
|
+
def matchColors(self) -> None:
|
2991
3004
|
"""Update the colours of the widget to match those of the syntax
|
2992
3005
|
theme rather than the main GUI.
|
2993
3006
|
"""
|
2994
|
-
|
2995
|
-
palette.
|
2996
|
-
palette.setColor(QPalette.ColorRole.
|
2997
|
-
palette.setColor(QPalette.ColorRole.
|
3007
|
+
syntax = SHARED.theme.syntaxTheme
|
3008
|
+
palette = self.palette()
|
3009
|
+
palette.setColor(QPalette.ColorRole.Window, syntax.back)
|
3010
|
+
palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
|
3011
|
+
palette.setColor(QPalette.ColorRole.Text, syntax.text)
|
2998
3012
|
self.setPalette(palette)
|
2999
3013
|
self.itemTitle.setTextColors(
|
3000
3014
|
color=palette.windowText().color(), faded=SHARED.theme.fadedText
|
@@ -3047,7 +3061,7 @@ class GuiDocEditHeader(QWidget):
|
|
3047
3061
|
@pyqtSlot(bool)
|
3048
3062
|
def _focusModeChanged(self, focusMode: bool) -> None:
|
3049
3063
|
"""Update minimise/maximise icon of the Focus Mode button."""
|
3050
|
-
self.minmaxButton.setThemeIcon("minimise" if focusMode else "maximise")
|
3064
|
+
self.minmaxButton.setThemeIcon("minimise" if focusMode else "maximise", "blue")
|
3051
3065
|
return
|
3052
3066
|
|
3053
3067
|
##
|
@@ -3080,14 +3094,11 @@ class GuiDocEditFooter(QWidget):
|
|
3080
3094
|
|
3081
3095
|
iPx = round(0.9*SHARED.theme.baseIconHeight)
|
3082
3096
|
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
3083
|
-
mPx = CONFIG.pxInt(8)
|
3084
|
-
bSp = CONFIG.pxInt(4)
|
3085
|
-
hSp = CONFIG.pxInt(6)
|
3086
3097
|
|
3087
3098
|
# Cached Translations
|
3099
|
+
self.initSettings()
|
3088
3100
|
self._trLineCount = self.tr("Line: {0} ({1})")
|
3089
|
-
self.
|
3090
|
-
self._trSelectCount = self.tr("Words: {0} selected")
|
3101
|
+
self._trSelectCount = self.tr("Selected: {0}")
|
3091
3102
|
|
3092
3103
|
# Main Widget Settings
|
3093
3104
|
self.setContentsMargins(0, 0, 0, 0)
|
@@ -3095,72 +3106,60 @@ class GuiDocEditFooter(QWidget):
|
|
3095
3106
|
|
3096
3107
|
# Status
|
3097
3108
|
self.statusIcon = QLabel("", self)
|
3098
|
-
self.statusIcon.setContentsMargins(0, 0, 0, 0)
|
3099
3109
|
self.statusIcon.setFixedHeight(iPx)
|
3100
3110
|
self.statusIcon.setAlignment(QtAlignLeftTop)
|
3101
3111
|
|
3102
|
-
self.statusText = QLabel(
|
3103
|
-
self.statusText.setIndent(0)
|
3104
|
-
self.statusText.setMargin(0)
|
3105
|
-
self.statusText.setContentsMargins(0, 0, 0, 0)
|
3112
|
+
self.statusText = QLabel("", self)
|
3106
3113
|
self.statusText.setAutoFillBackground(True)
|
3107
3114
|
self.statusText.setFixedHeight(fPx)
|
3108
3115
|
self.statusText.setAlignment(QtAlignLeftTop)
|
3109
3116
|
|
3110
3117
|
# Lines
|
3111
3118
|
self.linesIcon = QLabel("", self)
|
3112
|
-
self.linesIcon.setContentsMargins(0, 0, 0, 0)
|
3113
3119
|
self.linesIcon.setFixedHeight(iPx)
|
3114
3120
|
self.linesIcon.setAlignment(QtAlignLeftTop)
|
3115
3121
|
|
3116
3122
|
self.linesText = QLabel("", self)
|
3117
|
-
self.linesText.setIndent(0)
|
3118
|
-
self.linesText.setMargin(0)
|
3119
|
-
self.linesText.setContentsMargins(0, 0, 0, 0)
|
3120
3123
|
self.linesText.setAutoFillBackground(True)
|
3121
3124
|
self.linesText.setFixedHeight(fPx)
|
3122
3125
|
self.linesText.setAlignment(QtAlignLeftTop)
|
3123
3126
|
|
3124
3127
|
# Words
|
3125
3128
|
self.wordsIcon = QLabel("", self)
|
3126
|
-
self.wordsIcon.setContentsMargins(0, 0, 0, 0)
|
3127
3129
|
self.wordsIcon.setFixedHeight(iPx)
|
3128
3130
|
self.wordsIcon.setAlignment(QtAlignLeftTop)
|
3129
3131
|
|
3130
3132
|
self.wordsText = QLabel("", self)
|
3131
|
-
self.wordsText.setIndent(0)
|
3132
|
-
self.wordsText.setMargin(0)
|
3133
|
-
self.wordsText.setContentsMargins(0, 0, 0, 0)
|
3134
3133
|
self.wordsText.setAutoFillBackground(True)
|
3135
3134
|
self.wordsText.setFixedHeight(fPx)
|
3136
3135
|
self.wordsText.setAlignment(QtAlignLeftTop)
|
3137
3136
|
|
3138
3137
|
# Assemble Layout
|
3139
3138
|
self.outerBox = QHBoxLayout()
|
3140
|
-
self.outerBox.setSpacing(
|
3139
|
+
self.outerBox.setSpacing(4)
|
3141
3140
|
self.outerBox.addWidget(self.statusIcon)
|
3142
3141
|
self.outerBox.addWidget(self.statusText)
|
3143
3142
|
self.outerBox.addStretch(1)
|
3144
3143
|
self.outerBox.addWidget(self.linesIcon)
|
3145
3144
|
self.outerBox.addWidget(self.linesText)
|
3146
|
-
self.outerBox.addSpacing(
|
3145
|
+
self.outerBox.addSpacing(6)
|
3147
3146
|
self.outerBox.addWidget(self.wordsIcon)
|
3148
3147
|
self.outerBox.addWidget(self.wordsText)
|
3149
|
-
self.outerBox.setContentsMargins(
|
3148
|
+
self.outerBox.setContentsMargins(8, 8, 8, 8)
|
3150
3149
|
|
3151
3150
|
self.setLayout(self.outerBox)
|
3152
3151
|
|
3153
3152
|
# Fix Margins and Size
|
3154
3153
|
# This is needed for high DPI systems. See issue #499.
|
3155
3154
|
self.setContentsMargins(0, 0, 0, 0)
|
3156
|
-
self.setMinimumHeight(fPx +
|
3155
|
+
self.setMinimumHeight(fPx + 16)
|
3157
3156
|
|
3158
3157
|
# Fix the Colours
|
3159
3158
|
self.updateFont()
|
3160
3159
|
self.updateTheme()
|
3161
3160
|
|
3162
3161
|
# Initialise Info
|
3163
|
-
self.
|
3162
|
+
self.updateMainCount(0, False)
|
3164
3163
|
|
3165
3164
|
logger.debug("Ready: GuiDocEditFooter")
|
3166
3165
|
|
@@ -3170,6 +3169,13 @@ class GuiDocEditFooter(QWidget):
|
|
3170
3169
|
# Methods
|
3171
3170
|
##
|
3172
3171
|
|
3172
|
+
def initSettings(self) -> None:
|
3173
|
+
"""Apply user settings."""
|
3174
|
+
self._trMainCount = trStats(nwLabels.STATS_DISPLAY[
|
3175
|
+
nwStats.CHARS if CONFIG.useCharCount else nwStats.WORDS
|
3176
|
+
])
|
3177
|
+
return
|
3178
|
+
|
3173
3179
|
def updateFont(self) -> None:
|
3174
3180
|
"""Update the font settings."""
|
3175
3181
|
self.setFont(SHARED.theme.guiFont)
|
@@ -3181,19 +3187,21 @@ class GuiDocEditFooter(QWidget):
|
|
3181
3187
|
def updateTheme(self) -> None:
|
3182
3188
|
"""Update theme elements."""
|
3183
3189
|
iPx = round(0.9*SHARED.theme.baseIconHeight)
|
3184
|
-
self.linesIcon.setPixmap(SHARED.theme.getPixmap("
|
3185
|
-
self.wordsIcon.setPixmap(SHARED.theme.getPixmap("
|
3186
|
-
self.
|
3190
|
+
self.linesIcon.setPixmap(SHARED.theme.getPixmap("lines", (iPx, iPx)))
|
3191
|
+
self.wordsIcon.setPixmap(SHARED.theme.getPixmap("stats", (iPx, iPx)))
|
3192
|
+
self.matchColors()
|
3187
3193
|
return
|
3188
3194
|
|
3189
|
-
def
|
3195
|
+
def matchColors(self) -> None:
|
3190
3196
|
"""Update the colours of the widget to match those of the syntax
|
3191
3197
|
theme rather than the main GUI.
|
3192
3198
|
"""
|
3193
|
-
|
3194
|
-
|
3195
|
-
palette.
|
3196
|
-
palette.setColor(QPalette.ColorRole.
|
3199
|
+
syntax = SHARED.theme.syntaxTheme
|
3200
|
+
|
3201
|
+
palette = self.palette()
|
3202
|
+
palette.setColor(QPalette.ColorRole.Window, syntax.back)
|
3203
|
+
palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
|
3204
|
+
palette.setColor(QPalette.ColorRole.Text, syntax.text)
|
3197
3205
|
|
3198
3206
|
self.setPalette(palette)
|
3199
3207
|
self.statusText.setPalette(palette)
|
@@ -3212,7 +3220,7 @@ class GuiDocEditFooter(QWidget):
|
|
3212
3220
|
self._tItem = SHARED.project.tree[self._docHandle]
|
3213
3221
|
|
3214
3222
|
self.updateInfo()
|
3215
|
-
self.
|
3223
|
+
self.updateMainCount(0, False)
|
3216
3224
|
|
3217
3225
|
return
|
3218
3226
|
|
@@ -3234,23 +3242,24 @@ class GuiDocEditFooter(QWidget):
|
|
3234
3242
|
|
3235
3243
|
def updateLineCount(self, cursor: QTextCursor) -> None:
|
3236
3244
|
"""Update the line and document position counter."""
|
3237
|
-
|
3238
|
-
|
3239
|
-
|
3240
|
-
|
3241
|
-
self.
|
3242
|
-
|
3245
|
+
if document := cursor.document():
|
3246
|
+
cPos = cursor.position() + 1
|
3247
|
+
cLine = cursor.blockNumber() + 1
|
3248
|
+
cCount = max(document.characterCount(), 1)
|
3249
|
+
self.linesText.setText(
|
3250
|
+
self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %")
|
3251
|
+
)
|
3243
3252
|
return
|
3244
3253
|
|
3245
|
-
def
|
3246
|
-
"""Update
|
3247
|
-
if selection and
|
3248
|
-
|
3254
|
+
def updateMainCount(self, count: int, selection: bool) -> None:
|
3255
|
+
"""Update main counter information."""
|
3256
|
+
if selection and count:
|
3257
|
+
text = self._trSelectCount.format(f"{count:n}")
|
3249
3258
|
elif self._tItem:
|
3250
|
-
|
3251
|
-
|
3252
|
-
|
3259
|
+
count = self._tItem.mainCount
|
3260
|
+
diff = count - self._tItem.initCount
|
3261
|
+
text = self._trMainCount.format(f"{count:n}", f"{diff:+n}")
|
3253
3262
|
else:
|
3254
|
-
|
3255
|
-
self.wordsText.setText(
|
3263
|
+
text = self._trMainCount.format("0", "+0")
|
3264
|
+
self.wordsText.setText(text)
|
3256
3265
|
return
|