Add an optional editor toolbar (#1584)

This commit is contained in:
Veronica Berglyd Olsen
2023-11-08 23:18:06 +01:00
committed by GitHub
40 changed files with 1230 additions and 677 deletions
+2 -2
View File
@@ -77,10 +77,10 @@ Text Search Shortcuts
":kbd:`F3`", "Find the next occurrence of the search word" ":kbd:`F3`", "Find the next occurrence of the search word"
":kbd:`Ctrl+F`", "Open the search bar and search for the selected word, if any is selected" ":kbd:`Ctrl+F`", "Open the search bar and search for the selected word, if any is selected"
":kbd:`Ctrl+G`", "Find next occurrence of search word in current document" ":kbd:`Ctrl+G`", "Find the next occurrence of the search word"
":kbd:`Ctrl+H`", "Open the search tool and populate with the selected word (Mac :kbd:`Cmd+=`)" ":kbd:`Ctrl+H`", "Open the search tool and populate with the selected word (Mac :kbd:`Cmd+=`)"
":kbd:`Ctrl+Shift+1`", "Replace selected occurrence of the search word, and move to the next" ":kbd:`Ctrl+Shift+1`", "Replace selected occurrence of the search word, and move to the next"
":kbd:`Ctrl+Shift+G`", "Find previous occurrence of the search word" ":kbd:`Ctrl+Shift+G`", "Find the previous occurrence of the search word"
":kbd:`Shift+F3`", "Find the previous occurrence of the search word" ":kbd:`Shift+F3`", "Find the previous occurrence of the search word"
@@ -46,9 +46,17 @@ cross = typ_times.svg
down = typ_chevron-down.svg down = typ_chevron-down.svg
edit = typ_pencil.svg edit = typ_pencil.svg
export = typ_export.svg export = typ_export.svg
fmt_bold = nw_tb-bold.svg
fmt_italic = nw_tb-italic.svg
fmt_mode-md = nw_tb-markdown.svg
fmt_mode-sc = nw_tb-shortcode.svg
fmt_strike = nw_tb-strike.svg
fmt_subscript = nw_tb-subscript.svg
fmt_superscript = nw_tb-superscript.svg
fmt_underline = nw_tb-underline.svg
forward = typ_chevron-right.svg forward = typ_chevron-right.svg
maximise = typ_arrow-maximise.svg maximise = typ_arrow-maximise.svg
menu = typ_th-menu.svg menu = typ_th-dot-menu.svg
minimise = typ_arrow-minimise.svg minimise = typ_arrow-minimise.svg
noncheckable = mixed_input-none.svg noncheckable = mixed_input-none.svg
proj_chapter = mixed_document-chapter.svg proj_chapter = mixed_document-chapter.svg
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m1.3672 5.5562v-4.7625h1.6289q0.37252 0 0.68662 0.058435 0.32139 0.051131 0.55513 0.18991 0.24104 0.13878 0.37252 0.36522 0.13878 0.22644 0.13878 0.56975 0 0.1607-0.043826 0.32139-0.043826 0.16069-0.12417 0.29948-0.080349 0.13878-0.19721 0.24835-0.10957 0.10957-0.25565 0.1607v0.029218q0.18261 0.043827 0.33601 0.13878 0.1534 0.087653 0.27027 0.22644 0.11687 0.13878 0.18261 0.3287 0.065739 0.18261 0.065739 0.42365 0 0.36522-0.14608 0.62818-0.13878 0.26295-0.39444 0.43826-0.24835 0.168-0.58435 0.25565-0.33601 0.080349-0.72313 0.080349zm1.0737-2.8561h0.50401q0.39444 0 0.56975-0.1534 0.18261-0.1534 0.18261-0.40905 0-0.27757-0.18261-0.39444-0.18261-0.11687-0.56244-0.11687h-0.51131zm0 2.0233h0.60626q0.88383 0 0.88383-0.64279 0-0.31408-0.21914-0.44557-0.21914-0.13878-0.6647-0.13878h-0.60626z" fill="#aeaeae" stroke-opacity=".78039" stroke-width=".54782"/>
</svg>

After

Width:  |  Height:  |  Size: 1017 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m2.3084 5.5562 0.94666-4.7625h0.78646l-0.94666 4.7625z" fill="#aeaeae" stroke-opacity=".78039" stroke-width=".54615"/>
</svg>

After

Width:  |  Height:  |  Size: 277 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<g fill="#aeaeae" stroke-width="1.1">
<path d="m0.79397 2.8846-0.29357-0.21175 0.28393-0.5005-0.51975-0.23582 0.11068-0.3465 0.55825 0.1155 0.062562-0.56787h0.37057l0.062562 0.57268 0.55343-0.12032 0.1155 0.3465-0.51975 0.23582 0.28393 0.5005-0.29356 0.21175-0.385-0.42832z"/>
<path d="m2.8219 3.15-0.09999 0.37496h0.86866l-0.09374-0.37496q-0.08749-0.30622-0.16873-0.64993-0.081241-0.34372-0.16248-0.66243h-0.024997q-0.074992 0.32496-0.15623 0.66868-0.074992 0.33747-0.16248 0.64369zm-1.4749 2.0623 1.2749-4.0746h1.1062l1.2749 4.0746h-0.97489l-0.24997-0.96865h-1.2437l-0.24997 0.96865z"/>
<path d="m4.7812 2.8846-0.29357-0.21175 0.28394-0.5005-0.51975-0.23582 0.11069-0.3465 0.55825 0.1155 0.062562-0.56787h0.37056l0.062562 0.57268 0.55343-0.12032 0.1155 0.3465-0.51975 0.23582 0.28393 0.5005-0.29356 0.21175-0.385-0.42832z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 988 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<g fill="#aeaeae" stroke-width="1.3799">
<path d="m0.52917 5.2683v-4.1866h1.0272v0.37972h-0.49167v3.4272h0.49167v0.37972z"/>
<path d="m2.9 2.9219-0.077889 0.29209h0.67667l-0.073022-0.29209q-0.068154-0.23854-0.13144-0.50628-0.063285-0.26775-0.12657-0.51601h-0.019472q-0.058417 0.25314-0.1217 0.52088-0.058417 0.26288-0.12657 0.50141zm-1.1489 1.6065 0.9931-3.174h0.86166l0.9931 3.174h-0.75942l-0.19472-0.75455h-0.96876l-0.19472 0.75455z"/>
<path d="m4.7937 5.2683v-0.37972h0.49654v-3.4272h-0.49654v-0.37972h1.0272v4.1866z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 684 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m3.1785 5.5562q-0.46361 0-0.89912-0.17561-0.42848-0.17561-0.75863-0.49873l0.47766-0.55492q0.24585 0.2318 0.56195 0.37931 0.3161 0.14049 0.63219 0.14049 0.39336 0 0.59707-0.16156 0.20371-0.16156 0.20371-0.42848 0-0.14049-0.056194-0.23883-0.04917-0.098341-0.14751-0.16858-0.091316-0.077267-0.22478-0.14049-0.12644-0.063219-0.28097-0.12644l-0.63219-0.27395q-0.16858-0.070243-0.33717-0.17561-0.16156-0.10537-0.29502-0.25288-0.13346-0.14751-0.21775-0.34419-0.077268-0.19668-0.077268-0.44956 0-0.27395 0.11239-0.50575 0.11941-0.23883 0.32312-0.41444 0.20371-0.17561 0.48468-0.27395 0.288-0.098341 0.62517-0.098341 0.40039 0 0.77268 0.16156 0.37229 0.15454 0.63921 0.42146l-0.41444 0.5198q-0.22478-0.18263-0.46361-0.288-0.23883-0.10537-0.53385-0.10537-0.33014 0-0.52683 0.14751-0.19668 0.14049-0.19668 0.39336 0 0.13346 0.056194 0.2318 0.063219 0.091316 0.16156 0.16858 0.10537 0.070244 0.23883 0.13346 0.13346 0.056194 0.27395 0.11239l0.62516 0.2599q0.20371 0.084291 0.37229 0.19668 0.16858 0.11239 0.288 0.2599 0.12644 0.14751 0.19668 0.34419 0.070244 0.18966 0.070244 0.44253 0 0.28097-0.11239 0.52683-0.11239 0.24585-0.33015 0.43551-0.21073 0.18263-0.5198 0.29502-0.30205 0.10536-0.68838 0.10536zm-2.3848-2.6458h4.7625v0.52917h-4.7625z" fill="#aeaeae" stroke-opacity=".78039" stroke-width=".52683"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m2.124 3.0221-0.16749 0.58257h1.2161l-0.16749-0.58257q-0.11651-0.37867-0.22575-0.77919-0.10195-0.40052-0.2039-0.79375h-0.029128q-0.10195 0.40052-0.2039 0.80103-0.10195 0.39323-0.21846 0.7719zm-1.5948 2.5342 1.5511-4.7625h0.99037l1.5511 4.7625h-0.88842l-0.37139-1.2889h-1.6021l-0.37139 1.2889z" fill="#aeaeae" stroke-opacity=".78039" stroke-width=".54616"/>
<path d="m4.101 5.5562v-2.3812h0.75354q0.18388 0 0.3353 0.032769 0.15503 0.029128 0.2668 0.098307 0.11538 0.065538 0.17667 0.17841 0.064898 0.11287 0.064898 0.27671 0 0.17113-0.086529 0.31676-0.086529 0.14564-0.24156 0.20026v0.014565q0.19469 0.043691 0.32088 0.18569 0.12979 0.13836 0.12979 0.37867 0 0.17841-0.068503 0.30948-0.068502 0.13107-0.19108 0.21846-0.11899 0.087383-0.28483 0.13107-0.16585 0.04005-0.36054 0.04005zm0.41823-1.3981h0.29925q0.24878 0 0.35693-0.091024 0.11177-0.091025 0.11177-0.24394 0-0.17477-0.11538-0.24759-0.11538-0.072819-0.34612-0.072819h-0.30646zm0 1.0705h0.35334q0.25959 0 0.4002-0.094666 0.14061-0.098307 0.14061-0.29856 0-0.18933-0.13701-0.27308-0.13701-0.087384-0.40381-0.087384h-0.35334z" fill="#69c" stroke-opacity=".78039" stroke-width=".27174"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m2.124 3.0221-0.16749 0.58257h1.2161l-0.16749-0.58257q-0.11651-0.37867-0.22575-0.77919-0.10195-0.40052-0.2039-0.79375h-0.029128q-0.10195 0.40052-0.2039 0.80103-0.10195 0.39323-0.21846 0.7719zm-1.5948 2.5342 1.5511-4.7625h0.99037l1.5511 4.7625h-0.88842l-0.37139-1.2889h-1.6021l-0.37139 1.2889z" fill="#aeaeae" stroke-opacity=".78039" stroke-width=".54616"/>
<path d="m4.101 3.175v-2.3812h0.75352q0.18389 0 0.33531 0.032768 0.15503 0.029128 0.2668 0.098307 0.11538 0.065538 0.17667 0.17841 0.064898 0.11287 0.064898 0.27671 0 0.17113-0.086528 0.31676-0.086528 0.14564-0.24156 0.20026v0.014565q0.19469 0.043691 0.32088 0.1857 0.12979 0.13836 0.12979 0.37867 0 0.17841-0.068503 0.30948t-0.19108 0.21845q-0.11897 0.087382-0.28482 0.13107-0.16585 0.04005-0.36054 0.04005zm0.41824-1.3981h0.29924q0.24878 0 0.35693-0.091024 0.11177-0.091024 0.11177-0.24394 0-0.17477-0.11538-0.24759-0.11538-0.072818-0.34612-0.072818h-0.30646zm0 1.0705h0.35332q0.25959 0 0.4002-0.094666 0.14061-0.098307 0.14061-0.29855 0-0.18932-0.13701-0.27307-0.13701-0.087383-0.40381-0.087383h-0.35332z" fill="#69c" stroke-opacity=".78039" stroke-width=".27173"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m3.1782 5.0271q-0.36231 0-0.6547-0.1017-0.28603-0.10806-0.49579-0.33689-0.2034-0.22883-0.31782-0.58479-0.10806-0.36231-0.10806-0.87717v-2.3328h0.73733v2.3899q0 0.33689 0.057206 0.57207 0.063563 0.22883 0.17162 0.36866 0.11441 0.13984 0.26697 0.2034 0.15255 0.057206 0.34324 0.057206 0.19068 0 0.3496-0.057206 0.15891-0.063564 0.27332-0.2034 0.11441-0.13984 0.17798-0.36866 0.063564-0.23519 0.063564-0.57207v-2.3899h0.70555v2.3328q0 0.51487-0.10806 0.87717-0.10806 0.35595-0.31782 0.58479-0.2034 0.22883-0.49579 0.33689-0.28603 0.1017-0.64835 0.1017z" fill="#aeaeae" stroke-opacity=".78039" stroke-width=".47673"/>
<rect x="1.0583" y="5.2917" width="4.2333" height=".52917" fill="#aeaeae" stroke-width="1.2172"/>
</svg>

After

Width:  |  Height:  |  Size: 871 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m12 2a2.6315 2.6315 0 0 0-2.6314 2.6314 2.6315 2.6315 0 0 0 2.6314 2.6314 2.6315 2.6315 0 0 0 2.6314-2.6314 2.6315 2.6315 0 0 0-2.6314-2.6314zm0 7.3686a2.6315 2.6315 0 0 0-2.6314 2.6314 2.6315 2.6315 0 0 0 2.6314 2.6314 2.6315 2.6315 0 0 0 2.6314-2.6314 2.6315 2.6315 0 0 0-2.6314-2.6314zm0 7.3686a2.6315 2.6315 0 0 0-2.6314 2.6314 2.6315 2.6315 0 0 0 2.6314 2.6314 2.6315 2.6315 0 0 0 2.6314-2.6314 2.6315 2.6315 0 0 0-2.6314-2.6314z" fill="#aeaeae" stroke-width=".90909"/>
</svg>

After

Width:  |  Height:  |  Size: 629 B

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M19 17h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 10h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 3h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2z" fill="#69c"/>
</svg>

Before

Width:  |  Height:  |  Size: 397 B

@@ -46,9 +46,17 @@ cross = typ_times.svg
down = typ_chevron-down.svg down = typ_chevron-down.svg
edit = typ_pencil.svg edit = typ_pencil.svg
export = typ_export.svg export = typ_export.svg
fmt_bold = nw_tb-bold.svg
fmt_italic = nw_tb-italic.svg
fmt_mode-md = nw_tb-markdown.svg
fmt_mode-sc = nw_tb-shortcode.svg
fmt_strike = nw_tb-strike.svg
fmt_subscript = nw_tb-subscript.svg
fmt_superscript = nw_tb-superscript.svg
fmt_underline = nw_tb-underline.svg
forward = typ_chevron-right.svg forward = typ_chevron-right.svg
maximise = typ_arrow-maximise.svg maximise = typ_arrow-maximise.svg
menu = typ_th-menu.svg menu = typ_th-dot-menu.svg
minimise = typ_arrow-minimise.svg minimise = typ_arrow-minimise.svg
noncheckable = mixed_input-none.svg noncheckable = mixed_input-none.svg
proj_chapter = mixed_document-chapter.svg proj_chapter = mixed_document-chapter.svg
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m1.3672 5.5562v-4.7625h1.6289q0.37252 0 0.68662 0.058435 0.32139 0.051131 0.55513 0.18991 0.24104 0.13878 0.37252 0.36522 0.13878 0.22644 0.13878 0.56975 0 0.1607-0.043826 0.32139-0.043826 0.16069-0.12417 0.29948-0.080349 0.13878-0.19721 0.24835-0.10957 0.10957-0.25565 0.1607v0.029218q0.18261 0.043827 0.33601 0.13878 0.1534 0.087653 0.27027 0.22644 0.11687 0.13878 0.18261 0.3287 0.065739 0.18261 0.065739 0.42365 0 0.36522-0.14608 0.62818-0.13878 0.26295-0.39444 0.43826-0.24835 0.168-0.58435 0.25565-0.33601 0.080349-0.72313 0.080349zm1.0737-2.8561h0.50401q0.39444 0 0.56975-0.1534 0.18261-0.1534 0.18261-0.40905 0-0.27757-0.18261-0.39444-0.18261-0.11687-0.56244-0.11687h-0.51131zm0 2.0233h0.60626q0.88383 0 0.88383-0.64279 0-0.31408-0.21914-0.44557-0.21914-0.13878-0.6647-0.13878h-0.60626z" fill-opacity=".78039" stroke-opacity=".78039" stroke-width=".54782"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m2.3084 5.5562 0.94666-4.7625h0.78646l-0.94666 4.7625z" fill-opacity=".78039" stroke-opacity=".78039" stroke-width=".54615"/>
</svg>

After

Width:  |  Height:  |  Size: 284 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<g fill-opacity=".78039" stroke-width="1.1">
<path d="m0.79397 2.8846-0.29357-0.21175 0.28393-0.5005-0.51975-0.23582 0.11068-0.3465 0.55825 0.1155 0.062562-0.56787h0.37057l0.062562 0.57268 0.55343-0.12032 0.1155 0.3465-0.51975 0.23582 0.28393 0.5005-0.29356 0.21175-0.385-0.42832z"/>
<path d="m2.8219 3.15-0.09999 0.37496h0.86866l-0.09374-0.37496q-0.08749-0.30622-0.16873-0.64993-0.081241-0.34372-0.16248-0.66243h-0.024997q-0.074992 0.32496-0.15623 0.66868-0.074992 0.33747-0.16248 0.64369zm-1.4749 2.0623 1.2749-4.0746h1.1062l1.2749 4.0746h-0.97489l-0.24997-0.96865h-1.2437l-0.24997 0.96865z"/>
<path d="m4.7812 2.8846-0.29357-0.21175 0.28394-0.5005-0.51975-0.23582 0.11069-0.3465 0.55825 0.1155 0.062562-0.56787h0.37056l0.062562 0.57268 0.55343-0.12032 0.1155 0.3465-0.51975 0.23582 0.28393 0.5005-0.29356 0.21175-0.385-0.42832z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 995 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<g fill-opacity=".78039" stroke-width="1.3799">
<path d="m0.52917 5.2683v-4.1866h1.0272v0.37972h-0.49167v3.4272h0.49167v0.37972z"/>
<path d="m2.9 2.9219-0.077889 0.29209h0.67667l-0.073022-0.29209q-0.068154-0.23854-0.13144-0.50628-0.063285-0.26775-0.12657-0.51601h-0.019472q-0.058417 0.25314-0.1217 0.52088-0.058417 0.26288-0.12657 0.50141zm-1.1489 1.6065 0.9931-3.174h0.86166l0.9931 3.174h-0.75942l-0.19472-0.75455h-0.96876l-0.19472 0.75455z"/>
<path d="m4.7937 5.2683v-0.37972h0.49654v-3.4272h-0.49654v-0.37972h1.0272v4.1866z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 691 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m3.1785 5.5562q-0.46361 0-0.89912-0.17561-0.42848-0.17561-0.75863-0.49873l0.47766-0.55492q0.24585 0.2318 0.56195 0.37931 0.3161 0.14049 0.63219 0.14049 0.39336 0 0.59707-0.16156 0.20371-0.16156 0.20371-0.42848 0-0.14049-0.056194-0.23883-0.04917-0.098341-0.14751-0.16858-0.091316-0.077267-0.22478-0.14049-0.12644-0.063219-0.28097-0.12644l-0.63219-0.27395q-0.16858-0.070243-0.33717-0.17561-0.16156-0.10537-0.29502-0.25288-0.13346-0.14751-0.21775-0.34419-0.077268-0.19668-0.077268-0.44956 0-0.27395 0.11239-0.50575 0.11941-0.23883 0.32312-0.41444 0.20371-0.17561 0.48468-0.27395 0.288-0.098341 0.62517-0.098341 0.40039 0 0.77268 0.16156 0.37229 0.15454 0.63921 0.42146l-0.41444 0.5198q-0.22478-0.18263-0.46361-0.288-0.23883-0.10537-0.53385-0.10537-0.33014 0-0.52683 0.14751-0.19668 0.14049-0.19668 0.39336 0 0.13346 0.056194 0.2318 0.063219 0.091316 0.16156 0.16858 0.10537 0.070244 0.23883 0.13346 0.13346 0.056194 0.27395 0.11239l0.62516 0.2599q0.20371 0.084291 0.37229 0.19668 0.16858 0.11239 0.288 0.2599 0.12644 0.14751 0.19668 0.34419 0.070244 0.18966 0.070244 0.44253 0 0.28097-0.11239 0.52683-0.11239 0.24585-0.33015 0.43551-0.21073 0.18263-0.5198 0.29502-0.30205 0.10536-0.68838 0.10536zm-2.3848-2.6458h4.7625v0.52917h-4.7625z" fill-opacity=".78039" stroke-opacity=".78039" stroke-width=".52683"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m2.124 3.0221-0.16749 0.58257h1.2161l-0.16749-0.58257q-0.11651-0.37867-0.22575-0.77919-0.10195-0.40052-0.2039-0.79375h-0.029128q-0.10195 0.40052-0.2039 0.80103-0.10195 0.39323-0.21846 0.7719zm-1.5948 2.5342 1.5511-4.7625h0.99037l1.5511 4.7625h-0.88842l-0.37139-1.2889h-1.6021l-0.37139 1.2889z" fill-opacity=".78039" stroke-opacity=".78039" stroke-width=".54616"/>
<path d="m4.101 5.5562v-2.3812h0.75354q0.18388 0 0.3353 0.032769 0.15503 0.029128 0.2668 0.098307 0.11538 0.065538 0.17667 0.17841 0.064898 0.11287 0.064898 0.27671 0 0.17113-0.086529 0.31676-0.086529 0.14564-0.24156 0.20026v0.014565q0.19469 0.043691 0.32088 0.18569 0.12979 0.13836 0.12979 0.37867 0 0.17841-0.068503 0.30948-0.068502 0.13107-0.19108 0.21846-0.11899 0.087383-0.28483 0.13107-0.16585 0.04005-0.36054 0.04005zm0.41823-1.3981h0.29925q0.24878 0 0.35693-0.091024 0.11177-0.091025 0.11177-0.24394 0-0.17477-0.11538-0.24759-0.11538-0.072819-0.34612-0.072819h-0.30646zm0 1.0705h0.35334q0.25959 0 0.4002-0.094666 0.14061-0.098307 0.14061-0.29856 0-0.18933-0.13701-0.27308-0.13701-0.087384-0.40381-0.087384h-0.35334z" fill="#4271ae" stroke-opacity=".78039" stroke-width=".27174"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m2.124 3.0221-0.16749 0.58257h1.2161l-0.16749-0.58257q-0.11651-0.37867-0.22575-0.77919-0.10195-0.40052-0.2039-0.79375h-0.029128q-0.10195 0.40052-0.2039 0.80103-0.10195 0.39323-0.21846 0.7719zm-1.5948 2.5342 1.5511-4.7625h0.99037l1.5511 4.7625h-0.88842l-0.37139-1.2889h-1.6021l-0.37139 1.2889z" fill-opacity=".78039" stroke-opacity=".78039" stroke-width=".54616"/>
<path d="m4.101 3.175v-2.3812h0.75352q0.18389 0 0.33531 0.032768 0.15503 0.029128 0.2668 0.098307 0.11538 0.065538 0.17667 0.17841 0.064898 0.11287 0.064898 0.27671 0 0.17113-0.086528 0.31676-0.086528 0.14564-0.24156 0.20026v0.014565q0.19469 0.043691 0.32088 0.1857 0.12979 0.13836 0.12979 0.37867 0 0.17841-0.068503 0.30948t-0.19108 0.21845q-0.11897 0.087382-0.28482 0.13107-0.16585 0.04005-0.36054 0.04005zm0.41824-1.3981h0.29924q0.24878 0 0.35693-0.091024 0.11177-0.091024 0.11177-0.24394 0-0.17477-0.11538-0.24759-0.11538-0.072818-0.34612-0.072818h-0.30646zm0 1.0705h0.35332q0.25959 0 0.4002-0.094666 0.14061-0.098307 0.14061-0.29855 0-0.18932-0.13701-0.27307-0.13701-0.087383-0.40381-0.087383h-0.35332z" fill="#4271ae" stroke-opacity=".78039" stroke-width=".27173"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m3.1782 5.0271q-0.36231 0-0.6547-0.1017-0.28603-0.10806-0.49579-0.33689-0.2034-0.22883-0.31782-0.58479-0.10806-0.36231-0.10806-0.87717v-2.3328h0.73733v2.3899q0 0.33689 0.057206 0.57207 0.063563 0.22883 0.17162 0.36866 0.11441 0.13984 0.26697 0.2034 0.15255 0.057206 0.34324 0.057206 0.19068 0 0.3496-0.057206 0.15891-0.063564 0.27332-0.2034 0.11441-0.13984 0.17798-0.36866 0.063564-0.23519 0.063564-0.57207v-2.3899h0.70555v2.3328q0 0.51487-0.10806 0.87717-0.10806 0.35595-0.31782 0.58479-0.2034 0.22883-0.49579 0.33689-0.28603 0.1017-0.64835 0.1017z" fill-opacity=".78039" stroke-opacity=".78039" stroke-width=".47673"/>
<rect x="1.0583" y="5.2917" width="4.2333" height=".52917" fill-opacity=".78039" stroke-width="1.2172"/>
</svg>

After

Width:  |  Height:  |  Size: 885 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m12 2a2.6315 2.6315 0 0 0-2.6314 2.6314 2.6315 2.6315 0 0 0 2.6314 2.6314 2.6315 2.6315 0 0 0 2.6314-2.6314 2.6315 2.6315 0 0 0-2.6314-2.6314zm0 7.3686a2.6315 2.6315 0 0 0-2.6314 2.6314 2.6315 2.6315 0 0 0 2.6314 2.6314 2.6315 2.6315 0 0 0 2.6314-2.6314 2.6315 2.6315 0 0 0-2.6314-2.6314zm0 7.3686a2.6315 2.6315 0 0 0-2.6314 2.6314 2.6315 2.6315 0 0 0 2.6314 2.6314 2.6315 2.6315 0 0 0 2.6314-2.6314 2.6315 2.6315 0 0 0-2.6314-2.6314z" fill-opacity=".78039" stroke-width=".90909"/>
</svg>

After

Width:  |  Height:  |  Size: 636 B

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M19 17h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 10h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 3h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2z" fill="#4271ae"/>
</svg>

Before

Width:  |  Height:  |  Size: 400 B

+18 -12
View File
@@ -178,9 +178,11 @@ class Config:
self.spellLanguage = "en" self.spellLanguage = "en"
# State # State
self.showRefPanel = True # The reference panel for the viewer is visible self.showRefPanel = True # The reference panel for the viewer is visible
self.viewComments = True # Comments are shown in the viewer self.showEditToolBar = False # The document editor toolbar visibility
self.viewSynopsis = True # Synopsis is shown in the viewer self.useShortcodes = False # Use shorcodes for basic formatting
self.viewComments = True # Comments are shown in the viewer
self.viewSynopsis = True # Synopsis is shown in the viewer
# Search Bar Switches # Search Bar Switches
self.searchCase = False self.searchCase = False
@@ -598,15 +600,17 @@ class Config:
# State # State
sec = "State" sec = "State"
self.showRefPanel = conf.rdBool(sec, "showrefpanel", self.showRefPanel) self.showRefPanel = conf.rdBool(sec, "showrefpanel", self.showRefPanel)
self.viewComments = conf.rdBool(sec, "viewcomments", self.viewComments) self.showEditToolBar = conf.rdBool(sec, "showedittoolbar", self.showEditToolBar)
self.viewSynopsis = conf.rdBool(sec, "viewsynopsis", self.viewSynopsis) self.useShortcodes = conf.rdBool(sec, "useshortcodes", self.useShortcodes)
self.searchCase = conf.rdBool(sec, "searchcase", self.searchCase) self.viewComments = conf.rdBool(sec, "viewcomments", self.viewComments)
self.searchWord = conf.rdBool(sec, "searchword", self.searchWord) self.viewSynopsis = conf.rdBool(sec, "viewsynopsis", self.viewSynopsis)
self.searchRegEx = conf.rdBool(sec, "searchregex", self.searchRegEx) self.searchCase = conf.rdBool(sec, "searchcase", self.searchCase)
self.searchLoop = conf.rdBool(sec, "searchloop", self.searchLoop) self.searchWord = conf.rdBool(sec, "searchword", self.searchWord)
self.searchNextFile = conf.rdBool(sec, "searchnextfile", self.searchNextFile) self.searchRegEx = conf.rdBool(sec, "searchregex", self.searchRegEx)
self.searchMatchCap = conf.rdBool(sec, "searchmatchcap", self.searchMatchCap) self.searchLoop = conf.rdBool(sec, "searchloop", self.searchLoop)
self.searchNextFile = conf.rdBool(sec, "searchnextfile", self.searchNextFile)
self.searchMatchCap = conf.rdBool(sec, "searchmatchcap", self.searchMatchCap)
# Deprecated Settings or Locations as of 2.0 # Deprecated Settings or Locations as of 2.0
# ToDo: These will be loaded for a few minor releases until the users have converted them # ToDo: These will be loaded for a few minor releases until the users have converted them
@@ -721,6 +725,8 @@ class Config:
conf["State"] = { conf["State"] = {
"showrefpanel": str(self.showRefPanel), "showrefpanel": str(self.showRefPanel),
"showedittoolbar": str(self.showEditToolBar),
"useshortcodes": str(self.useShortcodes),
"viewcomments": str(self.viewComments), "viewcomments": str(self.viewComments),
"viewsynopsis": str(self.viewSynopsis), "viewsynopsis": str(self.viewSynopsis),
"searchcase": str(self.searchCase), "searchcase": str(self.searchCase),
+5 -3
View File
@@ -28,7 +28,7 @@ import json
import logging import logging
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any, TypeVar
from pathlib import Path from pathlib import Path
from novelwriter.error import logException from novelwriter.error import logException
@@ -40,6 +40,8 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
NWEnum = TypeVar("NWEnum", bound=Enum)
VALID_MAP = { VALID_MAP = {
"GuiWritingStats": { "GuiWritingStats": {
"winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2", "winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2",
@@ -201,11 +203,11 @@ class OptionState:
return checkBool(self._state[group].get(name, default), default) return checkBool(self._state[group].get(name, default), default)
return default return default
def getEnum(self, group: str, name: str, lookup: type, default: Enum) -> Enum: def getEnum(self, group: str, name: str, lookup: type, default: NWEnum) -> NWEnum:
"""Return the value mapped to an enum. Otherwise return the """Return the value mapped to an enum. Otherwise return the
default value. default value.
""" """
if issubclass(lookup, Enum): if issubclass(lookup, type(default)):
if group in self._state: if group in self._state:
if name in self._state[group]: if name in self._state[group]:
value = self._state[group][name] value = self._state[group][name]
+210 -60
View File
@@ -46,7 +46,7 @@ from PyQt5.QtGui import (
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAction, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu, QAction, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
QPlainTextEdit, QPushButton, QShortcut, QToolBar, QToolButton, QWidget, QPlainTextEdit, QPushButton, QShortcut, QToolBar, QToolButton, QVBoxLayout, QWidget,
qApp qApp
) )
@@ -83,6 +83,8 @@ class GuiDocEditor(QPlainTextEdit):
novelStructureChanged = pyqtSignal() novelStructureChanged = pyqtSignal()
novelItemMetaChanged = pyqtSignal(str) novelItemMetaChanged = pyqtSignal(str)
spellCheckStateChanged = pyqtSignal(bool) spellCheckStateChanged = pyqtSignal(bool)
closeDocumentRequest = pyqtSignal()
toggleFocusModeRequest = pyqtSignal()
def __init__(self, mainGui: GuiMain) -> None: def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui) super().__init__(parent=mainGui)
@@ -135,6 +137,12 @@ class GuiDocEditor(QPlainTextEdit):
self.docHeader = GuiDocEditHeader(self) self.docHeader = GuiDocEditHeader(self)
self.docFooter = GuiDocEditFooter(self) self.docFooter = GuiDocEditFooter(self)
self.docSearch = GuiDocEditSearch(self) self.docSearch = GuiDocEditSearch(self)
self.docToolBar = GuiDocToolBar(self)
# Connect Signals
self.docHeader.closeDocumentRequest.connect(self._closeCurrentDocument)
self.docHeader.toggleToolBarRequest.connect(self._toggleToolBarVisibility)
self.docToolBar.requestDocAction.connect(self.docAction)
# Context Menu # Context Menu
self.setContextMenuPolicy(Qt.CustomContextMenu) self.setContextMenuPolicy(Qt.CustomContextMenu)
@@ -248,6 +256,7 @@ class GuiDocEditor(QPlainTextEdit):
self.docSearch.updateTheme() self.docSearch.updateTheme()
self.docHeader.updateTheme() self.docHeader.updateTheme()
self.docFooter.updateTheme() self.docFooter.updateTheme()
self.docToolBar.updateTheme()
return return
def updateSyntaxColours(self) -> None: def updateSyntaxColours(self) -> None:
@@ -400,6 +409,7 @@ class GuiDocEditor(QPlainTextEdit):
qApp.processEvents() qApp.processEvents()
self.setDocumentChanged(False) self.setDocumentChanged(False)
self._qDocument.clearUndoRedoStacks() self._qDocument.clearUndoRedoStacks()
self.docToolBar.setVisible(CONFIG.showEditToolBar)
qApp.restoreOverrideCursor() qApp.restoreOverrideCursor()
@@ -514,6 +524,7 @@ class GuiDocEditor(QPlainTextEdit):
fY = wH - fH - tB - sH fY = wH - fH - tB - sH
self.docHeader.setGeometry(tB, tB, tW, tH) self.docHeader.setGeometry(tB, tB, tW, tH)
self.docFooter.setGeometry(tB, fY, tW, fH) self.docFooter.setGeometry(tB, fY, tW, fH)
self.docToolBar.move(0, tH)
rH = 0 rH = 0
if self.docSearch.isVisible(): if self.docSearch.isVisible():
@@ -841,30 +852,11 @@ class GuiDocEditor(QPlainTextEdit):
return True return True
def insertKeyWord(self, keyword: str) -> bool:
"""Insert a keyword in the text editor, at the cursor position.
If the insert line is not blank, a new line is started.
"""
if keyword not in nwKeyWords.VALID_KEYS:
logger.error("Invalid keyword '%s'", keyword)
return False
logger.debug("Inserting keyword '%s'", keyword)
state = self.insertNewBlock("%s: " % keyword)
return state
def closeSearch(self) -> bool: def closeSearch(self) -> bool:
"""Close the search box.""" """Close the search box."""
self.docSearch.closeSearch() self.docSearch.closeSearch()
return self.docSearch.isVisible() return self.docSearch.isVisible()
def toggleSearch(self) -> None:
"""Toggle the visibility of the search box."""
if self.docSearch.isVisible():
self.docSearch.closeSearch()
else:
self.beginSearch()
return
## ##
# Document Events and Maintenance # Document Events and Maintenance
## ##
@@ -959,6 +951,27 @@ class GuiDocEditor(QPlainTextEdit):
self.updateDocMargins() self.updateDocMargins()
return return
@pyqtSlot(str)
def insertKeyWord(self, keyword: str) -> bool:
"""Insert a keyword in the text editor, at the cursor position.
If the insert line is not blank, a new line is started.
"""
if keyword not in nwKeyWords.VALID_KEYS:
logger.error("Invalid keyword '%s'", keyword)
return False
logger.debug("Inserting keyword '%s'", keyword)
state = self.insertNewBlock("%s: " % keyword)
return state
@pyqtSlot()
def toggleSearch(self) -> None:
"""Toggle the visibility of the search box."""
if self.docSearch.isVisible():
self.docSearch.closeSearch()
else:
self.beginSearch()
return
## ##
# Private Slots # Private Slots
## ##
@@ -1179,6 +1192,21 @@ class GuiDocEditor(QPlainTextEdit):
return return
@pyqtSlot()
def _closeCurrentDocument(self) -> None:
"""Close the document. Forwarded to the main Gui."""
self.closeDocumentRequest.emit()
self.docToolBar.setVisible(False)
return
@pyqtSlot()
def _toggleToolBarVisibility(self) -> None:
"""Toggle the visibility of the tool bar."""
state = not self.docToolBar.isVisible()
self.docToolBar.setVisible(state)
CONFIG.showEditToolBar = state
return
## ##
# Search & Replace # Search & Replace
## ##
@@ -2072,6 +2100,142 @@ class BackgroundWordCounterSignals(QObject):
# END Class BackgroundWordCounterSignals # END Class BackgroundWordCounterSignals
# =============================================================================================== #
# The Formatting and Options Fold Out Menu
# Only used by DocEditor, and is opened by the first button in the header
# =============================================================================================== #
class GuiDocToolBar(QWidget):
requestDocAction = pyqtSignal(nwDocAction)
def __init__(self, docEditor: GuiDocEditor) -> None:
super().__init__(parent=docEditor)
logger.debug("Create: GuiDocToolBar")
cM = CONFIG.pxInt(4)
tPx = int(0.8*SHARED.theme.fontPixelSize)
iconSize = QSize(tPx, tPx)
self.setContentsMargins(0, 0, 0, 0)
# General Buttons
# ===============
self.tbMode = QToolButton(self)
self.tbMode.setToolTip(self.tr("Toggle Markdown or Shortcodes Mode"))
self.tbMode.setIconSize(iconSize)
self.tbMode.setCheckable(True)
self.tbMode.setChecked(CONFIG.useShortcodes)
self.tbMode.toggled.connect(self._toggleFormatMode)
self.tbBold = QToolButton(self)
self.tbBold.setIconSize(iconSize)
self.tbBold.clicked.connect(self._formatBold)
self.tbItalic = QToolButton(self)
self.tbItalic.setIconSize(iconSize)
self.tbItalic.clicked.connect(self._formatItalic)
self.tbStrike = QToolButton(self)
self.tbStrike.setIconSize(iconSize)
self.tbStrike.clicked.connect(self._formatStrike)
self.tbUnderline = QToolButton(self)
self.tbUnderline.setIconSize(iconSize)
self.tbUnderline.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE)
)
self.tbSuperscript = QToolButton(self)
self.tbSuperscript.setIconSize(iconSize)
self.tbSuperscript.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_SUP)
)
self.tbSubscript = QToolButton(self)
self.tbSubscript.setIconSize(iconSize)
self.tbSubscript.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_SUB)
)
# Assemble
# ========
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(self.tbMode)
self.outerBox.addWidget(self.tbBold)
self.outerBox.addWidget(self.tbItalic)
self.outerBox.addWidget(self.tbStrike)
self.outerBox.addWidget(self.tbUnderline)
self.outerBox.addWidget(self.tbSuperscript)
self.outerBox.addWidget(self.tbSubscript)
self.outerBox.setContentsMargins(cM, cM, cM, cM)
self.outerBox.setSpacing(cM)
self.setLayout(self.outerBox)
self.updateTheme()
logger.debug("Ready: GuiDocToolBar")
return
def updateTheme(self) -> None:
"""Initialise GUI elements that depend on specific settings."""
palette = QPalette()
palette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
palette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
palette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
self.setPalette(palette)
tPx = int(0.8*SHARED.theme.fontPixelSize)
self.tbMode.setIcon(SHARED.theme.getToggleIcon("fmt_mode", (tPx, tPx)))
self.tbBold.setIcon(SHARED.theme.getIcon("fmt_bold"))
self.tbItalic.setIcon(SHARED.theme.getIcon("fmt_italic"))
self.tbStrike.setIcon(SHARED.theme.getIcon("fmt_strike"))
self.tbUnderline.setIcon(SHARED.theme.getIcon("fmt_underline"))
self.tbSuperscript.setIcon(SHARED.theme.getIcon("fmt_superscript"))
self.tbSubscript.setIcon(SHARED.theme.getIcon("fmt_subscript"))
return
##
# Private Slots
##
@pyqtSlot(bool)
def _toggleFormatMode(self, checked: bool) -> None:
"""Toggle the formatting mode."""
CONFIG.useShortcodes = checked
return
@pyqtSlot()
def _formatBold(self):
"""Call the bold format action."""
self.requestDocAction.emit(
nwDocAction.SC_BOLD if self.tbMode.isChecked() else nwDocAction.STRONG
)
return
@pyqtSlot()
def _formatItalic(self):
"""Call the italic format action."""
self.requestDocAction.emit(
nwDocAction.SC_ITALIC if self.tbMode.isChecked() else nwDocAction.EMPH
)
return
@pyqtSlot()
def _formatStrike(self):
"""Call the strikethrough format action."""
self.requestDocAction.emit(
nwDocAction.SC_STRIKE if self.tbMode.isChecked() else nwDocAction.STRIKE
)
return
# END Class GuiDocToolBar
# =============================================================================================== # # =============================================================================================== #
# The Embedded Document Search/Replace Feature # The Embedded Document Search/Replace Feature
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport # Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
@@ -2487,6 +2651,9 @@ class GuiDocEditSearch(QFrame):
class GuiDocEditHeader(QWidget): class GuiDocEditHeader(QWidget):
closeDocumentRequest = pyqtSignal()
toggleToolBarRequest = pyqtSignal()
def __init__(self, docEditor: GuiDocEditor) -> None: def __init__(self, docEditor: GuiDocEditor) -> None:
super().__init__(parent=docEditor) super().__init__(parent=docEditor)
@@ -2499,6 +2666,7 @@ class GuiDocEditHeader(QWidget):
fPx = int(0.9*SHARED.theme.fontPixelSize) fPx = int(0.9*SHARED.theme.fontPixelSize)
hSp = CONFIG.pxInt(6) hSp = CONFIG.pxInt(6)
iconSize = QSize(fPx, fPx)
# Main Widget Settings # Main Widget Settings
self.setAutoFillBackground(True) self.setAutoFillBackground(True)
@@ -2518,46 +2686,46 @@ class GuiDocEditHeader(QWidget):
self.itemTitle.setFont(lblFont) self.itemTitle.setFont(lblFont)
# Buttons # Buttons
self.editButton = QToolButton(self) self.tbButton = QToolButton(self)
self.editButton.setContentsMargins(0, 0, 0, 0) self.tbButton.setContentsMargins(0, 0, 0, 0)
self.editButton.setIconSize(QSize(fPx, fPx)) self.tbButton.setIconSize(iconSize)
self.editButton.setFixedSize(fPx, fPx) self.tbButton.setFixedSize(fPx, fPx)
self.editButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.tbButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.editButton.setVisible(False) self.tbButton.setVisible(False)
self.editButton.setToolTip(self.tr("Edit document label")) self.tbButton.setToolTip(self.tr("Toggle Tool Bar"))
self.editButton.clicked.connect(self._editDocument) self.tbButton.clicked.connect(lambda: self.toggleToolBarRequest.emit())
self.searchButton = QToolButton(self) self.searchButton = QToolButton(self)
self.searchButton.setContentsMargins(0, 0, 0, 0) self.searchButton.setContentsMargins(0, 0, 0, 0)
self.searchButton.setIconSize(QSize(fPx, fPx)) self.searchButton.setIconSize(iconSize)
self.searchButton.setFixedSize(fPx, fPx) self.searchButton.setFixedSize(fPx, fPx)
self.searchButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.searchButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.searchButton.setVisible(False) self.searchButton.setVisible(False)
self.searchButton.setToolTip(self.tr("Search document")) self.searchButton.setToolTip(self.tr("Search"))
self.searchButton.clicked.connect(self._searchDocument) self.searchButton.clicked.connect(self.docEditor.toggleSearch)
self.minmaxButton = QToolButton(self) self.minmaxButton = QToolButton(self)
self.minmaxButton.setContentsMargins(0, 0, 0, 0) self.minmaxButton.setContentsMargins(0, 0, 0, 0)
self.minmaxButton.setIconSize(QSize(fPx, fPx)) self.minmaxButton.setIconSize(iconSize)
self.minmaxButton.setFixedSize(fPx, fPx) self.minmaxButton.setFixedSize(fPx, fPx)
self.minmaxButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.minmaxButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.minmaxButton.setVisible(False) self.minmaxButton.setVisible(False)
self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode")) self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode"))
self.minmaxButton.clicked.connect(self._minmaxDocument) self.minmaxButton.clicked.connect(lambda: self.docEditor.toggleFocusModeRequest.emit())
self.closeButton = QToolButton(self) self.closeButton = QToolButton(self)
self.closeButton.setContentsMargins(0, 0, 0, 0) self.closeButton.setContentsMargins(0, 0, 0, 0)
self.closeButton.setIconSize(QSize(fPx, fPx)) self.closeButton.setIconSize(iconSize)
self.closeButton.setFixedSize(fPx, fPx) self.closeButton.setFixedSize(fPx, fPx)
self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.closeButton.setVisible(False) self.closeButton.setVisible(False)
self.closeButton.setToolTip(self.tr("Close the document")) self.closeButton.setToolTip(self.tr("Close"))
self.closeButton.clicked.connect(self._closeDocument) self.closeButton.clicked.connect(self._closeDocument)
# Assemble Layout # Assemble Layout
self.outerBox = QHBoxLayout() self.outerBox = QHBoxLayout()
self.outerBox.setSpacing(hSp) self.outerBox.setSpacing(hSp)
self.outerBox.addWidget(self.editButton, 0) self.outerBox.addWidget(self.tbButton, 0)
self.outerBox.addWidget(self.searchButton, 0) self.outerBox.addWidget(self.searchButton, 0)
self.outerBox.addWidget(self.itemTitle, 1) self.outerBox.addWidget(self.itemTitle, 1)
self.outerBox.addWidget(self.minmaxButton, 0) self.outerBox.addWidget(self.minmaxButton, 0)
@@ -2583,7 +2751,7 @@ class GuiDocEditHeader(QWidget):
def updateTheme(self) -> None: def updateTheme(self) -> None:
"""Update theme elements.""" """Update theme elements."""
self.editButton.setIcon(SHARED.theme.getIcon("edit")) self.tbButton.setIcon(SHARED.theme.getIcon("menu"))
self.searchButton.setIcon(SHARED.theme.getIcon("search")) self.searchButton.setIcon(SHARED.theme.getIcon("search"))
self.minmaxButton.setIcon(SHARED.theme.getIcon("maximise")) self.minmaxButton.setIcon(SHARED.theme.getIcon("maximise"))
self.closeButton.setIcon(SHARED.theme.getIcon("close")) self.closeButton.setIcon(SHARED.theme.getIcon("close"))
@@ -2593,7 +2761,7 @@ class GuiDocEditHeader(QWidget):
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}" "QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
).format(*SHARED.theme.colText) ).format(*SHARED.theme.colText)
self.editButton.setStyleSheet(buttonStyle) self.tbButton.setStyleSheet(buttonStyle)
self.searchButton.setStyleSheet(buttonStyle) self.searchButton.setStyleSheet(buttonStyle)
self.minmaxButton.setStyleSheet(buttonStyle) self.minmaxButton.setStyleSheet(buttonStyle)
self.closeButton.setStyleSheet(buttonStyle) self.closeButton.setStyleSheet(buttonStyle)
@@ -2623,7 +2791,7 @@ class GuiDocEditHeader(QWidget):
self._docHandle = tHandle self._docHandle = tHandle
if tHandle is None: if tHandle is None:
self.itemTitle.setText("") self.itemTitle.setText("")
self.editButton.setVisible(False) self.tbButton.setVisible(False)
self.searchButton.setVisible(False) self.searchButton.setVisible(False)
self.closeButton.setVisible(False) self.closeButton.setVisible(False)
self.minmaxButton.setVisible(False) self.minmaxButton.setVisible(False)
@@ -2645,7 +2813,7 @@ class GuiDocEditHeader(QWidget):
return False return False
self.itemTitle.setText(nwItem.itemName) self.itemTitle.setText(nwItem.itemName)
self.editButton.setVisible(True) self.tbButton.setVisible(True)
self.searchButton.setVisible(True) self.searchButton.setVisible(True)
self.closeButton.setVisible(True) self.closeButton.setVisible(True)
self.minmaxButton.setVisible(True) self.minmaxButton.setVisible(True)
@@ -2667,34 +2835,16 @@ class GuiDocEditHeader(QWidget):
# Private Slots # Private Slots
## ##
@pyqtSlot()
def _editDocument(self) -> None:
"""Open the edit item dialog from the main GUI."""
self.mainGui.editItemLabel(self._docHandle)
return
@pyqtSlot()
def _searchDocument(self) -> None:
"""Toggle the visibility of the search box."""
self.docEditor.toggleSearch()
return
@pyqtSlot() @pyqtSlot()
def _closeDocument(self) -> None: def _closeDocument(self) -> None:
"""Trigger the close editor on the main window.""" """Trigger the close editor on the main window."""
self.mainGui.closeDocEditor() self.closeDocumentRequest.emit()
self.editButton.setVisible(False) self.tbButton.setVisible(False)
self.searchButton.setVisible(False) self.searchButton.setVisible(False)
self.closeButton.setVisible(False) self.closeButton.setVisible(False)
self.minmaxButton.setVisible(False) self.minmaxButton.setVisible(False)
return return
@pyqtSlot()
def _minmaxDocument(self) -> None:
"""Switch on or off Focus Mode."""
self.mainGui.toggleFocusMode()
return
## ##
# Events # Events
## ##
+10 -22
View File
@@ -34,8 +34,8 @@ from typing import TYPE_CHECKING
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QPoint, QSize, Qt, QUrl from PyQt5.QtCore import pyqtSignal, pyqtSlot, QPoint, QSize, Qt, QUrl
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QColor, QCursor, QFont, QIcon, QMouseEvent, QPalette, QResizeEvent, QColor, QCursor, QFont, QMouseEvent, QPalette, QResizeEvent, QTextCursor,
QTextCursor, QTextOption QTextOption
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAction, qApp, QFrame, QHBoxLayout, QLabel, QMenu, QScrollArea, QAction, qApp, QFrame, QHBoxLayout, QLabel, QMenu, QScrollArea,
@@ -705,7 +705,7 @@ class GuiDocViewHeader(QWidget):
self.backButton.setFixedSize(fPx, fPx) self.backButton.setFixedSize(fPx, fPx)
self.backButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.backButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.backButton.setVisible(False) self.backButton.setVisible(False)
self.backButton.setToolTip(self.tr("Go backward")) self.backButton.setToolTip(self.tr("Go Backward"))
self.backButton.clicked.connect(self.docViewer.navBackward) self.backButton.clicked.connect(self.docViewer.navBackward)
self.forwardButton = QToolButton(self) self.forwardButton = QToolButton(self)
@@ -714,7 +714,7 @@ class GuiDocViewHeader(QWidget):
self.forwardButton.setFixedSize(fPx, fPx) self.forwardButton.setFixedSize(fPx, fPx)
self.forwardButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.forwardButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.forwardButton.setVisible(False) self.forwardButton.setVisible(False)
self.forwardButton.setToolTip(self.tr("Go forward")) self.forwardButton.setToolTip(self.tr("Go Forward"))
self.forwardButton.clicked.connect(self.docViewer.navForward) self.forwardButton.clicked.connect(self.docViewer.navForward)
self.refreshButton = QToolButton(self) self.refreshButton = QToolButton(self)
@@ -723,7 +723,7 @@ class GuiDocViewHeader(QWidget):
self.refreshButton.setFixedSize(fPx, fPx) self.refreshButton.setFixedSize(fPx, fPx)
self.refreshButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.refreshButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.refreshButton.setVisible(False) self.refreshButton.setVisible(False)
self.refreshButton.setToolTip(self.tr("Reload the document")) self.refreshButton.setToolTip(self.tr("Reload"))
self.refreshButton.clicked.connect(self._refreshDocument) self.refreshButton.clicked.connect(self._refreshDocument)
self.closeButton = QToolButton(self) self.closeButton = QToolButton(self)
@@ -732,7 +732,7 @@ class GuiDocViewHeader(QWidget):
self.closeButton.setFixedSize(fPx, fPx) self.closeButton.setFixedSize(fPx, fPx)
self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.closeButton.setVisible(False) self.closeButton.setVisible(False)
self.closeButton.setToolTip(self.tr("Close the document")) self.closeButton.setToolTip(self.tr("Close"))
self.closeButton.clicked.connect(self._closeDocument) self.closeButton.clicked.connect(self._closeDocument)
# Assemble Layout # Assemble Layout
@@ -1020,24 +1020,12 @@ class GuiDocViewFooter(QWidget):
# Methods # Methods
## ##
def updateTheme(self): def updateTheme(self) -> None:
"""Update theme elements. """Update theme elements."""
"""
# Icons # Icons
fPx = int(0.9*SHARED.theme.fontPixelSize) fPx = int(0.9*SHARED.theme.fontPixelSize)
stickyIcon = SHARED.theme.getToggleIcon("sticky", (fPx, fPx))
stickyOn = SHARED.theme.getPixmap("sticky-on", (fPx, fPx)) bulletIcon = SHARED.theme.getToggleIcon("bullet", (fPx, fPx))
stickyOff = SHARED.theme.getPixmap("sticky-off", (fPx, fPx))
stickyIcon = QIcon()
stickyIcon.addPixmap(stickyOn, QIcon.Normal, QIcon.On)
stickyIcon.addPixmap(stickyOff, QIcon.Normal, QIcon.Off)
bulletOn = SHARED.theme.getPixmap("bullet-on", (fPx, fPx))
bulletOff = SHARED.theme.getPixmap("bullet-off", (fPx, fPx))
bulletIcon = QIcon()
bulletIcon.addPixmap(bulletOn, QIcon.Normal, QIcon.On)
bulletIcon.addPixmap(bulletOff, QIcon.Normal, QIcon.Off)
self.showHide.setIcon(SHARED.theme.getIcon("reference")) self.showHide.setIcon(SHARED.theme.getIcon("reference"))
self.stickyRefs.setIcon(stickyIcon) self.stickyRefs.setIcon(stickyIcon)
+184 -76
View File
@@ -30,7 +30,7 @@ from pathlib import Path
from urllib.parse import urljoin from urllib.parse import urljoin
from urllib.request import pathname2url from urllib.request import pathname2url
from PyQt5.QtCore import QUrl, pyqtSlot from PyQt5.QtCore import QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QDesktopServices from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QMenuBar, QAction from PyQt5.QtWidgets import QMenuBar, QAction
@@ -50,6 +50,11 @@ class GuiMainMenu(QMenuBar):
add them from this class. add them from this class.
""" """
requestDocAction = pyqtSignal(nwDocAction)
requestDocInsert = pyqtSignal(nwDocInsert)
requestDocInsertText = pyqtSignal(str)
requestDocKeyWordInsert = pyqtSignal(str)
def __init__(self, mainGui: GuiMain) -> None: def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui) super().__init__(parent=mainGui)
@@ -68,11 +73,6 @@ class GuiMainMenu(QMenuBar):
self._buildToolsMenu() self._buildToolsMenu()
self._buildHelpMenu() self._buildHelpMenu()
# Function Pointers
self._docAction = self.mainGui.passDocumentAction
self._docInsert = self.mainGui.docEditor.insertText
self._insertKeyWord = self.mainGui.docEditor.insertKeyWord
logger.debug("Ready: GuiMainMenu") logger.debug("Ready: GuiMainMenu")
return return
@@ -209,7 +209,7 @@ class GuiMainMenu(QMenuBar):
# Document > Close # Document > Close
self.aCloseDoc = self.docuMenu.addAction(self.tr("Close Document")) self.aCloseDoc = self.docuMenu.addAction(self.tr("Close Document"))
self.aCloseDoc.setShortcut("Ctrl+W") self.aCloseDoc.setShortcut("Ctrl+W")
self.aCloseDoc.triggered.connect(lambda: self.mainGui.closeDocEditor()) self.aCloseDoc.triggered.connect(self.mainGui.closeDocEditor)
# Document > Separator # Document > Separator
self.docuMenu.addSeparator() self.docuMenu.addSeparator()
@@ -246,12 +246,12 @@ class GuiMainMenu(QMenuBar):
# Edit > Undo # Edit > Undo
self.aEditUndo = self.editMenu.addAction(self.tr("Undo")) self.aEditUndo = self.editMenu.addAction(self.tr("Undo"))
self.aEditUndo.setShortcut("Ctrl+Z") self.aEditUndo.setShortcut("Ctrl+Z")
self.aEditUndo.triggered.connect(lambda: self._docAction(nwDocAction.UNDO)) self.aEditUndo.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.UNDO))
# Edit > Redo # Edit > Redo
self.aEditRedo = self.editMenu.addAction(self.tr("Redo")) self.aEditRedo = self.editMenu.addAction(self.tr("Redo"))
self.aEditRedo.setShortcut("Ctrl+Y") self.aEditRedo.setShortcut("Ctrl+Y")
self.aEditRedo.triggered.connect(lambda: self._docAction(nwDocAction.REDO)) self.aEditRedo.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.REDO))
# Edit > Separator # Edit > Separator
self.editMenu.addSeparator() self.editMenu.addSeparator()
@@ -259,17 +259,17 @@ class GuiMainMenu(QMenuBar):
# Edit > Cut # Edit > Cut
self.aEditCut = self.editMenu.addAction(self.tr("Cut")) self.aEditCut = self.editMenu.addAction(self.tr("Cut"))
self.aEditCut.setShortcut("Ctrl+X") self.aEditCut.setShortcut("Ctrl+X")
self.aEditCut.triggered.connect(lambda: self._docAction(nwDocAction.CUT)) self.aEditCut.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.CUT))
# Edit > Copy # Edit > Copy
self.aEditCopy = self.editMenu.addAction(self.tr("Copy")) self.aEditCopy = self.editMenu.addAction(self.tr("Copy"))
self.aEditCopy.setShortcut("Ctrl+C") self.aEditCopy.setShortcut("Ctrl+C")
self.aEditCopy.triggered.connect(lambda: self._docAction(nwDocAction.COPY)) self.aEditCopy.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.COPY))
# Edit > Paste # Edit > Paste
self.aEditPaste = self.editMenu.addAction(self.tr("Paste")) self.aEditPaste = self.editMenu.addAction(self.tr("Paste"))
self.aEditPaste.setShortcut("Ctrl+V") self.aEditPaste.setShortcut("Ctrl+V")
self.aEditPaste.triggered.connect(lambda: self._docAction(nwDocAction.PASTE)) self.aEditPaste.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.PASTE))
# Edit > Separator # Edit > Separator
self.editMenu.addSeparator() self.editMenu.addSeparator()
@@ -277,12 +277,12 @@ class GuiMainMenu(QMenuBar):
# Edit > Select All # Edit > Select All
self.aSelectAll = self.editMenu.addAction(self.tr("Select All")) self.aSelectAll = self.editMenu.addAction(self.tr("Select All"))
self.aSelectAll.setShortcut("Ctrl+A") self.aSelectAll.setShortcut("Ctrl+A")
self.aSelectAll.triggered.connect(lambda: self._docAction(nwDocAction.SEL_ALL)) self.aSelectAll.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.SEL_ALL))
# Edit > Select Paragraph # Edit > Select Paragraph
self.aSelectPar = self.editMenu.addAction(self.tr("Select Paragraph")) self.aSelectPar = self.editMenu.addAction(self.tr("Select Paragraph"))
self.aSelectPar.setShortcut("Ctrl+Shift+A") self.aSelectPar.setShortcut("Ctrl+Shift+A")
self.aSelectPar.triggered.connect(lambda: self._docAction(nwDocAction.SEL_PARA)) self.aSelectPar.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.SEL_PARA))
return return
@@ -330,7 +330,7 @@ class GuiMainMenu(QMenuBar):
# View > Focus Mode # View > Focus Mode
self.aFocusMode = self.viewMenu.addAction(self.tr("Focus Mode")) self.aFocusMode = self.viewMenu.addAction(self.tr("Focus Mode"))
self.aFocusMode.setShortcut("F8") self.aFocusMode.setShortcut("F8")
self.aFocusMode.triggered.connect(lambda: self.mainGui.toggleFocusMode()) self.aFocusMode.triggered.connect(self.mainGui.toggleFocusMode)
# View > Toggle Full Screen # View > Toggle Full Screen
self.aFullScreen = self.viewMenu.addAction(self.tr("Full Screen Mode")) self.aFullScreen = self.viewMenu.addAction(self.tr("Full Screen Mode"))
@@ -350,22 +350,30 @@ class GuiMainMenu(QMenuBar):
# Insert > Short Dash # Insert > Short Dash
self.aInsENDash = self.mInsDashes.addAction(self.tr("Short Dash")) self.aInsENDash = self.mInsDashes.addAction(self.tr("Short Dash"))
self.aInsENDash.setShortcut("Ctrl+K, -") self.aInsENDash.setShortcut("Ctrl+K, -")
self.aInsENDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_ENDASH)) self.aInsENDash.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_ENDASH)
)
# Insert > Long Dash # Insert > Long Dash
self.aInsEMDash = self.mInsDashes.addAction(self.tr("Long Dash")) self.aInsEMDash = self.mInsDashes.addAction(self.tr("Long Dash"))
self.aInsEMDash.setShortcut("Ctrl+K, _") self.aInsEMDash.setShortcut("Ctrl+K, _")
self.aInsEMDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_EMDASH)) self.aInsEMDash.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_EMDASH)
)
# Insert > Long Dash # Insert > Long Dash
self.aInsHorBar = self.mInsDashes.addAction(self.tr("Horizontal Bar")) self.aInsHorBar = self.mInsDashes.addAction(self.tr("Horizontal Bar"))
self.aInsHorBar.setShortcut("Ctrl+K, Ctrl+_") self.aInsHorBar.setShortcut("Ctrl+K, Ctrl+_")
self.aInsHorBar.triggered.connect(lambda: self._docInsert(nwUnicode.U_HBAR)) self.aInsHorBar.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_HBAR)
)
# Insert > Figure Dash # Insert > Figure Dash
self.aInsFigDash = self.mInsDashes.addAction(self.tr("Figure Dash")) self.aInsFigDash = self.mInsDashes.addAction(self.tr("Figure Dash"))
self.aInsFigDash.setShortcut("Ctrl+K, ~") self.aInsFigDash.setShortcut("Ctrl+K, ~")
self.aInsFigDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_FGDASH)) self.aInsFigDash.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_FGDASH)
)
# Insert > Quote Marks # Insert > Quote Marks
self.mInsQuotes = self.insMenu.addMenu(self.tr("Quote Marks")) self.mInsQuotes = self.insMenu.addMenu(self.tr("Quote Marks"))
@@ -373,27 +381,37 @@ class GuiMainMenu(QMenuBar):
# Insert > Left Single Quote # Insert > Left Single Quote
self.aInsQuoteLS = self.mInsQuotes.addAction(self.tr("Left Single Quote")) self.aInsQuoteLS = self.mInsQuotes.addAction(self.tr("Left Single Quote"))
self.aInsQuoteLS.setShortcut("Ctrl+K, 1") self.aInsQuoteLS.setShortcut("Ctrl+K, 1")
self.aInsQuoteLS.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_LS)) self.aInsQuoteLS.triggered.connect(
lambda: self.requestDocInsert.emit(nwDocInsert.QUOTE_LS)
)
# Insert > Right Single Quote # Insert > Right Single Quote
self.aInsQuoteRS = self.mInsQuotes.addAction(self.tr("Right Single Quote")) self.aInsQuoteRS = self.mInsQuotes.addAction(self.tr("Right Single Quote"))
self.aInsQuoteRS.setShortcut("Ctrl+K, 2") self.aInsQuoteRS.setShortcut("Ctrl+K, 2")
self.aInsQuoteRS.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_RS)) self.aInsQuoteRS.triggered.connect(
lambda: self.requestDocInsert.emit(nwDocInsert.QUOTE_RS)
)
# Insert > Left Double Quote # Insert > Left Double Quote
self.aInsQuoteLD = self.mInsQuotes.addAction(self.tr("Left Double Quote")) self.aInsQuoteLD = self.mInsQuotes.addAction(self.tr("Left Double Quote"))
self.aInsQuoteLD.setShortcut("Ctrl+K, 3") self.aInsQuoteLD.setShortcut("Ctrl+K, 3")
self.aInsQuoteLD.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_LD)) self.aInsQuoteLD.triggered.connect(
lambda: self.requestDocInsert.emit(nwDocInsert.QUOTE_LD)
)
# Insert > Right Double Quote # Insert > Right Double Quote
self.aInsQuoteRD = self.mInsQuotes.addAction(self.tr("Right Double Quote")) self.aInsQuoteRD = self.mInsQuotes.addAction(self.tr("Right Double Quote"))
self.aInsQuoteRD.setShortcut("Ctrl+K, 4") self.aInsQuoteRD.setShortcut("Ctrl+K, 4")
self.aInsQuoteRD.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_RD)) self.aInsQuoteRD.triggered.connect(
lambda: self.requestDocInsert.emit(nwDocInsert.QUOTE_RD)
)
# Insert > Alternative Apostrophe # Insert > Alternative Apostrophe
self.aInsMSApos = self.mInsQuotes.addAction(self.tr("Alternative Apostrophe")) self.aInsMSApos = self.mInsQuotes.addAction(self.tr("Alternative Apostrophe"))
self.aInsMSApos.setShortcut("Ctrl+K, '") self.aInsMSApos.setShortcut("Ctrl+K, '")
self.aInsMSApos.triggered.connect(lambda: self._docInsert(nwUnicode.U_MAPOSS)) self.aInsMSApos.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_MAPOSS)
)
# Insert > Symbols # Insert > Symbols
self.mInsPunct = self.insMenu.addMenu(self.tr("General Punctuation")) self.mInsPunct = self.insMenu.addMenu(self.tr("General Punctuation"))
@@ -401,17 +419,23 @@ class GuiMainMenu(QMenuBar):
# Insert > Ellipsis # Insert > Ellipsis
self.aInsEllipsis = self.mInsPunct.addAction(self.tr("Ellipsis")) self.aInsEllipsis = self.mInsPunct.addAction(self.tr("Ellipsis"))
self.aInsEllipsis.setShortcut("Ctrl+K, .") self.aInsEllipsis.setShortcut("Ctrl+K, .")
self.aInsEllipsis.triggered.connect(lambda: self._docInsert(nwUnicode.U_HELLIP)) self.aInsEllipsis.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_HELLIP)
)
# Insert > Prime # Insert > Prime
self.aInsPrime = self.mInsPunct.addAction(self.tr("Prime")) self.aInsPrime = self.mInsPunct.addAction(self.tr("Prime"))
self.aInsPrime.setShortcut("Ctrl+K, Ctrl+'") self.aInsPrime.setShortcut("Ctrl+K, Ctrl+'")
self.aInsPrime.triggered.connect(lambda: self._docInsert(nwUnicode.U_PRIME)) self.aInsPrime.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_PRIME)
)
# Insert > Double Prime # Insert > Double Prime
self.aInsDPrime = self.mInsPunct.addAction(self.tr("Double Prime")) self.aInsDPrime = self.mInsPunct.addAction(self.tr("Double Prime"))
self.aInsDPrime.setShortcut("Ctrl+K, Ctrl+\"") self.aInsDPrime.setShortcut("Ctrl+K, Ctrl+\"")
self.aInsDPrime.triggered.connect(lambda: self._docInsert(nwUnicode.U_DPRIME)) self.aInsDPrime.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_DPRIME)
)
# Insert > White Spaces # Insert > White Spaces
self.mInsSpace = self.insMenu.addMenu(self.tr("White Spaces")) self.mInsSpace = self.insMenu.addMenu(self.tr("White Spaces"))
@@ -419,17 +443,23 @@ class GuiMainMenu(QMenuBar):
# Insert > Non-Breaking Space # Insert > Non-Breaking Space
self.aInsNBSpace = self.mInsSpace.addAction(self.tr("Non-Breaking Space")) self.aInsNBSpace = self.mInsSpace.addAction(self.tr("Non-Breaking Space"))
self.aInsNBSpace.setShortcut("Ctrl+K, Space") self.aInsNBSpace.setShortcut("Ctrl+K, Space")
self.aInsNBSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_NBSP)) self.aInsNBSpace.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_NBSP)
)
# Insert > Thin Space # Insert > Thin Space
self.aInsThinSpace = self.mInsSpace.addAction(self.tr("Thin Space")) self.aInsThinSpace = self.mInsSpace.addAction(self.tr("Thin Space"))
self.aInsThinSpace.setShortcut("Ctrl+K, Shift+Space") self.aInsThinSpace.setShortcut("Ctrl+K, Shift+Space")
self.aInsThinSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_THSP)) self.aInsThinSpace.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_THSP)
)
# Insert > Thin Non-Breaking Space # Insert > Thin Non-Breaking Space
self.aInsThinNBSpace = self.mInsSpace.addAction(self.tr("Thin Non-Breaking Space")) self.aInsThinNBSpace = self.mInsSpace.addAction(self.tr("Thin Non-Breaking Space"))
self.aInsThinNBSpace.setShortcut("Ctrl+K, Ctrl+Space") self.aInsThinNBSpace.setShortcut("Ctrl+K, Ctrl+Space")
self.aInsThinNBSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_THNBSP)) self.aInsThinNBSpace.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_THNBSP)
)
# Insert > Symbols # Insert > Symbols
self.mInsSymbol = self.insMenu.addMenu(self.tr("Other Symbols")) self.mInsSymbol = self.insMenu.addMenu(self.tr("Other Symbols"))
@@ -437,42 +467,58 @@ class GuiMainMenu(QMenuBar):
# Insert > List Bullet # Insert > List Bullet
self.aInsBullet = self.mInsSymbol.addAction(self.tr("List Bullet")) self.aInsBullet = self.mInsSymbol.addAction(self.tr("List Bullet"))
self.aInsBullet.setShortcut("Ctrl+K, *") self.aInsBullet.setShortcut("Ctrl+K, *")
self.aInsBullet.triggered.connect(lambda: self._docInsert(nwUnicode.U_BULL)) self.aInsBullet.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_BULL)
)
# Insert > Hyphen Bullet # Insert > Hyphen Bullet
self.aInsHyBull = self.mInsSymbol.addAction(self.tr("Hyphen Bullet")) self.aInsHyBull = self.mInsSymbol.addAction(self.tr("Hyphen Bullet"))
self.aInsHyBull.setShortcut("Ctrl+K, Ctrl+-") self.aInsHyBull.setShortcut("Ctrl+K, Ctrl+-")
self.aInsHyBull.triggered.connect(lambda: self._docInsert(nwUnicode.U_HYBULL)) self.aInsHyBull.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_HYBULL)
)
# Insert > Flower Mark # Insert > Flower Mark
self.aInsFlower = self.mInsSymbol.addAction(self.tr("Flower Mark")) self.aInsFlower = self.mInsSymbol.addAction(self.tr("Flower Mark"))
self.aInsFlower.setShortcut("Ctrl+K, Ctrl+*") self.aInsFlower.setShortcut("Ctrl+K, Ctrl+*")
self.aInsFlower.triggered.connect(lambda: self._docInsert(nwUnicode.U_FLOWER)) self.aInsFlower.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_FLOWER)
)
# Insert > Per Mille # Insert > Per Mille
self.aInsPerMille = self.mInsSymbol.addAction(self.tr("Per Mille")) self.aInsPerMille = self.mInsSymbol.addAction(self.tr("Per Mille"))
self.aInsPerMille.setShortcut("Ctrl+K, %") self.aInsPerMille.setShortcut("Ctrl+K, %")
self.aInsPerMille.triggered.connect(lambda: self._docInsert(nwUnicode.U_PERMIL)) self.aInsPerMille.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_PERMIL)
)
# Insert > Degree Symbol # Insert > Degree Symbol
self.aInsDegree = self.mInsSymbol.addAction(self.tr("Degree Symbol")) self.aInsDegree = self.mInsSymbol.addAction(self.tr("Degree Symbol"))
self.aInsDegree.setShortcut("Ctrl+K, Ctrl+O") self.aInsDegree.setShortcut("Ctrl+K, Ctrl+O")
self.aInsDegree.triggered.connect(lambda: self._docInsert(nwUnicode.U_DEGREE)) self.aInsDegree.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_DEGREE)
)
# Insert > Minus Sign # Insert > Minus Sign
self.aInsMinus = self.mInsSymbol.addAction(self.tr("Minus Sign")) self.aInsMinus = self.mInsSymbol.addAction(self.tr("Minus Sign"))
self.aInsMinus.setShortcut("Ctrl+K, Ctrl+M") self.aInsMinus.setShortcut("Ctrl+K, Ctrl+M")
self.aInsMinus.triggered.connect(lambda: self._docInsert(nwUnicode.U_MINUS)) self.aInsMinus.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_MINUS)
)
# Insert > Times Sign # Insert > Times Sign
self.aInsTimes = self.mInsSymbol.addAction(self.tr("Times Sign")) self.aInsTimes = self.mInsSymbol.addAction(self.tr("Times Sign"))
self.aInsTimes.setShortcut("Ctrl+K, Ctrl+X") self.aInsTimes.setShortcut("Ctrl+K, Ctrl+X")
self.aInsTimes.triggered.connect(lambda: self._docInsert(nwUnicode.U_TIMES)) self.aInsTimes.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_TIMES)
)
# Insert > Division # Insert > Division
self.aInsDivide = self.mInsSymbol.addAction(self.tr("Division Sign")) self.aInsDivide = self.mInsSymbol.addAction(self.tr("Division Sign"))
self.aInsDivide.setShortcut("Ctrl+K, Ctrl+D") self.aInsDivide.setShortcut("Ctrl+K, Ctrl+D")
self.aInsDivide.triggered.connect(lambda: self._docInsert(nwUnicode.U_DIVIDE)) self.aInsDivide.triggered.connect(
lambda: self.requestDocInsertText.emit(nwUnicode.U_DIVIDE)
)
# Insert > Tags and References # Insert > Tags and References
self.mInsKeywords = self.insMenu.addMenu(self.tr("Tags and References")) self.mInsKeywords = self.insMenu.addMenu(self.tr("Tags and References"))
@@ -491,7 +537,7 @@ class GuiMainMenu(QMenuBar):
self.mInsKWItems[keyWord][0].setText(trConst(nwLabels.KEY_NAME[keyWord])) self.mInsKWItems[keyWord][0].setText(trConst(nwLabels.KEY_NAME[keyWord]))
self.mInsKWItems[keyWord][0].setShortcut(self.mInsKWItems[keyWord][1]) self.mInsKWItems[keyWord][0].setShortcut(self.mInsKWItems[keyWord][1])
self.mInsKWItems[keyWord][0].triggered.connect( self.mInsKWItems[keyWord][0].triggered.connect(
lambda n, keyWord=keyWord: self._insertKeyWord(keyWord) lambda n, keyWord=keyWord: self.requestDocKeyWordInsert.emit(keyWord)
) )
self.mInsKeywords.addAction(self.mInsKWItems[keyWord][0]) self.mInsKeywords.addAction(self.mInsKWItems[keyWord][0])
@@ -501,22 +547,30 @@ class GuiMainMenu(QMenuBar):
# Insert > Synopsis Comment # Insert > Synopsis Comment
self.aInsSynopsis = self.mInsComments.addAction(self.tr("Synopsis Comment")) self.aInsSynopsis = self.mInsComments.addAction(self.tr("Synopsis Comment"))
self.aInsSynopsis.setShortcut("Ctrl+K, S") self.aInsSynopsis.setShortcut("Ctrl+K, S")
self.aInsSynopsis.triggered.connect(lambda: self._docInsert(nwDocInsert.SYNOPSIS)) self.aInsSynopsis.triggered.connect(
lambda: self.requestDocInsert.emit(nwDocInsert.SYNOPSIS)
)
# Insert > Symbols # Insert > Symbols
self.mInsBreaks = self.insMenu.addMenu(self.tr("Page Break and Space")) self.mInsBreaks = self.insMenu.addMenu(self.tr("Page Break and Space"))
# Insert > New Page # Insert > New Page
self.aInsNewPage = self.mInsBreaks.addAction(self.tr("Page Break")) self.aInsNewPage = self.mInsBreaks.addAction(self.tr("Page Break"))
self.aInsNewPage.triggered.connect(lambda: self._docInsert(nwDocInsert.NEW_PAGE)) self.aInsNewPage.triggered.connect(
lambda: self.requestDocInsert.emit(nwDocInsert.NEW_PAGE)
)
# Insert > Vertical Space (Single) # Insert > Vertical Space (Single)
self.aInsVSpaceS = self.mInsBreaks.addAction(self.tr("Vertical Space (Single)")) self.aInsVSpaceS = self.mInsBreaks.addAction(self.tr("Vertical Space (Single)"))
self.aInsVSpaceS.triggered.connect(lambda: self._docInsert(nwDocInsert.VSPACE_S)) self.aInsVSpaceS.triggered.connect(
lambda: self.requestDocInsert.emit(nwDocInsert.VSPACE_S)
)
# Insert > Vertical Space (Multi) # Insert > Vertical Space (Multi)
self.aInsVSpaceM = self.mInsBreaks.addAction(self.tr("Vertical Space (Multi)")) self.aInsVSpaceM = self.mInsBreaks.addAction(self.tr("Vertical Space (Multi)"))
self.aInsVSpaceM.triggered.connect(lambda: self._docInsert(nwDocInsert.VSPACE_M)) self.aInsVSpaceM.triggered.connect(
lambda: self.requestDocInsert.emit(nwDocInsert.VSPACE_M)
)
# Insert > Placeholder Text # Insert > Placeholder Text
self.aLipsumText = self.mInsBreaks.addAction(self.tr("Placeholder Text")) self.aLipsumText = self.mInsBreaks.addAction(self.tr("Placeholder Text"))
@@ -532,17 +586,23 @@ class GuiMainMenu(QMenuBar):
# Format > Emphasis # Format > Emphasis
self.aFmtEmph = self.fmtMenu.addAction(self.tr("Emphasis")) self.aFmtEmph = self.fmtMenu.addAction(self.tr("Emphasis"))
self.aFmtEmph.setShortcut("Ctrl+I") self.aFmtEmph.setShortcut("Ctrl+I")
self.aFmtEmph.triggered.connect(lambda: self._docAction(nwDocAction.EMPH)) self.aFmtEmph.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.EMPH)
)
# Format > Strong Emphasis # Format > Strong Emphasis
self.aFmtStrong = self.fmtMenu.addAction(self.tr("Strong Emphasis")) self.aFmtStrong = self.fmtMenu.addAction(self.tr("Strong Emphasis"))
self.aFmtStrong.setShortcut("Ctrl+B") self.aFmtStrong.setShortcut("Ctrl+B")
self.aFmtStrong.triggered.connect(lambda: self._docAction(nwDocAction.STRONG)) self.aFmtStrong.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.STRONG)
)
# Format > Strikethrough # Format > Strikethrough
self.aFmtStrike = self.fmtMenu.addAction(self.tr("Strikethrough")) self.aFmtStrike = self.fmtMenu.addAction(self.tr("Strikethrough"))
self.aFmtStrike.setShortcut("Ctrl+D") self.aFmtStrike.setShortcut("Ctrl+D")
self.aFmtStrike.triggered.connect(lambda: self._docAction(nwDocAction.STRIKE)) self.aFmtStrike.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.STRIKE)
)
# Edit > Separator # Edit > Separator
self.fmtMenu.addSeparator() self.fmtMenu.addSeparator()
@@ -550,12 +610,16 @@ class GuiMainMenu(QMenuBar):
# Format > Double Quotes # Format > Double Quotes
self.aFmtDQuote = self.fmtMenu.addAction(self.tr("Wrap Double Quotes")) self.aFmtDQuote = self.fmtMenu.addAction(self.tr("Wrap Double Quotes"))
self.aFmtDQuote.setShortcut("Ctrl+\"") self.aFmtDQuote.setShortcut("Ctrl+\"")
self.aFmtDQuote.triggered.connect(lambda: self._docAction(nwDocAction.D_QUOTE)) self.aFmtDQuote.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.D_QUOTE)
)
# Format > Single Quotes # Format > Single Quotes
self.aFmtSQuote = self.fmtMenu.addAction(self.tr("Wrap Single Quotes")) self.aFmtSQuote = self.fmtMenu.addAction(self.tr("Wrap Single Quotes"))
self.aFmtSQuote.setShortcut("Ctrl+'") self.aFmtSQuote.setShortcut("Ctrl+'")
self.aFmtSQuote.triggered.connect(lambda: self._docAction(nwDocAction.S_QUOTE)) self.aFmtSQuote.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.S_QUOTE)
)
# Format > Separator # Format > Separator
self.fmtMenu.addSeparator() self.fmtMenu.addSeparator()
@@ -564,28 +628,40 @@ class GuiMainMenu(QMenuBar):
self.mShortcodes = self.fmtMenu.addMenu(self.tr("More Formats ...")) self.mShortcodes = self.fmtMenu.addMenu(self.tr("More Formats ..."))
# Shortcode Italic # Shortcode Italic
self.aScItalic = self.mShortcodes.addAction(self.tr("Italics Shortcode")) self.aScItalic = self.mShortcodes.addAction(self.tr("Italics (Shortcode)"))
self.aScItalic.triggered.connect(lambda: self._docAction(nwDocAction.SC_ITALIC)) self.aScItalic.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_ITALIC)
)
# Shortcode Bold # Shortcode Bold
self.aScBold = self.mShortcodes.addAction(self.tr("Bold Shortcode")) self.aScBold = self.mShortcodes.addAction(self.tr("Bold (Shortcode)"))
self.aScBold.triggered.connect(lambda: self._docAction(nwDocAction.SC_BOLD)) self.aScBold.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_BOLD)
# Shortcode Underline )
self.aScULine = self.mShortcodes.addAction(self.tr("Underline Shortcode"))
self.aScULine.triggered.connect(lambda: self._docAction(nwDocAction.SC_ULINE))
# Shortcode Strikethrough # Shortcode Strikethrough
self.aScStrike = self.mShortcodes.addAction(self.tr("Strikethrough Shortcode")) self.aScStrike = self.mShortcodes.addAction(self.tr("Strikethrough (Shortcode)"))
self.aScStrike.triggered.connect(lambda: self._docAction(nwDocAction.SC_STRIKE)) self.aScStrike.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_STRIKE)
)
# Shortcode Underline
self.aScULine = self.mShortcodes.addAction(self.tr("Underline"))
self.aScULine.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE)
)
# Shortcode Superscript # Shortcode Superscript
self.aScSuper = self.mShortcodes.addAction(self.tr("Superscript")) self.aScSuper = self.mShortcodes.addAction(self.tr("Superscript"))
self.aScSuper.triggered.connect(lambda: self._docAction(nwDocAction.SC_SUP)) self.aScSuper.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_SUP)
)
# Shortcode Subscript # Shortcode Subscript
self.aScSub = self.mShortcodes.addAction(self.tr("Subscript")) self.aScSub = self.mShortcodes.addAction(self.tr("Subscript"))
self.aScSub.triggered.connect(lambda: self._docAction(nwDocAction.SC_SUB)) self.aScSub.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_SUB)
)
# Format > Separator # Format > Separator
self.fmtMenu.addSeparator() self.fmtMenu.addSeparator()
@@ -593,33 +669,45 @@ class GuiMainMenu(QMenuBar):
# Format > Header 1 (Partition) # Format > Header 1 (Partition)
self.aFmtHead1 = self.fmtMenu.addAction(self.tr("Header 1 (Partition)")) self.aFmtHead1 = self.fmtMenu.addAction(self.tr("Header 1 (Partition)"))
self.aFmtHead1.setShortcut("Ctrl+1") self.aFmtHead1.setShortcut("Ctrl+1")
self.aFmtHead1.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H1)) self.aFmtHead1.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H1)
)
# Format > Header 2 (Chapter) # Format > Header 2 (Chapter)
self.aFmtHead2 = self.fmtMenu.addAction(self.tr("Header 2 (Chapter)")) self.aFmtHead2 = self.fmtMenu.addAction(self.tr("Header 2 (Chapter)"))
self.aFmtHead2.setShortcut("Ctrl+2") self.aFmtHead2.setShortcut("Ctrl+2")
self.aFmtHead2.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H2)) self.aFmtHead2.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H2)
)
# Format > Header 3 (Scene) # Format > Header 3 (Scene)
self.aFmtHead3 = self.fmtMenu.addAction(self.tr("Header 3 (Scene)")) self.aFmtHead3 = self.fmtMenu.addAction(self.tr("Header 3 (Scene)"))
self.aFmtHead3.setShortcut("Ctrl+3") self.aFmtHead3.setShortcut("Ctrl+3")
self.aFmtHead3.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H3)) self.aFmtHead3.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H3)
)
# Format > Header 4 (Section) # Format > Header 4 (Section)
self.aFmtHead4 = self.fmtMenu.addAction(self.tr("Header 4 (Section)")) self.aFmtHead4 = self.fmtMenu.addAction(self.tr("Header 4 (Section)"))
self.aFmtHead4.setShortcut("Ctrl+4") self.aFmtHead4.setShortcut("Ctrl+4")
self.aFmtHead4.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H4)) self.aFmtHead4.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H4)
)
# Format > Separator # Format > Separator
self.fmtMenu.addSeparator() self.fmtMenu.addSeparator()
# Format > Novel Title # Format > Novel Title
self.aFmtTitle = self.fmtMenu.addAction(self.tr("Novel Title")) self.aFmtTitle = self.fmtMenu.addAction(self.tr("Novel Title"))
self.aFmtTitle.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TTL)) self.aFmtTitle.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_TTL)
)
# Format > Unnumbered Chapter # Format > Unnumbered Chapter
self.aFmtUnNum = self.fmtMenu.addAction(self.tr("Unnumbered Chapter")) self.aFmtUnNum = self.fmtMenu.addAction(self.tr("Unnumbered Chapter"))
self.aFmtUnNum.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_UNN)) self.aFmtUnNum.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_UNN)
)
# Format > Separator # Format > Separator
self.fmtMenu.addSeparator() self.fmtMenu.addSeparator()
@@ -627,17 +715,23 @@ class GuiMainMenu(QMenuBar):
# Format > Align Left # Format > Align Left
self.aFmtAlignLeft = self.fmtMenu.addAction(self.tr("Align Left")) self.aFmtAlignLeft = self.fmtMenu.addAction(self.tr("Align Left"))
self.aFmtAlignLeft.setShortcut("Ctrl+5") self.aFmtAlignLeft.setShortcut("Ctrl+5")
self.aFmtAlignLeft.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_L)) self.aFmtAlignLeft.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.ALIGN_L)
)
# Format > Align Centre # Format > Align Centre
self.aFmtAlignCentre = self.fmtMenu.addAction(self.tr("Align Centre")) self.aFmtAlignCentre = self.fmtMenu.addAction(self.tr("Align Centre"))
self.aFmtAlignCentre.setShortcut("Ctrl+6") self.aFmtAlignCentre.setShortcut("Ctrl+6")
self.aFmtAlignCentre.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_C)) self.aFmtAlignCentre.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.ALIGN_C)
)
# Format > Align Right # Format > Align Right
self.aFmtAlignRight = self.fmtMenu.addAction(self.tr("Align Right")) self.aFmtAlignRight = self.fmtMenu.addAction(self.tr("Align Right"))
self.aFmtAlignRight.setShortcut("Ctrl+7") self.aFmtAlignRight.setShortcut("Ctrl+7")
self.aFmtAlignRight.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_R)) self.aFmtAlignRight.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.ALIGN_R)
)
# Format > Separator # Format > Separator
self.fmtMenu.addSeparator() self.fmtMenu.addSeparator()
@@ -645,12 +739,16 @@ class GuiMainMenu(QMenuBar):
# Format > Indent Left # Format > Indent Left
self.aFmtIndentLeft = self.fmtMenu.addAction(self.tr("Indent Left")) self.aFmtIndentLeft = self.fmtMenu.addAction(self.tr("Indent Left"))
self.aFmtIndentLeft.setShortcut("Ctrl+8") self.aFmtIndentLeft.setShortcut("Ctrl+8")
self.aFmtIndentLeft.triggered.connect(lambda: self._docAction(nwDocAction.INDENT_L)) self.aFmtIndentLeft.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.INDENT_L)
)
# Format > Indent Right # Format > Indent Right
self.aFmtIndentRight = self.fmtMenu.addAction(self.tr("Indent Right")) self.aFmtIndentRight = self.fmtMenu.addAction(self.tr("Indent Right"))
self.aFmtIndentRight.setShortcut("Ctrl+9") self.aFmtIndentRight.setShortcut("Ctrl+9")
self.aFmtIndentRight.triggered.connect(lambda: self._docAction(nwDocAction.INDENT_R)) self.aFmtIndentRight.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.INDENT_R)
)
# Format > Separator # Format > Separator
self.fmtMenu.addSeparator() self.fmtMenu.addSeparator()
@@ -658,27 +756,37 @@ class GuiMainMenu(QMenuBar):
# Format > Comment # Format > Comment
self.aFmtComment = self.fmtMenu.addAction(self.tr("Toggle Comment")) self.aFmtComment = self.fmtMenu.addAction(self.tr("Toggle Comment"))
self.aFmtComment.setShortcut("Ctrl+/") self.aFmtComment.setShortcut("Ctrl+/")
self.aFmtComment.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_COM)) self.aFmtComment.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_COM)
)
# Format > Remove Block Format # Format > Remove Block Format
self.aFmtNoFormat = self.fmtMenu.addAction(self.tr("Remove Block Format")) self.aFmtNoFormat = self.fmtMenu.addAction(self.tr("Remove Block Format"))
self.aFmtNoFormat.setShortcuts(["Ctrl+0", "Ctrl+Shift+/"]) self.aFmtNoFormat.setShortcuts(["Ctrl+0", "Ctrl+Shift+/"])
self.aFmtNoFormat.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TXT)) self.aFmtNoFormat.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.BLOCK_TXT)
)
# Format > Separator # Format > Separator
self.fmtMenu.addSeparator() self.fmtMenu.addSeparator()
# Format > Replace Single Quotes # Format > Replace Single Quotes
self.aFmtReplSng = self.fmtMenu.addAction(self.tr("Convert Single Quotes")) self.aFmtReplSng = self.fmtMenu.addAction(self.tr("Convert Single Quotes"))
self.aFmtReplSng.triggered.connect(lambda: self._docAction(nwDocAction.REPL_SNG)) self.aFmtReplSng.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.REPL_SNG)
)
# Format > Replace Double Quotes # Format > Replace Double Quotes
self.aFmtReplDbl = self.fmtMenu.addAction(self.tr("Convert Double Quotes")) self.aFmtReplDbl = self.fmtMenu.addAction(self.tr("Convert Double Quotes"))
self.aFmtReplDbl.triggered.connect(lambda: self._docAction(nwDocAction.REPL_DBL)) self.aFmtReplDbl.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.REPL_DBL)
)
# Format > Remove In-Paragraph Breaks # Format > Remove In-Paragraph Breaks
self.aFmtRmBreaks = self.fmtMenu.addAction(self.tr("Remove In-Paragraph Breaks")) self.aFmtRmBreaks = self.fmtMenu.addAction(self.tr("Remove In-Paragraph Breaks"))
self.aFmtRmBreaks.triggered.connect(lambda: self._docAction(nwDocAction.RM_BREAKS)) self.aFmtRmBreaks.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.RM_BREAKS)
)
return return
+101 -125
View File
@@ -29,9 +29,10 @@ import logging
from enum import Enum from enum import Enum
from time import time from time import time
from typing import TYPE_CHECKING
from PyQt5.QtGui import QFont, QPalette from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal from PyQt5.QtCore import QModelIndex, QPoint, Qt, QSize, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView,
QInputDialog, QMenu, QSizePolicy, QToolButton, QToolTip, QTreeWidget, QInputDialog, QMenu, QSizePolicy, QToolButton, QToolTip, QTreeWidget,
@@ -42,8 +43,12 @@ from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocMode, nwItemClass, nwOutline from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
from novelwriter.common import minmax from novelwriter.common import minmax
from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
from novelwriter.core.index import IndexHeading
from novelwriter.extensions.novelselector import NovelSelector from novelwriter.extensions.novelselector import NovelSelector
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -63,7 +68,7 @@ class GuiNovelView(QWidget):
selectedItemChanged = pyqtSignal(str) selectedItemChanged = pyqtSignal(str)
openDocumentRequest = pyqtSignal(str, Enum, str, bool) openDocumentRequest = pyqtSignal(str, Enum, str, bool)
def __init__(self, mainGui): def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui) super().__init__(parent=mainGui)
self.mainGui = mainGui self.mainGui = mainGui
@@ -92,33 +97,29 @@ class GuiNovelView(QWidget):
# Methods # Methods
## ##
def updateTheme(self): def updateTheme(self) -> None:
"""Update theme elements. """Update theme elements."""
"""
self.novelBar.updateTheme() self.novelBar.updateTheme()
self.novelTree.updateTheme() self.novelTree.updateTheme()
self.refreshTree() self.refreshTree()
return return
def initSettings(self): def initSettings(self) -> None:
"""Initialise GUI elements that depend on specific settings. """Initialise GUI elements that depend on specific settings."""
"""
self.novelTree.initSettings() self.novelTree.initSettings()
return return
def clearNovelView(self): def clearNovelView(self) -> None:
"""Clear project-related GUI content. """Clear project-related GUI content."""
"""
self.novelTree.clearContent() self.novelTree.clearContent()
self.novelBar.clearContent() self.novelBar.clearContent()
self.novelBar.setEnabled(False) self.novelBar.setEnabled(False)
return return
def openProjectTasks(self): def openProjectTasks(self) -> None:
"""Run open project tasks. """Run open project tasks."""
"""
lastNovel = SHARED.project.data.getLastHandle("novelTree") lastNovel = SHARED.project.data.getLastHandle("novelTree")
if lastNovel not in SHARED.project.tree: if lastNovel and lastNovel not in SHARED.project.tree:
lastNovel = SHARED.project.tree.findRoot(nwItemClass.NOVEL) lastNovel = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
logger.debug("Setting novel tree to root item '%s'", lastNovel) logger.debug("Setting novel tree to root item '%s'", lastNovel)
@@ -140,7 +141,7 @@ class GuiNovelView(QWidget):
return return
def closeProjectTasks(self): def closeProjectTasks(self) -> None:
"""Run closing project tasks.""" """Run closing project tasks."""
lastColType = self.novelTree.lastColType lastColType = self.novelTree.lastColType
lastColSize = self.novelTree.lastColSize lastColSize = self.novelTree.lastColSize
@@ -150,15 +151,13 @@ class GuiNovelView(QWidget):
self.clearNovelView() self.clearNovelView()
return return
def setTreeFocus(self): def setTreeFocus(self) -> None:
"""Set the focus to the tree widget. """Set the focus to the tree widget."""
"""
self.novelTree.setFocus() self.novelTree.setFocus()
return return
def treeHasFocus(self): def treeHasFocus(self) -> bool:
"""Check if the novel tree has focus. """Check if the novel tree has focus."""
"""
return self.novelTree.hasFocus() return self.novelTree.hasFocus()
## ##
@@ -166,21 +165,19 @@ class GuiNovelView(QWidget):
## ##
@pyqtSlot() @pyqtSlot()
def refreshTree(self): def refreshTree(self) -> None:
"""Refresh the current tree. """Refresh the current tree."""
"""
self.novelTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("novelTree")) self.novelTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("novelTree"))
return return
@pyqtSlot(str) @pyqtSlot(str)
def updateRootItem(self, tHandle): def updateRootItem(self, tHandle: str) -> None:
"""If any root item changes, rebuild the novel root menu. """If any root item changes, rebuild the novel root menu."""
"""
self.novelBar.buildNovelRootMenu() self.novelBar.buildNovelRootMenu()
return return
@pyqtSlot(str) @pyqtSlot(str)
def updateNovelItemMeta(self, tHandle): def updateNovelItemMeta(self, tHandle: str) -> None:
"""The meta data of a novel item has changed, and the tree item """The meta data of a novel item has changed, and the tree item
needs to be refreshed. needs to be refreshed.
""" """
@@ -192,7 +189,7 @@ class GuiNovelView(QWidget):
class GuiNovelToolBar(QWidget): class GuiNovelToolBar(QWidget):
def __init__(self, novelView): def __init__(self, novelView: GuiNovelView) -> None:
super().__init__(parent=novelView) super().__init__(parent=novelView)
logger.debug("Create: GuiNovelToolBar") logger.debug("Create: GuiNovelToolBar")
@@ -269,9 +266,8 @@ class GuiNovelToolBar(QWidget):
# Methods # Methods
## ##
def updateTheme(self): def updateTheme(self) -> None:
"""Update theme elements. """Update theme elements."""
"""
# Icons # Icons
self.tbNovel.setIcon(SHARED.theme.getIcon("cls_novel")) self.tbNovel.setIcon(SHARED.theme.getIcon("cls_novel"))
self.tbRefresh.setIcon(SHARED.theme.getIcon("refresh")) self.tbRefresh.setIcon(SHARED.theme.getIcon("refresh"))
@@ -287,10 +283,11 @@ class GuiNovelToolBar(QWidget):
"QToolButton {{padding: {0}px; border: none; background: transparent;}} " "QToolButton {{padding: {0}px; border: none; background: transparent;}} "
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}" "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue()) ).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
self.tbNovel.setStyleSheet(buttonStyle) self.tbNovel.setStyleSheet(buttonStyle)
self.tbRefresh.setStyleSheet(buttonStyle) self.tbRefresh.setStyleSheet(buttonStyle)
self.tbMore.setStyleSheet(buttonStyle) self.tbMore.setStyleSheet(buttonStyleMenu)
self.novelValue.setStyleSheet( self.novelValue.setStyleSheet(
"QComboBox {border-style: none; padding-left: 0;} " "QComboBox {border-style: none; padding-left: 0;} "
@@ -301,30 +298,26 @@ class GuiNovelToolBar(QWidget):
return return
def clearContent(self): def clearContent(self) -> None:
"""Run clearing project tasks. """Run clearing project tasks."""
"""
self.novelValue.clear() self.novelValue.clear()
self.novelValue.setToolTip("") self.novelValue.setToolTip("")
return return
def buildNovelRootMenu(self): def buildNovelRootMenu(self) -> None:
"""Build the novel root menu. """Build the novel root menu."""
"""
self.novelValue.updateList(prefix=self.novelPrefix) self.novelValue.updateList(prefix=self.novelPrefix)
self.tbNovel.setVisible(self.novelValue.count() > 1) self.tbNovel.setVisible(self.novelValue.count() > 1)
return return
def setCurrentRoot(self, rootHandle): def setCurrentRoot(self, rootHandle: str | None) -> None:
"""Set the current active root handle. """Set the current active root handle."""
"""
self.novelValue.setHandle(rootHandle) self.novelValue.setHandle(rootHandle)
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True) self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
return return
def setLastColType(self, colType, doRefresh=True): def setLastColType(self, colType: NovelTreeColumn, doRefresh: bool = True) -> None:
"""Set the last column type. """Set the last column type."""
"""
self.aLastCol[colType].setChecked(True) self.aLastCol[colType].setChecked(True)
self.novelView.novelTree.setLastColType(colType, doRefresh=doRefresh) self.novelView.novelTree.setLastColType(colType, doRefresh=doRefresh)
return return
@@ -334,24 +327,21 @@ class GuiNovelToolBar(QWidget):
## ##
@pyqtSlot() @pyqtSlot()
def _openNovelSelector(self): def _openNovelSelector(self) -> None:
"""Trigger the dropdown list of the novel selector. """Trigger the dropdown list of the novel selector."""
"""
self.novelValue.showPopup() self.novelValue.showPopup()
return return
@pyqtSlot() @pyqtSlot()
def _refreshNovelTree(self): def _refreshNovelTree(self) -> None:
"""Rebuild the current tree. """Rebuild the current tree."""
"""
rootHandle = SHARED.project.data.getLastHandle("novelTree") rootHandle = SHARED.project.data.getLastHandle("novelTree")
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True) self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
return return
@pyqtSlot() @pyqtSlot()
def _selectLastColumnSize(self): def _selectLastColumnSize(self) -> None:
"""Set the maximum width for the last column. """Set the maximum width for the last column."""
"""
oldSize = self.novelView.novelTree.lastColSize oldSize = self.novelView.novelTree.lastColSize
newSize, isOk = QInputDialog.getInt( newSize, isOk = QInputDialog.getInt(
self, self.tr("Column Size"), self.tr("Maximum column size in %"), oldSize, 15, 75, 5 self, self.tr("Column Size"), self.tr("Maximum column size in %"), oldSize, 15, 75, 5
@@ -365,9 +355,8 @@ class GuiNovelToolBar(QWidget):
# Internal Functions # Internal Functions
## ##
def _addLastColAction(self, colType, actionLabel): def _addLastColAction(self, colType, actionLabel) -> None:
"""Add a column selection entry to the last column menu. """Add a column selection entry to the last column menu."""
"""
aLast = self.mLastCol.addAction(actionLabel) aLast = self.mLastCol.addAction(actionLabel)
aLast.setCheckable(True) aLast.setCheckable(True)
aLast.setActionGroup(self.gLastCol) aLast.setActionGroup(self.gLastCol)
@@ -391,7 +380,7 @@ class GuiNovelTree(QTreeWidget):
D_KEY = Qt.ItemDataRole.UserRole + 2 D_KEY = Qt.ItemDataRole.UserRole + 2
D_EXTRA = Qt.ItemDataRole.UserRole + 3 D_EXTRA = Qt.ItemDataRole.UserRole + 3
def __init__(self, novelView): def __init__(self, novelView: GuiNovelView) -> None:
super().__init__(parent=novelView) super().__init__(parent=novelView)
logger.debug("Create: GuiNovelTree") logger.debug("Create: GuiNovelTree")
@@ -461,9 +450,8 @@ class GuiNovelTree(QTreeWidget):
return return
def initSettings(self): def initSettings(self) -> None:
"""Set or update tree widget settings. """Set or update tree widget settings."""
"""
# Scroll bars # Scroll bars
if CONFIG.hideVScroll: if CONFIG.hideVScroll:
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
@@ -477,11 +465,10 @@ class GuiNovelTree(QTreeWidget):
return return
def updateTheme(self): def updateTheme(self) -> None:
"""Update theme elements. """Update theme elements."""
"""
iPx = SHARED.theme.baseIconSize iPx = SHARED.theme.baseIconSize
self._pMore = SHARED.theme.loadDecoration("deco_doc_more", pxH=iPx) self._pMore = SHARED.theme.loadDecoration("deco_doc_more", h=iPx)
return return
## ##
@@ -489,28 +476,26 @@ class GuiNovelTree(QTreeWidget):
## ##
@property @property
def lastColType(self): def lastColType(self) -> NovelTreeColumn:
return self._lastCol return self._lastCol
@property @property
def lastColSize(self): def lastColSize(self) -> int:
return int(self._lastColSize * 100) return int(self._lastColSize * 100)
## ##
# Class Methods # Class Methods
## ##
def clearContent(self): def clearContent(self) -> None:
"""Clear the GUI content and the related maps. """Clear the GUI content and the related maps."""
"""
self.clear() self.clear()
self._treeMap = {} self._treeMap = {}
self._lastBuild = 0 self._lastBuild = 0
return return
def refreshTree(self, rootHandle=None, overRide=False): def refreshTree(self, rootHandle: str | None = None, overRide: bool = False) -> None:
"""Refresh the tree if it has been changed. """Refresh the tree if it has been changed."""
"""
logger.debug("Requesting refresh of the novel tree") logger.debug("Requesting refresh of the novel tree")
if rootHandle is None: if rootHandle is None:
rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL) rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
@@ -534,9 +519,8 @@ class GuiNovelTree(QTreeWidget):
return return
def refreshHandle(self, tHandle): def refreshHandle(self, tHandle: str) -> None:
"""Refresh the data for a given handle. """Refresh the data for a given handle."""
"""
idxData = SHARED.project.index.getItemData(tHandle) idxData = SHARED.project.index.getItemData(tHandle)
if idxData is None: if idxData is None:
return return
@@ -554,7 +538,7 @@ class GuiNovelTree(QTreeWidget):
return return
def getSelectedHandle(self): def getSelectedHandle(self) -> tuple[str | None, str | None]:
"""Get the currently selected or active handle. If multiple """Get the currently selected or active handle. If multiple
items are selected, return the first. items are selected, return the first.
""" """
@@ -566,9 +550,8 @@ class GuiNovelTree(QTreeWidget):
return tHandle, sTitle return tHandle, sTitle
return None, None return None, None
def setLastColType(self, colType, doRefresh=True): def setLastColType(self, colType: NovelTreeColumn, doRefresh: bool = True) -> None:
"""Change the content type of the last column and rebuild. """Change the content type of the last column and rebuild."""
"""
if self._lastCol != colType: if self._lastCol != colType:
logger.debug("Changing last column to %s", colType.name) logger.debug("Changing last column to %s", colType.name)
self._lastCol = colType self._lastCol = colType
@@ -578,15 +561,13 @@ class GuiNovelTree(QTreeWidget):
self.refreshTree(rootHandle=lastNovel, overRide=True) self.refreshTree(rootHandle=lastNovel, overRide=True)
return return
def setLastColSize(self, colSize): def setLastColSize(self, colSize: int) -> None:
"""Set the column size in integer values between 15 and 75. """Set the column size in integer values between 15 and 75."""
"""
self._lastColSize = minmax(colSize, 15, 75)/100.0 self._lastColSize = minmax(colSize, 15, 75)/100.0
return return
def setActiveHandle(self, tHandle): def setActiveHandle(self, tHandle: str | None) -> None:
"""Highlight the rows associated with a given handle. """Highlight the rows associated with a given handle."""
"""
tStart = time() tStart = time()
self._actHandle = tHandle self._actHandle = tHandle
@@ -612,20 +593,20 @@ class GuiNovelTree(QTreeWidget):
# Events # Events
## ##
def mousePressEvent(self, theEvent): def mousePressEvent(self, event: QMouseEvent) -> None:
"""Overload mousePressEvent to clear selection if clicking the """Overload mousePressEvent to clear selection if clicking the
mouse in a blank area of the tree view, and to load a document mouse in a blank area of the tree view, and to load a document
for viewing if the user middle-clicked. for viewing if the user middle-clicked.
""" """
super().mousePressEvent(theEvent) super().mousePressEvent(event)
if theEvent.button() == Qt.LeftButton: if event.button() == Qt.LeftButton:
selItem = self.indexAt(theEvent.pos()) selItem = self.indexAt(event.pos())
if not selItem.isValid(): if not selItem.isValid():
self.clearSelection() self.clearSelection()
elif theEvent.button() == Qt.MiddleButton: elif event.button() == Qt.MiddleButton:
selItem = self.itemAt(theEvent.pos()) selItem = self.itemAt(event.pos())
if not isinstance(selItem, QTreeWidgetItem): if not isinstance(selItem, QTreeWidgetItem):
return return
@@ -637,16 +618,14 @@ class GuiNovelTree(QTreeWidget):
return return
def focusOutEvent(self, theEvent): def focusOutEvent(self, event: QFocusEvent) -> None:
"""Clear the selection when the tree no longer has focus. """Clear the selection when the tree no longer has focus."""
""" super().focusOutEvent(event)
super().focusOutEvent(theEvent)
self.clearSelection() self.clearSelection()
return return
def resizeEvent(self, event): def resizeEvent(self, event: QResizeEvent) -> None:
"""Elide labels in the extra column. """Elide labels in the extra column."""
"""
super().resizeEvent(event) super().resizeEvent(event)
newW = event.size().width() newW = event.size().width()
oldW = event.oldSize().width() oldW = event.oldSize().width()
@@ -665,18 +644,17 @@ class GuiNovelTree(QTreeWidget):
## ##
@pyqtSlot("QModelIndex") @pyqtSlot("QModelIndex")
def _treeItemClicked(self, mIndex): def _treeItemClicked(self, index: QModelIndex) -> None:
"""The user clicked on an item in the tree. """The user clicked on an item in the tree."""
""" if index.column() == self.C_MORE:
if mIndex.column() == self.C_MORE: tHandle = index.siblingAtColumn(self.C_DATA).data(self.D_HANDLE)
tHandle = mIndex.siblingAtColumn(self.C_DATA).data(self.D_HANDLE) sTitle = index.siblingAtColumn(self.C_DATA).data(self.D_TITLE)
sTitle = mIndex.siblingAtColumn(self.C_DATA).data(self.D_TITLE) tipPos = self.mapToGlobal(self.visualRect(index).topRight())
tipPos = self.mapToGlobal(self.visualRect(mIndex).topRight())
self._popMetaBox(tipPos, tHandle, sTitle) self._popMetaBox(tipPos, tHandle, sTitle)
return return
@pyqtSlot() @pyqtSlot()
def _treeSelectionChange(self): def _treeSelectionChange(self) -> None:
"""Extract the handle and line number of the currently selected """Extract the handle and line number of the currently selected
title, and send it to the tree meta panel. title, and send it to the tree meta panel.
""" """
@@ -686,7 +664,7 @@ class GuiNovelTree(QTreeWidget):
return return
@pyqtSlot("QTreeWidgetItem*", int) @pyqtSlot("QTreeWidgetItem*", int)
def _treeDoubleClick(self, tItem, colNo): def _treeDoubleClick(self, item: QTreeWidgetItem, column: int) -> None:
"""Extract the handle and line number of the title double- """Extract the handle and line number of the title double-
clicked, and send it to the main gui class for opening in the clicked, and send it to the main gui class for opening in the
document editor. document editor.
@@ -699,9 +677,8 @@ class GuiNovelTree(QTreeWidget):
# Internal Functions # Internal Functions
## ##
def _populateTree(self, rootHandle): def _populateTree(self, rootHandle: str | None) -> None:
"""Build the tree based on the project index. """Build the tree based on the project index."""
"""
self.clearContent() self.clearContent()
tStart = time() tStart = time()
logger.debug("Building novel tree for root item '%s'", rootHandle) logger.debug("Building novel tree for root item '%s'", rootHandle)
@@ -728,9 +705,9 @@ class GuiNovelTree(QTreeWidget):
return return
def _updateTreeItemValues(self, trItem, idxItem, tHandle, sTitle): def _updateTreeItemValues(self, trItem: QTreeWidgetItem, idxItem: IndexHeading,
"""Set the tree item values from the index entry. tHandle: str, sTitle: str) -> None:
""" """Set the tree item values from the index entry."""
iLevel = nwHeaders.H_LEVEL.get(idxItem.level, 0) iLevel = nwHeaders.H_LEVEL.get(idxItem.level, 0)
hDec = SHARED.theme.getHeaderDecoration(iLevel) hDec = SHARED.theme.getHeaderDecoration(iLevel)
@@ -750,9 +727,8 @@ class GuiNovelTree(QTreeWidget):
return return
def _getLastColumnText(self, tHandle, sTitle): def _getLastColumnText(self, tHandle: str, sTitle: str) -> tuple[str, str]:
"""Generate the text for the last column based on user settings. """Generate text for the last column based on user settings."""
"""
if self._lastCol == NovelTreeColumn.HIDDEN: if self._lastCol == NovelTreeColumn.HIDDEN:
return "", "" return "", ""
@@ -777,14 +753,15 @@ class GuiNovelTree(QTreeWidget):
return "", "" return "", ""
def _popMetaBox(self, qPos, tHandle, sTitle): def _popMetaBox(self, qPos: QPoint, tHandle: str, sTitle: str) -> None:
"""Show the novel meta data box. """Show the novel meta data box."""
"""
logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle) logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
pIndex = SHARED.project.index pIndex = SHARED.project.index
novIdx = pIndex.getItemHeader(tHandle, sTitle) novIdx = pIndex.getItemHeader(tHandle, sTitle)
refTags = pIndex.getReferences(tHandle, sTitle) refTags = pIndex.getReferences(tHandle, sTitle)
if not novIdx:
return
synopText = novIdx.synopsis synopText = novIdx.synopsis
if synopText: if synopText:
@@ -814,9 +791,8 @@ class GuiNovelTree(QTreeWidget):
return return
@staticmethod @staticmethod
def _appendMetaTag(refs, key, lines): def _appendMetaTag(refs: dict, key: str, lines: list[str]) -> list[str]:
"""Generate a reference list for a given reference key. """Generate a reference list for a given reference key."""
"""
tags = ", ".join(refs.get(key, [])) tags = ", ".join(refs.get(key, []))
if tags: if tags:
lines.append(f"<b>{trConst(nwLabels.KEY_NAME[key])}</b>: {tags}") lines.append(f"<b>{trConst(nwLabels.KEY_NAME[key])}</b>: {tags}")
+1
View File
@@ -261,6 +261,7 @@ class GuiOutlineToolBar(QToolBar):
self.novelValue.updateList(includeAll=True) self.novelValue.updateList(includeAll=True)
self.aRefresh.setIcon(SHARED.theme.getIcon("refresh")) self.aRefresh.setIcon(SHARED.theme.getIcon("refresh"))
self.tbColumns.setIcon(SHARED.theme.getIcon("menu")) self.tbColumns.setIcon(SHARED.theme.getIcon("menu"))
self.tbColumns.setStyleSheet("QToolButton::menu-indicator {image: none;}")
return return
def populateNovelList(self) -> None: def populateNovelList(self) -> None:
+4 -3
View File
@@ -361,12 +361,13 @@ class GuiProjectToolBar(QWidget):
"QToolButton {{padding: {0}px; border: none; background: transparent;}} " "QToolButton {{padding: {0}px; border: none; background: transparent;}} "
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}" "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue()) ).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
self.tbQuick.setStyleSheet(buttonStyle) self.tbQuick.setStyleSheet(buttonStyleMenu)
self.tbMoveU.setStyleSheet(buttonStyle) self.tbMoveU.setStyleSheet(buttonStyle)
self.tbMoveD.setStyleSheet(buttonStyle) self.tbMoveD.setStyleSheet(buttonStyle)
self.tbAdd.setStyleSheet(buttonStyle) self.tbAdd.setStyleSheet(buttonStyleMenu)
self.tbMore.setStyleSheet(buttonStyle) self.tbMore.setStyleSheet(buttonStyleMenu)
self.tbQuick.setIcon(SHARED.theme.getIcon("bookmark")) self.tbQuick.setIcon(SHARED.theme.getIcon("bookmark"))
self.tbMoveU.setIcon(SHARED.theme.getIcon("up")) self.tbMoveU.setIcon(SHARED.theme.getIcon("up"))
-1
View File
@@ -115,7 +115,6 @@ class GuiSideBar(QWidget):
self.outerBox.setSpacing(CONFIG.pxInt(4)) self.outerBox.setSpacing(CONFIG.pxInt(4))
self.setLayout(self.outerBox) self.setLayout(self.outerBox)
self.updateTheme() self.updateTheme()
logger.debug("Ready: GuiSideBar") logger.debug("Ready: GuiSideBar")
+91 -73
View File
@@ -36,7 +36,7 @@ from PyQt5.QtGui import (
) )
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.enum import nwItemLayout, nwItemType from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.common import NWConfigParser, minmax from novelwriter.common import NWConfigParser, minmax
from novelwriter.constants import nwLabels from novelwriter.constants import nwLabels
@@ -51,7 +51,7 @@ logger = logging.getLogger(__name__)
class GuiTheme: class GuiTheme:
def __init__(self): def __init__(self) -> None:
self.iconCache = GuiIcons(self) self.iconCache = GuiIcons(self)
@@ -130,6 +130,7 @@ class GuiTheme:
self.getIcon = self.iconCache.getIcon self.getIcon = self.iconCache.getIcon
self.getPixmap = self.iconCache.getPixmap self.getPixmap = self.iconCache.getPixmap
self.getItemIcon = self.iconCache.getItemIcon self.getItemIcon = self.iconCache.getItemIcon
self.getToggleIcon = self.iconCache.getToggleIcon
self.loadDecoration = self.iconCache.loadDecoration self.loadDecoration = self.iconCache.loadDecoration
self.getHeaderDecoration = self.iconCache.getHeaderDecoration self.getHeaderDecoration = self.iconCache.getHeaderDecoration
@@ -182,7 +183,7 @@ class GuiTheme:
# Theme Methods # Theme Methods
## ##
def loadTheme(self): def loadTheme(self) -> bool:
"""Load the currently specified GUI theme.""" """Load the currently specified GUI theme."""
guiTheme = CONFIG.guiTheme guiTheme = CONFIG.guiTheme
if guiTheme not in self._availThemes: if guiTheme not in self._availThemes:
@@ -269,7 +270,7 @@ class GuiTheme:
return True return True
def loadSyntax(self): def loadSyntax(self) -> bool:
"""Load the currently specified syntax highlighter theme.""" """Load the currently specified syntax highlighter theme."""
guiSyntax = CONFIG.guiSyntax guiSyntax = CONFIG.guiSyntax
if guiSyntax not in self._availSyntax: if guiSyntax not in self._availSyntax:
@@ -363,7 +364,7 @@ class GuiTheme:
# Internal Functions # Internal Functions
## ##
def _setGuiFont(self): def _setGuiFont(self) -> None:
"""Update the GUI's font style from settings.""" """Update the GUI's font style from settings."""
theFont = QFont() theFont = QFont()
fontDB = QFontDatabase() fontDB = QFontDatabase()
@@ -395,9 +396,7 @@ class GuiTheme:
return True return True
def _parseColour( def _parseColour(self, parser: NWConfigParser, section: str, name: str) -> list[int]:
self, parser: NWConfigParser, section: str, name: str
) -> list[int]:
"""Parse a colour value from a config string.""" """Parse a colour value from a config string."""
if parser.has_option(section, name): if parser.has_option(section, name):
values = parser.get(section, name).split(",") values = parser.get(section, name).split(",")
@@ -414,9 +413,8 @@ class GuiTheme:
result = [0, 0, 0] result = [0, 0, 0]
return result return result
def _setPalette( def _setPalette(self, parser: NWConfigParser, section: str,
self, parser: NWConfigParser, section: str, name: str, value: QPalette.ColorRole name: str, value: QPalette.ColorRole) -> None:
):
"""Set a palette colour value from a config string.""" """Set a palette colour value from a config string."""
self._guiPalette.setColor( self._guiPalette.setColor(
value, QColor(*self._parseColour(parser, section, name)) value, QColor(*self._parseColour(parser, section, name))
@@ -447,14 +445,22 @@ class GuiIcons:
ICON_KEYS = { ICON_KEYS = {
# Project and GUI Icons # Project and GUI Icons
"novelwriter", "alert_error", "alert_info", "alert_question", "alert_warn", "novelwriter", "alert_error", "alert_info", "alert_question", "alert_warn",
"build_excluded", "build_filtered", "build_included", "cls_archive", "cls_character", "build_excluded", "build_filtered", "build_included", "proj_chapter", "proj_details",
"cls_custom", "cls_entity", "cls_none", "cls_novel", "cls_object", "cls_plot", "proj_document", "proj_folder", "proj_note", "proj_nwx", "proj_section", "proj_scene",
"cls_timeline", "cls_trash", "cls_world", "proj_chapter", "proj_details", "proj_document", "proj_stats", "proj_title", "status_idle", "status_lang", "status_lines", "status_stats",
"proj_folder", "proj_note", "proj_nwx", "proj_section", "proj_scene", "proj_stats", "status_time", "view_build", "view_editor", "view_novel", "view_outline",
"proj_title", "search_cancel", "search_case", "search_loop", "search_preserve",
"search_project", "search_regex", "search_word", "status_idle", "status_lang", # Class Icons
"status_lines", "status_stats", "status_time", "view_build", "view_editor", "view_novel", "cls_archive", "cls_character", "cls_custom", "cls_entity", "cls_none", "cls_novel",
"view_outline", "cls_object", "cls_plot", "cls_timeline", "cls_trash", "cls_world",
# Search Icons
"search_cancel", "search_case", "search_loop", "search_preserve", "search_project",
"search_regex", "search_word",
# Format Icons
"fmt_bold", "fmt_italic", "fmt_mode-md", "fmt_mode-sc", "fmt_strike", "fmt_subscript",
"fmt_superscript", "fmt_underline",
# General Button Icons # General Button Icons
"add", "backward", "bookmark", "browse", "checked", "close", "cross", "down", "edit", "add", "backward", "bookmark", "browse", "checked", "close", "cross", "down", "edit",
@@ -469,21 +475,27 @@ class GuiIcons:
"deco_doc_h0", "deco_doc_h1", "deco_doc_h2", "deco_doc_h3", "deco_doc_h4", "deco_doc_more", "deco_doc_h0", "deco_doc_h1", "deco_doc_h2", "deco_doc_h3", "deco_doc_h4", "deco_doc_more",
} }
TOGGLE_ICON_KEYS = {
"sticky": ("sticky-on", "sticky-off"),
"bullet": ("bullet-on", "bullet-off"),
"fmt_mode": ("fmt_mode-sc", "fmt_mode-md"),
}
IMAGE_MAP = { IMAGE_MAP = {
"wiz-back": "wizard-back.jpg", "wiz-back": "wizard-back.jpg",
} }
def __init__(self, mainTheme): def __init__(self, mainTheme: GuiTheme) -> None:
self.mainTheme = mainTheme self.mainTheme = mainTheme
# Storage # Storage
self._qIcons = {} self._qIcons: dict[str, QIcon] = {}
self._themeMap = {} self._themeMap: dict[str, Path] = {}
self._headerDec = [] self._headerDec: list[QPixmap] = []
self._confName = "icons.conf"
# Icon Theme Path # Icon Theme Path
self._confName = "icons.conf"
self._iconPath = CONFIG.assetPath("icons") self._iconPath = CONFIG.assetPath("icons")
# Icon Theme Meta # Icon Theme Meta
@@ -501,7 +513,7 @@ class GuiIcons:
# Actions # Actions
## ##
def loadTheme(self, iconTheme): def loadTheme(self, iconTheme: str) -> bool:
"""Update the theme map. This is more of an init, since many of """Update the theme map. This is more of an init, since many of
the GUI icons cannot really be replaced without writing specific the GUI icons cannot really be replaced without writing specific
update functions for the classes where they're used. update functions for the classes where they're used.
@@ -575,52 +587,60 @@ class GuiIcons:
# Access Functions # Access Functions
## ##
def loadDecoration(self, decoKey, pxW=None, pxH=None): def loadDecoration(self, name: str, w: int | None = None, h: int | None = None) -> QPixmap:
"""Load graphical decoration element based on the decoration """Load graphical decoration element based on the decoration
map or the icon map. This function always returns a QPixmap. map or the icon map. This function always returns a QPixmap.
""" """
if decoKey in self._themeMap: if name in self._themeMap:
imgPath = self._themeMap[decoKey] imgPath = self._themeMap[name]
elif decoKey in self.IMAGE_MAP: elif name in self.IMAGE_MAP:
imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[decoKey] imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name]
else: else:
logger.error("Decoration with name '%s' does not exist", decoKey) logger.error("Decoration with name '%s' does not exist", name)
return QPixmap() return QPixmap()
if not imgPath.is_file(): if not imgPath.is_file():
logger.error("Asset not found: %s", imgPath) logger.error("Asset not found: %s", imgPath)
return QPixmap() return QPixmap()
theDeco = QPixmap(str(imgPath)) pixmap = QPixmap(str(imgPath))
if pxW is not None and pxH is not None: if w is not None and h is not None:
return theDeco.scaled(pxW, pxH, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) return pixmap.scaled(w, h, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
elif pxW is None and pxH is not None: elif w is None and h is not None:
return theDeco.scaledToHeight(pxH, Qt.SmoothTransformation) return pixmap.scaledToHeight(h, Qt.SmoothTransformation)
elif pxW is not None and pxH is None: elif w is not None and h is None:
return theDeco.scaledToWidth(pxW, Qt.SmoothTransformation) return pixmap.scaledToWidth(w, Qt.SmoothTransformation)
return theDeco return pixmap
def getIcon(self, iconKey): def getIcon(self, name: str) -> QIcon:
"""Return an icon from the icon buffer. If it doesn't exist, """Return an icon from the icon buffer, or load it."""
return, load it, and if it still doesn't exist, return an empty if name in self._qIcons:
icon. return self._qIcons[name]
"""
if iconKey in self._qIcons:
return self._qIcons[iconKey]
else: else:
qIcon = self._loadIcon(iconKey) icon = self._loadIcon(name)
self._qIcons[iconKey] = qIcon self._qIcons[name] = icon
return qIcon return icon
def getPixmap(self, iconKey, iconSize): def getToggleIcon(self, name: str, size: tuple[int, int]) -> QIcon:
"""Return a toggle icon from the icon buffer. or load it."""
if name in self.TOGGLE_ICON_KEYS:
pOne = self.getPixmap(self.TOGGLE_ICON_KEYS[name][0], size)
pTwo = self.getPixmap(self.TOGGLE_ICON_KEYS[name][1], size)
icon = QIcon()
icon.addPixmap(pOne, QIcon.Normal, QIcon.On)
icon.addPixmap(pTwo, QIcon.Normal, QIcon.Off)
return icon
return QIcon()
def getPixmap(self, name: str, size: tuple[int, int]) -> QPixmap:
"""Return an icon from the icon buffer as a QPixmap. If it """Return an icon from the icon buffer as a QPixmap. If it
doesn't exist, return an empty QPixmap. doesn't exist, return an empty QPixmap.
""" """
qIcon = self.getIcon(iconKey) return self.getIcon(name).pixmap(size[0], size[1], QIcon.Normal)
return qIcon.pixmap(iconSize[0], iconSize[1], QIcon.Normal)
def getItemIcon(self, tType, tClass, tLayout, hLevel="H0"): def getItemIcon(self, tType: nwItemType, tClass: nwItemClass,
tLayout: nwItemLayout, hLevel: str = "H0") -> QIcon:
"""Get the correct icon for a project item based on type, class """Get the correct icon for a project item based on type, class
and header level and header level
""" """
@@ -647,17 +667,16 @@ class GuiIcons:
return self.getIcon(iconName) return self.getIcon(iconName)
def getHeaderDecoration(self, hLevel): def getHeaderDecoration(self, hLevel: int) -> QPixmap:
"""Get the decoration for a specific header level. """Get the decoration for a specific header level."""
"""
if not self._headerDec: if not self._headerDec:
iPx = self.mainTheme.baseIconSize iPx = self.mainTheme.baseIconSize
self._headerDec = [ self._headerDec = [
self.loadDecoration("deco_doc_h0", pxH=iPx), self.loadDecoration("deco_doc_h0", h=iPx),
self.loadDecoration("deco_doc_h1", pxH=iPx), self.loadDecoration("deco_doc_h1", h=iPx),
self.loadDecoration("deco_doc_h2", pxH=iPx), self.loadDecoration("deco_doc_h2", h=iPx),
self.loadDecoration("deco_doc_h3", pxH=iPx), self.loadDecoration("deco_doc_h3", h=iPx),
self.loadDecoration("deco_doc_h4", pxH=iPx), self.loadDecoration("deco_doc_h4", h=iPx),
] ]
return self._headerDec[minmax(hLevel, 0, 4)] return self._headerDec[minmax(hLevel, 0, 4)]
@@ -665,27 +684,27 @@ class GuiIcons:
# Internal Functions # Internal Functions
## ##
def _loadIcon(self, iconKey): def _loadIcon(self, name: str) -> QIcon:
"""Load an icon from the assets themes folder. Is guaranteed to """Load an icon from the assets themes folder. Is guaranteed to
return a QIcon. return a QIcon.
""" """
if iconKey not in self.ICON_KEYS: if name not in self.ICON_KEYS:
logger.error("Requested unknown icon name '%s'", iconKey) logger.error("Requested unknown icon name '%s'", name)
return QIcon() return QIcon()
# If we just want the app icons, return right away # If we just want the app icons, return right away
if iconKey == "novelwriter": if name == "novelwriter":
return QIcon(str(self._iconPath / "novelwriter.svg")) return QIcon(str(self._iconPath / "novelwriter.svg"))
elif iconKey == "proj_nwx": elif name == "proj_nwx":
return QIcon(str(self._iconPath / "x-novelwriter-project.svg")) return QIcon(str(self._iconPath / "x-novelwriter-project.svg"))
# Otherwise, we load from the theme folder # Otherwise, we load from the theme folder
if iconKey in self._themeMap: if name in self._themeMap:
logger.debug("Loading: %s", self._themeMap[iconKey].name) logger.debug("Loading: %s", self._themeMap[name].name)
return QIcon(str(self._themeMap[iconKey])) return QIcon(str(self._themeMap[name]))
# If we didn't find one, give up and return an empty icon # If we didn't find one, give up and return an empty icon
logger.warning("Did not load an icon for '%s'", iconKey) logger.warning("Did not load an icon for '%s'", name)
return QIcon() return QIcon()
@@ -696,9 +715,8 @@ class GuiIcons:
# Module Functions # Module Functions
# =============================================================================================== # # =============================================================================================== #
def _loadInternalName(confParser, confFile): def _loadInternalName(confParser: NWConfigParser, confFile: str | Path) -> str:
"""Open a conf file and read the 'name' setting. """Open a conf file and read the 'name' setting."""
"""
try: try:
with open(confFile, mode="r", encoding="utf-8") as inFile: with open(confFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
+199 -176
View File
@@ -62,7 +62,7 @@ from novelwriter.tools.writingstats import GuiWritingStats
from novelwriter.core.coretools import ProjectBuilder from novelwriter.core.coretools import ProjectBuilder
from novelwriter.enum import ( from novelwriter.enum import (
nwDocAction, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView
) )
from novelwriter.common import getGuiItem, hexToInt from novelwriter.common import getGuiItem, hexToInt
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
@@ -243,6 +243,11 @@ class GuiMain(QMainWindow):
SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage) SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage) SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
self.mainMenu.requestDocAction.connect(self._passDocumentAction)
self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert)
self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord)
self.sideBar.viewChangeRequested.connect(self._changeView) self.sideBar.viewChangeRequested.connect(self._changeView)
self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox) self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
@@ -267,6 +272,8 @@ class GuiMain(QMainWindow):
self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta) self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta)
self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage) self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage)
self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState) self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState)
self.docEditor.closeDocumentRequest.connect(self.closeDocEditor)
self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
self.docViewer.loadDocumentTagRequest.connect(self._followTag) self.docViewer.loadDocumentTagRequest.connect(self._followTag)
@@ -733,20 +740,6 @@ class GuiMain(QMainWindow):
return True return True
def passDocumentAction(self, action: nwDocAction) -> None:
"""Pass on document action to the document viewer if it has
focus, or pass it to the document editor if it or any of
its child widgets have focus. If neither has focus, ignore the
action.
"""
if self.docViewer.hasFocus():
self.docViewer.docAction(action)
elif self.docEditor.hasFocus():
self.docEditor.docAction(action)
else:
logger.debug("Action cancelled as neither editor nor viewer has focus")
return
## ##
# Tree Item Actions # Tree Item Actions
## ##
@@ -1119,12 +1112,6 @@ class GuiMain(QMainWindow):
self.outlineView.setTreeFocus() self.outlineView.setTreeFocus()
return return
def closeDocEditor(self) -> None:
"""Close the document editor. This does not hide the editor."""
self.closeDocument()
SHARED.project.data.setLastHandle(None, "editor")
return
def closeDocViewer(self, byUser: bool = True) -> bool: def closeDocViewer(self, byUser: bool = True) -> bool:
"""Close the document view panel.""" """Close the document view panel."""
self.docViewer.clearViewer() self.docViewer.clearViewer()
@@ -1139,13 +1126,44 @@ class GuiMain(QMainWindow):
return not self.splitView.isVisible() return not self.splitView.isVisible()
def toggleFocusMode(self) -> bool: def toggleFullScreenMode(self) -> None:
"""Toggle full screen mode"""
self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
return
##
# Events
##
def closeEvent(self, event: QCloseEvent):
"""Capture the closing event of the GUI and call the close
function to handle all the close process steps.
"""
if self.closeMain():
event.accept()
else:
event.ignore()
return
##
# Public Slots
##
@pyqtSlot()
def closeDocEditor(self) -> None:
"""Close the document editor. This does not hide the editor."""
self.closeDocument()
SHARED.project.data.setLastHandle(None, "editor")
return
@pyqtSlot()
def toggleFocusMode(self) -> None:
"""Handle toggle focus mode. The Main GUI Focus Mode hides tree, """Handle toggle focus mode. The Main GUI Focus Mode hides tree,
view, statusbar and menu. view, statusbar and menu.
""" """
if self.docEditor.docHandle is None: if self.docEditor.docHandle is None:
logger.error("No document open, so not activating Focus Mode") logger.error("No document open, so not activating Focus Mode")
return False return
self.isFocusMode = not self.isFocusMode self.isFocusMode = not self.isFocusMode
if self.isFocusMode: if self.isFocusMode:
@@ -1169,11 +1187,165 @@ class GuiMain(QMainWindow):
elif self.docViewer.docHandle is not None: elif self.docViewer.docHandle is not None:
self.splitView.setVisible(True) self.splitView.setVisible(True)
return True return
def toggleFullScreenMode(self) -> None: ##
"""Toggle full screen mode""" # Private Slots
self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) ##
@pyqtSlot(str, nwDocMode)
def _followTag(self, tag: str, mode: nwDocMode) -> None:
"""Follow a tag after user interaction with a link."""
tHandle, sTitle = self._getTagSource(tag)
if tHandle is not None:
if mode == nwDocMode.EDIT:
self.openDocument(tHandle)
elif mode == nwDocMode.VIEW:
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
return
@pyqtSlot(str, nwDocMode, str, bool)
def _openDocument(self, tHandle: str, mode: nwDocMode, sTitle: str, setFocus: bool) -> None:
"""Handle an open document request."""
if tHandle is not None:
if mode == nwDocMode.EDIT:
tLine = None
hItem = SHARED.project.index.getItemHeader(tHandle, sTitle)
if hItem is not None:
tLine = hItem.line
self.openDocument(tHandle, tLine=tLine, changeFocus=setFocus)
elif mode == nwDocMode.VIEW:
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
return
@pyqtSlot(nwView)
def _changeView(self, view: nwView) -> None:
"""Handle the requested change of view from the GuiViewBar."""
if view == nwView.EDITOR:
# Only change the main stack, but not the project stack
self.mainStack.setCurrentWidget(self.splitMain)
elif view == nwView.PROJECT:
self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.projView)
elif view == nwView.NOVEL:
self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.novelView)
elif view == nwView.OUTLINE:
self.mainStack.setCurrentWidget(self.outlineView)
return
@pyqtSlot(nwDocAction)
def _passDocumentAction(self, action: nwDocAction) -> None:
"""Pass on a document action to the document viewer if it has
focus, or pass it to the document editor if it or any of its
child widgets have focus. If neither has focus, ignore it.
"""
if self.docViewer.hasFocus():
self.docViewer.docAction(action)
elif self.docEditor.hasFocus():
self.docEditor.docAction(action)
else:
logger.debug("Action cancelled as neither editor nor viewer has focus")
return
@pyqtSlot(str)
@pyqtSlot(nwDocInsert)
def _passDocumentInsert(self, content: str | nwDocInsert) -> None:
"""Pass on a document insert action to the document editor if it
has focus. If not, ignore it.
"""
if self.docEditor.hasFocus():
self.docEditor.insertText(content)
return
@pyqtSlot()
def _timeTick(self) -> None:
"""Process time tick of the main timer."""
if not SHARED.hasProject:
return
currTime = time()
editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime
userIdle = qApp.applicationState() != Qt.ApplicationActive
self.mainStatus.setUserIdle(editIdle or userIdle)
SHARED.updateIdleTime(currTime, editIdle or userIdle)
self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
return
@pyqtSlot()
def _autoSaveProject(self) -> None:
"""Autosave of the project. This is a timer-activated slot."""
doSave = SHARED.hasProject
doSave &= SHARED.project.projChanged
doSave &= SHARED.project.storage.isOpen()
if doSave:
logger.debug("Autosaving project")
self.saveProject(autoSave=True)
return
@pyqtSlot()
def _autoSaveDocument(self) -> None:
"""Autosave of the document. This is a timer-activated slot."""
if SHARED.hasProject and self.docEditor.docChanged:
logger.debug("Autosaving document")
self.saveDocument()
return
@pyqtSlot()
def _updateStatusWordCount(self) -> None:
"""Update the word count on the status bar."""
if not SHARED.hasProject:
self.mainStatus.setProjectStats(0, 0)
SHARED.project.updateWordCounts()
if CONFIG.incNotesWCount:
iTotal = sum(SHARED.project.data.initCounts)
cTotal = sum(SHARED.project.data.currCounts)
self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
else:
iNovel, _ = SHARED.project.data.initCounts
cNovel, _ = SHARED.project.data.currCounts
self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
return
@pyqtSlot()
def _keyPressReturn(self) -> None:
"""Forward the return/enter keypress to the function that opens
the currently selected item.
"""
self.openSelectedItem()
return
@pyqtSlot()
def _keyPressEscape(self) -> None:
"""Process escape keypress in the main window."""
if self.docEditor.docSearch.isVisible():
self.docEditor.closeSearch()
elif self.isFocusMode:
self.toggleFocusMode()
return
@pyqtSlot(int)
def _mainStackChanged(self, index: int) -> None:
"""Process main window tab change."""
if index == self.idxOutlineView:
if SHARED.hasProject:
self.outlineView.refreshTree()
return
@pyqtSlot(int)
def _projStackChanged(self, index: int) -> None:
"""Process project view tab change."""
sHandle = None
if index == self.idxProjView:
sHandle = self.projView.getSelectedHandle()
elif index == self.idxNovelView:
sHandle, _ = self.novelView.getSelectedHandle()
self.itemDetails.updateViewBox(sHandle)
return return
## ##
@@ -1328,153 +1500,4 @@ class GuiMain(QMainWindow):
return None, None return None, None
return tHandle, sTitle return tHandle, sTitle
##
# Events
##
def closeEvent(self, event: QCloseEvent):
"""Capture the closing event of the GUI and call the close
function to handle all the close process steps.
"""
if self.closeMain():
event.accept()
else:
event.ignore()
return
##
# Private Slots
##
@pyqtSlot(str, nwDocMode)
def _followTag(self, tag: str, mode: nwDocMode) -> None:
"""Follow a tag after user interaction with a link."""
tHandle, sTitle = self._getTagSource(tag)
if tHandle is not None:
if mode == nwDocMode.EDIT:
self.openDocument(tHandle)
elif mode == nwDocMode.VIEW:
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
return
@pyqtSlot(str, nwDocMode, str, bool)
def _openDocument(self, tHandle: str, mode: nwDocMode, sTitle: str, setFocus: bool) -> None:
"""Handle an open document request."""
if tHandle is not None:
if mode == nwDocMode.EDIT:
tLine = None
hItem = SHARED.project.index.getItemHeader(tHandle, sTitle)
if hItem is not None:
tLine = hItem.line
self.openDocument(tHandle, tLine=tLine, changeFocus=setFocus)
elif mode == nwDocMode.VIEW:
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
return
@pyqtSlot(nwView)
def _changeView(self, view: nwView) -> None:
"""Handle the requested change of view from the GuiViewBar."""
if view == nwView.EDITOR:
# Only change the main stack, but not the project stack
self.mainStack.setCurrentWidget(self.splitMain)
elif view == nwView.PROJECT:
self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.projView)
elif view == nwView.NOVEL:
self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.novelView)
elif view == nwView.OUTLINE:
self.mainStack.setCurrentWidget(self.outlineView)
return
@pyqtSlot()
def _timeTick(self) -> None:
"""Process time tick of the main timer."""
if not SHARED.hasProject:
return
currTime = time()
editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime
userIdle = qApp.applicationState() != Qt.ApplicationActive
self.mainStatus.setUserIdle(editIdle or userIdle)
SHARED.updateIdleTime(currTime, editIdle or userIdle)
self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
return
@pyqtSlot()
def _autoSaveProject(self) -> None:
"""Autosave of the project. This is a timer-activated slot."""
doSave = SHARED.hasProject
doSave &= SHARED.project.projChanged
doSave &= SHARED.project.storage.isOpen()
if doSave:
logger.debug("Autosaving project")
self.saveProject(autoSave=True)
return
@pyqtSlot()
def _autoSaveDocument(self) -> None:
"""Autosave of the document. This is a timer-activated slot."""
if SHARED.hasProject and self.docEditor.docChanged:
logger.debug("Autosaving document")
self.saveDocument()
return
@pyqtSlot()
def _updateStatusWordCount(self) -> None:
"""Update the word count on the status bar."""
if not SHARED.hasProject:
self.mainStatus.setProjectStats(0, 0)
SHARED.project.updateWordCounts()
if CONFIG.incNotesWCount:
iTotal = sum(SHARED.project.data.initCounts)
cTotal = sum(SHARED.project.data.currCounts)
self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
else:
iNovel, _ = SHARED.project.data.initCounts
cNovel, _ = SHARED.project.data.currCounts
self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
return
@pyqtSlot()
def _keyPressReturn(self) -> None:
"""Forward the return/enter keypress to the function that opens
the currently selected item.
"""
self.openSelectedItem()
return
@pyqtSlot()
def _keyPressEscape(self) -> None:
"""Process escape keypress in the main window."""
if self.docEditor.docSearch.isVisible():
self.docEditor.closeSearch()
elif self.isFocusMode:
self.toggleFocusMode()
return
@pyqtSlot(int)
def _mainStackChanged(self, index: int) -> None:
"""Process main window tab change."""
if index == self.idxOutlineView:
if SHARED.hasProject:
self.outlineView.refreshTree()
return
@pyqtSlot(int)
def _projStackChanged(self, index: int) -> None:
"""Process project view tab change."""
sHandle = None
if index == self.idxProjView:
sHandle = self.projView.getSelectedHandle()
elif index == self.idxNovelView:
sHandle, _ = self.novelView.getSelectedHandle()
self.itemDetails.updateViewBox(sHandle)
return
# END Class GuiMain # END Class GuiMain
@@ -68,6 +68,8 @@ useridletime = 300
[State] [State]
showrefpanel = True showrefpanel = True
showedittoolbar = False
useshortcodes = False
viewcomments = True viewcomments = True
viewsynopsis = True viewsynopsis = True
searchcase = False searchcase = False
@@ -68,6 +68,8 @@ useridletime = 300
[State] [State]
showrefpanel = True showrefpanel = True
showedittoolbar = False
useshortcodes = False
viewcomments = True viewcomments = True
viewsynopsis = True viewsynopsis = True
searchcase = False searchcase = False
+1 -1
View File
@@ -140,7 +140,7 @@ def testCoreOptions_SetGet(mockGUI):
assert theOpts.getFloat("GuiProjectDetails", "mockItem", None) is None assert theOpts.getFloat("GuiProjectDetails", "mockItem", None) is None
assert theOpts.getBool("GuiProjectDetails", "clearDouble", None) is True assert theOpts.getBool("GuiProjectDetails", "clearDouble", None) is True
assert theOpts.getBool("GuiProjectDetails", "mockItem", None) is None assert theOpts.getBool("GuiProjectDetails", "mockItem", None) is None
assert theOpts.getEnum("GuiNovelView", "lastCol", NovelTreeColumn, None) == nwColHidden assert theOpts.getEnum("GuiNovelView", "lastCol", NovelTreeColumn, nwColHidden) == nwColHidden
# Get from non-existent groups # Get from non-existent groups
assert theOpts.getValue("SomeGroup", "mockItem", None) is None assert theOpts.getValue("SomeGroup", "mockItem", None) is None
+278 -107
View File
@@ -24,15 +24,15 @@ import pytest
from tools import C, buildTestProject from tools import C, buildTestProject
from mocked import causeOSError from mocked import causeOSError
from PyQt5.QtCore import QThreadPool, Qt
from PyQt5.QtGui import QTextBlock, QTextCursor, QTextOption from PyQt5.QtGui import QTextBlock, QTextCursor, QTextOption
from PyQt5.QtCore import QThreadPool, Qt
from PyQt5.QtWidgets import QAction, qApp from PyQt5.QtWidgets import QAction, qApp
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemLayout, nwTrinary, nwWidget from novelwriter.enum import nwDocAction, nwDocInsert, nwItemLayout, nwTrinary, nwWidget
from novelwriter.constants import nwKeyWords, nwUnicode from novelwriter.constants import nwKeyWords, nwUnicode
from novelwriter.core.index import countWords from novelwriter.core.index import countWords
from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.doceditor import GuiDocEditor, GuiDocToolBar
KEY_DELAY = 1 KEY_DELAY = 1
@@ -214,9 +214,8 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True assert nwGUI.openDocument(C.hSceneDoc) is True
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText) text = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
theDoc = nwGUI.docEditor.document() theDoc = nwGUI.docEditor.document()
# Select/Cut/Copy/Paste/Undo/Redo # Select/Cut/Copy/Paste/Undo/Redo
@@ -228,7 +227,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) is True assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) is True
theCursor = nwGUI.docEditor.textCursor() theCursor = nwGUI.docEditor.textCursor()
assert theCursor.hasSelection() is True assert theCursor.hasSelection() is True
assert theCursor.selectedText() == theText.replace("\n", "\u2029") assert theCursor.selectedText() == text.replace("\n", "\u2029")
theCursor.clearSelection() theCursor.clearSelection()
# Select Paragraph # Select Paragraph
@@ -239,7 +238,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert theCursor.selectedText() == ipsumText[1] assert theCursor.selectedText() == ipsumText[1]
# Cut Selected Text # Cut Selected Text
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(1000) nwGUI.docEditor.setCursorPosition(1000)
assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA) is True assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA) is True
assert nwGUI.docEditor.docAction(nwDocAction.CUT) is True assert nwGUI.docEditor.docAction(nwDocAction.CUT) is True
@@ -254,10 +253,10 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Paste Back In # Paste Back In
assert nwGUI.docEditor.docAction(nwDocAction.PASTE) is True assert nwGUI.docEditor.docAction(nwDocAction.PASTE) is True
assert nwGUI.docEditor.getText() == theText assert nwGUI.docEditor.getText() == text
# Copy Next Paragraph # Copy Next Paragraph
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(1500) nwGUI.docEditor.setCursorPosition(1500)
assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA) is True assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA) is True
assert nwGUI.docEditor.docAction(nwDocAction.COPY) is True assert nwGUI.docEditor.docAction(nwDocAction.COPY) is True
@@ -279,84 +278,132 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# Emphasis/Undo/Redo # Emphasis/Undo/Redo
# ================== # ==================
theText = "### A Scene\n\n%s" % ipsumText[0] text = "### A Scene\n\n%s" % ipsumText[0]
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
# Emphasis # Emphasis
nwGUI.docEditor.setCursorPosition(50) nwGUI.docEditor.setCursorPosition(50)
assert nwGUI.docEditor.docAction(nwDocAction.EMPH) is True assert nwGUI.docEditor.docAction(nwDocAction.EMPH) is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "_consectetur_") assert nwGUI.docEditor.getText() == text.replace("consectetur", "_consectetur_")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == theText assert nwGUI.docEditor.getText() == text
# Strong # Strong
nwGUI.docEditor.setCursorPosition(50) nwGUI.docEditor.setCursorPosition(50)
assert nwGUI.docEditor.docAction(nwDocAction.STRONG) is True assert nwGUI.docEditor.docAction(nwDocAction.STRONG) is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "**consectetur**") assert nwGUI.docEditor.getText() == text.replace("consectetur", "**consectetur**")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == theText assert nwGUI.docEditor.getText() == text
# Strikeout # Strikeout
nwGUI.docEditor.setCursorPosition(50) nwGUI.docEditor.setCursorPosition(50)
assert nwGUI.docEditor.docAction(nwDocAction.STRIKE) is True assert nwGUI.docEditor.docAction(nwDocAction.STRIKE) is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "~~consectetur~~") assert nwGUI.docEditor.getText() == text.replace("consectetur", "~~consectetur~~")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == theText assert nwGUI.docEditor.getText() == text
# Redo # Redo
assert nwGUI.docEditor.docAction(nwDocAction.REDO) is True assert nwGUI.docEditor.docAction(nwDocAction.REDO) is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "~~consectetur~~") assert nwGUI.docEditor.getText() == text.replace("consectetur", "~~consectetur~~")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == theText assert nwGUI.docEditor.getText() == text
# Shortcodes
# ==========
text = "### A Scene\n\n%s" % ipsumText[0]
nwGUI.docEditor.replaceText(text)
# Italic
nwGUI.docEditor.setCursorPosition(46)
assert nwGUI.docEditor.docAction(nwDocAction.SC_ITALIC) is True
assert nwGUI.docEditor.getText() == text.replace("consectetur", "[i]consectetur[/i]")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == text
# Bold
nwGUI.docEditor.setCursorPosition(46)
assert nwGUI.docEditor.docAction(nwDocAction.SC_BOLD) is True
assert nwGUI.docEditor.getText() == text.replace("consectetur", "[b]consectetur[/b]")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == text
# Strikethrough
nwGUI.docEditor.setCursorPosition(46)
assert nwGUI.docEditor.docAction(nwDocAction.SC_STRIKE) is True
assert nwGUI.docEditor.getText() == text.replace("consectetur", "[s]consectetur[/s]")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == text
# Underline
nwGUI.docEditor.setCursorPosition(46)
assert nwGUI.docEditor.docAction(nwDocAction.SC_ULINE) is True
assert nwGUI.docEditor.getText() == text.replace("consectetur", "[u]consectetur[/u]")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == text
# Superscript
nwGUI.docEditor.setCursorPosition(46)
assert nwGUI.docEditor.docAction(nwDocAction.SC_SUP) is True
assert nwGUI.docEditor.getText() == text.replace("consectetur", "[sup]consectetur[/sup]")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == text
# Subscript
nwGUI.docEditor.setCursorPosition(46)
assert nwGUI.docEditor.docAction(nwDocAction.SC_SUB) is True
assert nwGUI.docEditor.getText() == text.replace("consectetur", "[sub]consectetur[/sub]")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == text
# Quotes # Quotes
# ====== # ======
theText = "### A Scene\n\n%s" % ipsumText[0] text = "### A Scene\n\n%s" % ipsumText[0]
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
# Add Single Quotes # Add Single Quotes
nwGUI.docEditor.setCursorPosition(50) nwGUI.docEditor.setCursorPosition(50)
assert nwGUI.docEditor.docAction(nwDocAction.S_QUOTE) is True assert nwGUI.docEditor.docAction(nwDocAction.S_QUOTE) is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u2018consectetur\u2019") assert nwGUI.docEditor.getText() == text.replace("consectetur", "\u2018consectetur\u2019")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == theText assert nwGUI.docEditor.getText() == text
# Add Double Quotes # Add Double Quotes
nwGUI.docEditor.setCursorPosition(50) nwGUI.docEditor.setCursorPosition(50)
assert nwGUI.docEditor.docAction(nwDocAction.D_QUOTE) is True assert nwGUI.docEditor.docAction(nwDocAction.D_QUOTE) is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u201cconsectetur\u201d") assert nwGUI.docEditor.getText() == text.replace("consectetur", "\u201cconsectetur\u201d")
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True assert nwGUI.docEditor.docAction(nwDocAction.UNDO) is True
assert nwGUI.docEditor.getText() == theText assert nwGUI.docEditor.getText() == text
# Replace Single Quotes # Replace Single Quotes
repText = theText.replace("consectetur", "'consectetur'") repText = text.replace("consectetur", "'consectetur'")
nwGUI.docEditor.replaceText(repText) nwGUI.docEditor.replaceText(repText)
assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) is True assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) is True
assert nwGUI.docEditor.docAction(nwDocAction.REPL_SNG) is True assert nwGUI.docEditor.docAction(nwDocAction.REPL_SNG) is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u2018consectetur\u2019") assert nwGUI.docEditor.getText() == text.replace("consectetur", "\u2018consectetur\u2019")
# Replace Double Quotes # Replace Double Quotes
repText = theText.replace("consectetur", "\"consectetur\"") repText = text.replace("consectetur", "\"consectetur\"")
nwGUI.docEditor.replaceText(repText) nwGUI.docEditor.replaceText(repText)
assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) is True assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) is True
assert nwGUI.docEditor.docAction(nwDocAction.REPL_DBL) is True assert nwGUI.docEditor.docAction(nwDocAction.REPL_DBL) is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u201cconsectetur\u201d") assert nwGUI.docEditor.getText() == text.replace("consectetur", "\u201cconsectetur\u201d")
# Remove Line Breaks # Remove Line Breaks
# ================== # ==================
theText = "### A Scene\n\n%s" % ipsumText[0] text = "### A Scene\n\n%s" % ipsumText[0]
repText = theText[:100] + theText[100:].replace(" ", "\n", 3) repText = text[:100] + text[100:].replace(" ", "\n", 3)
nwGUI.docEditor.replaceText(repText) nwGUI.docEditor.replaceText(repText)
assert nwGUI.docEditor.docAction(nwDocAction.RM_BREAKS) is True assert nwGUI.docEditor.docAction(nwDocAction.RM_BREAKS) is True
assert nwGUI.docEditor.getText().strip() == theText.strip() assert nwGUI.docEditor.getText().strip() == text.strip()
# Format Block # Format Block
# ============ # ============
theText = "## Scene Title\n\nScene text.\n\n" text = "## Scene Title\n\nScene text.\n\n"
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
# Header 1 # Header 1
nwGUI.docEditor.setCursorPosition(0) nwGUI.docEditor.setCursorPosition(0)
@@ -437,20 +484,125 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
# END Test testGuiEditor_Actions # END Test testGuiEditor_Actions
@pytest.mark.gui
def testGuiEditor_ToolBar(qtbot, nwGUI, projPath, mockRnd):
"""Test the document actions. This is not an extensive test of the
action features, just that the actions are actually called. The
various action features are tested when their respective functions
are tested.
"""
buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True
docEditor: GuiDocEditor = nwGUI.docEditor
docToolBar: GuiDocToolBar = docEditor.docToolBar
text = (
"### A Scene\n\n"
"Text bold one\n\n"
"Text bold two\n\n"
"Text italic one\n\n"
"Text italic two\n\n"
"Text strikethrough one\n\n"
"Text strikethrough two\n\n"
"Text underline one\n\n"
"Text superscript one\n\n"
"Text subscript one\n\n"
)
length = len(text)
docEditor.replaceText(text)
assert len(docEditor.getText()) == length
# Show the ToolBar
assert docToolBar.isVisible() is False
docEditor._toggleToolBarVisibility()
assert docToolBar.isVisible() is True
# Markdown Mode
assert docToolBar.tbMode.isChecked() is False
# Click Bold
docEditor.setCursorPosition(20)
docToolBar.tbBold.click()
assert len(docEditor.getText()) == length + 4
# Click Italic
docEditor.setCursorPosition(54)
docToolBar.tbItalic.click()
assert len(docEditor.getText()) == length + 6
# Click Strikethrough
docEditor.setCursorPosition(90)
docToolBar.tbStrike.click()
assert len(docEditor.getText()) == length + 10
# Shortcode Mode
docToolBar.tbMode.click()
assert docToolBar.tbMode.isChecked() is True
# Click Bold
docEditor.setCursorPosition(39)
docToolBar.tbBold.click()
assert len(docEditor.getText()) == length + 17
# Click Italic
docEditor.setCursorPosition(80)
docToolBar.tbItalic.click()
assert len(docEditor.getText()) == length + 24
# Click Strikethrough
docEditor.setCursorPosition(132)
docToolBar.tbStrike.click()
assert len(docEditor.getText()) == length + 31
# Click Underline
docEditor.setCursorPosition(163)
docToolBar.tbUnderline.click()
assert len(docEditor.getText()) == length + 38
# Click Superscript
docEditor.setCursorPosition(190)
docToolBar.tbSuperscript.click()
assert len(docEditor.getText()) == length + 49
# Click Subscript
docEditor.setCursorPosition(223)
docToolBar.tbSubscript.click()
assert len(docEditor.getText()) == length + 60
# Check Result
assert docEditor.getText() == (
"### A Scene\n\n"
"Text **bold** one\n\n"
"Text [b]bold[/b] two\n\n"
"Text _italic_ one\n\n"
"Text [i]italic[/i] two\n\n"
"Text ~~strikethrough~~ one\n\n"
"Text [s]strikethrough[/s] two\n\n"
"Text [u]underline[/u] one\n\n"
"Text [sup]superscript[/sup] one\n\n"
"Text [sub]subscript[/sub] one\n\n"
)
# qtbot.stop()
# END Test testGuiEditor_ToolBar
@pytest.mark.gui @pytest.mark.gui
def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd): def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd):
"""Test the document insert functions.""" """Test the document insert functions."""
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True assert nwGUI.openDocument(C.hSceneDoc) is True
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText) text = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
# Insert Text # Insert Text
# =========== # ===========
theText = "### A Scene\n\n%s" % ipsumText[0] text = "### A Scene\n\n%s" % ipsumText[0]
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
# No Document Handle # No Document Handle
nwGUI.docEditor._docHandle = None nwGUI.docEditor._docHandle = None
@@ -461,23 +613,23 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
# Insert String # Insert String
nwGUI.docEditor.setCursorPosition(24) nwGUI.docEditor.setCursorPosition(24)
assert nwGUI.docEditor.insertText(", ipsumer,") is True assert nwGUI.docEditor.insertText(", ipsumer,") is True
assert nwGUI.docEditor.getText() == theText[:24] + ", ipsumer," + theText[24:] assert nwGUI.docEditor.getText() == text[:24] + ", ipsumer," + text[24:]
# Single Quotes # Single Quotes
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(41) nwGUI.docEditor.setCursorPosition(41)
assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_LS) is True assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_LS) is True
nwGUI.docEditor.setCursorPosition(53) nwGUI.docEditor.setCursorPosition(53)
assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_RS) is True assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_RS) is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u2018consectetur\u2019") assert nwGUI.docEditor.getText() == text.replace("consectetur", "\u2018consectetur\u2019")
# Double Quotes # Double Quotes
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(41) nwGUI.docEditor.setCursorPosition(41)
assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_LD) is True assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_LD) is True
nwGUI.docEditor.setCursorPosition(53) nwGUI.docEditor.setCursorPosition(53)
assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_RD) is True assert nwGUI.docEditor.insertText(nwDocInsert.QUOTE_RD) is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "\u201cconsectetur\u201d") assert nwGUI.docEditor.getText() == text.replace("consectetur", "\u201cconsectetur\u201d")
# Invalid Inserts # Invalid Inserts
assert nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT) is False assert nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT) is False
@@ -486,18 +638,18 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
# Insert KeyWords # Insert KeyWords
# =============== # ===============
theText = "### A Scene\n\n\n%s" % ipsumText[0] text = "### A Scene\n\n\n%s" % ipsumText[0]
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorLine(3) nwGUI.docEditor.setCursorLine(3)
# Invalid Keyword # Invalid Keyword
assert nwGUI.docEditor.insertKeyWord("stuff") is False assert nwGUI.docEditor.insertKeyWord("stuff") is False
assert nwGUI.docEditor.getText() == theText assert nwGUI.docEditor.getText() == text
# Valid Keyword # Valid Keyword
assert nwGUI.docEditor.insertKeyWord(nwKeyWords.POV_KEY) is True assert nwGUI.docEditor.insertKeyWord(nwKeyWords.POV_KEY) is True
assert nwGUI.docEditor.insertText("Jane\n") assert nwGUI.docEditor.insertText("Jane\n")
assert nwGUI.docEditor.getText() == theText.replace( assert nwGUI.docEditor.getText() == text.replace(
"\n\n\n", "\n\n@pov: Jane\n\n", 1 "\n\n\n", "\n\n@pov: Jane\n\n", 1
) )
@@ -510,7 +662,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
nwGUI.docEditor.setCursorPosition(20) nwGUI.docEditor.setCursorPosition(20)
assert nwGUI.docEditor.insertKeyWord(nwKeyWords.CHAR_KEY) is True assert nwGUI.docEditor.insertKeyWord(nwKeyWords.CHAR_KEY) is True
assert nwGUI.docEditor.insertText("John") assert nwGUI.docEditor.insertText("John")
assert nwGUI.docEditor.getText() == theText.replace( assert nwGUI.docEditor.getText() == text.replace(
"\n\n\n", "\n\n@pov: Jane\n@char: John\n\n", 1 "\n\n\n", "\n\n@pov: Jane\n@char: John\n\n", 1
) )
@@ -525,45 +677,45 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True assert nwGUI.openDocument(C.hSceneDoc) is True
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText) text = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
# Clear Surrounding # Clear Surrounding
# ================= # =================
# No Selection # No Selection
theText = "### A Scene\n\n%s" % ipsumText[0] text = "### A Scene\n\n%s" % ipsumText[0]
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
theCursor = nwGUI.docEditor.textCursor() cursor = nwGUI.docEditor.textCursor()
assert nwGUI.docEditor._clearSurrounding(theCursor, 1) is False assert nwGUI.docEditor._clearSurrounding(cursor, 1) is False
# Clear Characters, 1 Layer # Clear Characters, 1 Layer
repText = theText.replace("consectetur", "=consectetur=") repText = text.replace("consectetur", "=consectetur=")
nwGUI.docEditor.replaceText(repText) nwGUI.docEditor.replaceText(repText)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
theCursor = nwGUI.docEditor.textCursor() cursor = nwGUI.docEditor.textCursor()
theCursor.select(QTextCursor.WordUnderCursor) cursor.select(QTextCursor.WordUnderCursor)
assert nwGUI.docEditor._clearSurrounding(theCursor, 1) is True assert nwGUI.docEditor._clearSurrounding(cursor, 1) is True
assert nwGUI.docEditor.getText() == theText assert nwGUI.docEditor.getText() == text
# Clear Characters, 2 Layers # Clear Characters, 2 Layers
repText = theText.replace("consectetur", "==consectetur==") repText = text.replace("consectetur", "==consectetur==")
nwGUI.docEditor.replaceText(repText) nwGUI.docEditor.replaceText(repText)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
theCursor = nwGUI.docEditor.textCursor() cursor = nwGUI.docEditor.textCursor()
theCursor.select(QTextCursor.WordUnderCursor) cursor.select(QTextCursor.WordUnderCursor)
assert nwGUI.docEditor._clearSurrounding(theCursor, 2) is True assert nwGUI.docEditor._clearSurrounding(cursor, 2) is True
assert nwGUI.docEditor.getText() == theText assert nwGUI.docEditor.getText() == text
# Wrap Selection # Wrap Selection
# ============== # ==============
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]) text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2])
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
# No Selection # No Selection
@@ -572,23 +724,23 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
assert nwGUI.docEditor._wrapSelection("=", "=") is False assert nwGUI.docEditor._wrapSelection("=", "=") is False
# Wrap Equal # Wrap Equal
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._wrapSelection("=") is True assert nwGUI.docEditor._wrapSelection("=") is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "=consectetur=") assert nwGUI.docEditor.getText() == text.replace("consectetur", "=consectetur=")
# Wrap Unequal # Wrap Unequal
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._wrapSelection("=", "*") is True assert nwGUI.docEditor._wrapSelection("=", "*") is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "=consectetur*") assert nwGUI.docEditor.getText() == text.replace("consectetur", "=consectetur*")
# Past Paragraph # Past Paragraph
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
theCursor = nwGUI.docEditor.textCursor() cursor = nwGUI.docEditor.textCursor()
theCursor.setPosition(13, QTextCursor.MoveAnchor) cursor.setPosition(13, QTextCursor.MoveAnchor)
theCursor.setPosition(1000, QTextCursor.KeepAnchor) cursor.setPosition(1000, QTextCursor.KeepAnchor)
nwGUI.docEditor.setTextCursor(theCursor) nwGUI.docEditor.setTextCursor(cursor)
assert nwGUI.docEditor._wrapSelection("=") is True assert nwGUI.docEditor._wrapSelection("=") is True
newText = nwGUI.docEditor.getText() newText = nwGUI.docEditor.getText()
@@ -599,8 +751,8 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
# Toggle Format # Toggle Format
# ============= # =============
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]) text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2])
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
# No Selection # No Selection
@@ -609,17 +761,17 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
assert nwGUI.docEditor._toggleFormat(2, "=") is False assert nwGUI.docEditor._toggleFormat(2, "=") is False
# Wrap Single Equal # Wrap Single Equal
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._toggleFormat(1, "=") is True assert nwGUI.docEditor._toggleFormat(1, "=") is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "=consectetur=") assert nwGUI.docEditor.getText() == text.replace("consectetur", "=consectetur=")
# Past Paragraph # Past Paragraph
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
theCursor = nwGUI.docEditor.textCursor() cursor = nwGUI.docEditor.textCursor()
theCursor.setPosition(13, QTextCursor.MoveAnchor) cursor.setPosition(13, QTextCursor.MoveAnchor)
theCursor.setPosition(1000, QTextCursor.KeepAnchor) cursor.setPosition(1000, QTextCursor.KeepAnchor)
nwGUI.docEditor.setTextCursor(theCursor) nwGUI.docEditor.setTextCursor(cursor)
assert nwGUI.docEditor._toggleFormat(1, "=") is True assert nwGUI.docEditor._toggleFormat(1, "=") is True
newText = nwGUI.docEditor.getText() newText = nwGUI.docEditor.getText()
@@ -628,31 +780,31 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
assert newPara[2] == ipsumText[1] assert newPara[2] == ipsumText[1]
# Wrap Double Equal # Wrap Double Equal
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._toggleFormat(2, "=") is True assert nwGUI.docEditor._toggleFormat(2, "=") is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "==consectetur==") assert nwGUI.docEditor.getText() == text.replace("consectetur", "==consectetur==")
# Toggle Double Equal # Toggle Double Equal
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._toggleFormat(2, "=") is True assert nwGUI.docEditor._toggleFormat(2, "=") is True
assert nwGUI.docEditor._toggleFormat(2, "=") is True assert nwGUI.docEditor._toggleFormat(2, "=") is True
assert nwGUI.docEditor.getText() == theText assert nwGUI.docEditor.getText() == text
# Toggle Triple+Double Equal # Toggle Triple+Double Equal
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._toggleFormat(3, "=") is True assert nwGUI.docEditor._toggleFormat(3, "=") is True
assert nwGUI.docEditor._toggleFormat(2, "=") is True assert nwGUI.docEditor._toggleFormat(2, "=") is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "=consectetur=") assert nwGUI.docEditor.getText() == text.replace("consectetur", "=consectetur=")
# Toggle Unequal # Toggle Unequal
repText = theText.replace("consectetur", "=consectetur==") repText = text.replace("consectetur", "=consectetur==")
nwGUI.docEditor.replaceText(repText) nwGUI.docEditor.replaceText(repText)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._toggleFormat(1, "=") is True assert nwGUI.docEditor._toggleFormat(1, "=") is True
assert nwGUI.docEditor.getText() == theText.replace("consectetur", "consectetur=") assert nwGUI.docEditor.getText() == text.replace("consectetur", "consectetur=")
assert nwGUI.docEditor._toggleFormat(1, "=") is True assert nwGUI.docEditor._toggleFormat(1, "=") is True
assert nwGUI.docEditor.getText() == repText assert nwGUI.docEditor.getText() == repText
@@ -660,15 +812,15 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
# ============== # ==============
# No Selection # No Selection
theText = "### A Scene\n\n%s" % ipsumText[0].replace("consectetur", "=consectetur=") text = "### A Scene\n\n%s" % ipsumText[0].replace("consectetur", "=consectetur=")
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is False assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is False
# First Paragraph Selected # First Paragraph Selected
# This should not replace anything in second paragraph # This should not replace anything in second paragraph
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]).replace("ipsum", "=ipsum=") text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2]).replace("ipsum", "=ipsum=")
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA) assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA)
assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is True assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is True
@@ -679,12 +831,12 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
assert newPara[2] == ipsumText[1].replace("ipsum", "=ipsum=") assert newPara[2] == ipsumText[1].replace("ipsum", "=ipsum=")
# Edge of Document # Edge of Document
theText = ipsumText[0].replace("Lorem", "=Lorem=") text = ipsumText[0].replace("Lorem", "=Lorem=")
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL)
assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is True assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is True
assert nwGUI.docEditor.getText() == theText.replace("=Lorem=", "<Lorem>") assert nwGUI.docEditor.getText() == text.replace("=Lorem=", "<Lorem>")
# Remove Line Breaks # Remove Line Breaks
# ================== # ==================
@@ -693,20 +845,20 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
parTwo = ipsumText[1].replace(" ", "\n", 5) parTwo = ipsumText[1].replace(" ", "\n", 5)
# Remove All # Remove All
theText = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo) text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo)
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) nwGUI.docEditor.setCursorPosition(45)
nwGUI.docEditor._removeInParLineBreaks() nwGUI.docEditor._removeInParLineBreaks()
assert nwGUI.docEditor.getText() == "### A Scene\n\n%s\n" % "\n\n".join(ipsumText[0:2]) assert nwGUI.docEditor.getText() == "### A Scene\n\n%s\n" % "\n\n".join(ipsumText[0:2])
# Remove First Paragraph # Remove First Paragraph
# Second paragraphs should remain unchanged # Second paragraphs should remain unchanged
theText = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo) text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo)
nwGUI.docEditor.replaceText(theText) nwGUI.docEditor.replaceText(text)
theCursor = nwGUI.docEditor.textCursor() cursor = nwGUI.docEditor.textCursor()
theCursor.setPosition(16, QTextCursor.MoveAnchor) cursor.setPosition(16, QTextCursor.MoveAnchor)
theCursor.setPosition(680, QTextCursor.KeepAnchor) cursor.setPosition(680, QTextCursor.KeepAnchor)
nwGUI.docEditor.setTextCursor(theCursor) nwGUI.docEditor.setTextCursor(cursor)
nwGUI.docEditor._removeInParLineBreaks() nwGUI.docEditor._removeInParLineBreaks()
newText = nwGUI.docEditor.getText() newText = nwGUI.docEditor.getText()
@@ -720,6 +872,25 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
assert newPara[6] == twoBits[4] assert newPara[6] == twoBits[4]
assert newPara[7] == " ".join(twoBits[5:]) assert newPara[7] == " ".join(twoBits[5:])
# Key Press Events
# ================
text = "### A Scene\n\n%s\n\n%s" % (parOne, parTwo)
nwGUI.docEditor.replaceText(text)
assert nwGUI.docEditor.getText() == text
# Select All
qtbot.keyClick(nwGUI.docEditor, Qt.Key_A, modifier=Qt.ControlModifier, delay=KEY_DELAY)
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Delete, delay=KEY_DELAY)
assert nwGUI.docEditor.getText() == ""
# Undo
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Z, modifier=Qt.ControlModifier, delay=KEY_DELAY)
assert nwGUI.docEditor.getText() == text
# Redo
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Y, modifier=Qt.ControlModifier, delay=KEY_DELAY)
assert nwGUI.docEditor.getText() == ""
# qtbot.stop() # qtbot.stop()
# END Test testGuiEditor_TextManipulation # END Test testGuiEditor_TextManipulation
+7 -3
View File
@@ -594,14 +594,18 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
# ========== # ==========
# No document open, so not allowing focus mode # No document open, so not allowing focus mode
assert nwGUI.toggleFocusMode() is False nwGUI.toggleFocusMode()
assert nwGUI.treePane.isVisible() is True
assert nwGUI.mainStatus.isVisible() is True
assert nwGUI.mainMenu.isVisible() is True
assert nwGUI.sideBar.isVisible() is True
# Open a file in editor and viewer # Open a file in editor and viewer
assert nwGUI.openDocument(C.hSceneDoc) assert nwGUI.openDocument(C.hSceneDoc)
assert nwGUI.viewDocument(C.hSceneDoc) assert nwGUI.viewDocument(C.hSceneDoc)
# Enable focus mode # Enable focus mode
assert nwGUI.toggleFocusMode() is True nwGUI.toggleFocusMode()
assert nwGUI.treePane.isVisible() is False assert nwGUI.treePane.isVisible() is False
assert nwGUI.mainStatus.isVisible() is False assert nwGUI.mainStatus.isVisible() is False
assert nwGUI.mainMenu.isVisible() is False assert nwGUI.mainMenu.isVisible() is False
@@ -609,7 +613,7 @@ def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
assert nwGUI.splitView.isVisible() is False assert nwGUI.splitView.isVisible() is False
# Disable focus mode # Disable focus mode
assert nwGUI.toggleFocusMode() is True nwGUI.toggleFocusMode()
assert nwGUI.treePane.isVisible() is True assert nwGUI.treePane.isVisible() is True
assert nwGUI.mainStatus.isVisible() is True assert nwGUI.mainStatus.isVisible() is True
assert nwGUI.mainMenu.isVisible() is True assert nwGUI.mainMenu.isVisible() is True
+3 -3
View File
@@ -322,17 +322,17 @@ def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, tstPaths):
assert qPix.isNull() is True assert qPix.isNull() is True
# Test image sizes # Test image sizes
qPix = iconCache.loadDecoration("wiz-back", pxW=100, pxH=None) qPix = iconCache.loadDecoration("wiz-back", w=100, h=None)
assert qPix.isNull() is False assert qPix.isNull() is False
assert qPix.width() == 100 assert qPix.width() == 100
assert qPix.height() > 100 assert qPix.height() > 100
qPix = iconCache.loadDecoration("wiz-back", pxW=None, pxH=100) qPix = iconCache.loadDecoration("wiz-back", w=None, h=100)
assert qPix.isNull() is False assert qPix.isNull() is False
assert qPix.width() < 100 assert qPix.width() < 100
assert qPix.height() == 100 assert qPix.height() == 100
qPix = iconCache.loadDecoration("wiz-back", pxW=100, pxH=100) qPix = iconCache.loadDecoration("wiz-back", w=100, h=100)
assert qPix.isNull() is False assert qPix.isNull() is False
assert qPix.width() == 100 assert qPix.width() == 100
assert qPix.height() == 100 assert qPix.height() == 100