Merge branch 'main' into xml_rw_class
@@ -14,7 +14,7 @@ jobs:
|
||||
testLinux:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Python Setup
|
||||
|
||||
@@ -229,11 +229,28 @@ final release.
|
||||
|
||||
----
|
||||
|
||||
## Version 1.6.6 [2022-10-25]
|
||||
|
||||
### Release Notes
|
||||
|
||||
This is a bugfix release that fixes a minor issues with following tags in the editor. It is now
|
||||
possible to also follow tags that contain spaces.
|
||||
|
||||
### Detailed Changelog
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
* Fix a bug where only the word under the cursor would be looked up when the user tried to follow a
|
||||
tag in the editor. The lookup function now uses the same parser for the `@`-line as the syntax
|
||||
highlighter does, so they should behave consistently. Issue #1195. PR #1209.
|
||||
|
||||
----
|
||||
|
||||
## Version 1.6.5 [2022-10-13]
|
||||
|
||||
### Release Notes
|
||||
|
||||
This is a bugfix release that fixes a a few minor issues. The idle time for new projects would be
|
||||
This is a bugfix release that fixes a few minor issues. The idle time for new projects would be
|
||||
artificially inflated as the clock was not reset when the project was first created. This only
|
||||
affects the first entry in the writing statistics. A scaling issue for the Preferences dialog has
|
||||
also been fixed. It only affected screens with UI scaling enabled. Lastly, typing `Shift+Enter` in
|
||||
|
||||
@@ -137,6 +137,7 @@ a key or key combination for the inserted content.
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`F`", "Insert a ``@focus`` keyword."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`C`", "Insert a ``@char`` keyword."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`P`", "Insert a ``@plot`` keyword."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`S`", "Insert a synopsis comment."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`T`", "Insert a ``@time`` keyword."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`L`", "Insert a ``@location`` keyword."
|
||||
":kbd:`Ctrl`:kbd:`K`, :kbd:`O`", "Insert an ``@object`` keyword."
|
||||
|
||||
@@ -21,7 +21,7 @@ backward = typ_chevron-left.svg
|
||||
bookmark = typ_bookmark.svg
|
||||
bullet-off = typ_media-record-outline.svg
|
||||
bullet-on = typ_media-record.svg
|
||||
check = typ_tick.svg
|
||||
checked = mixed_input-checked.svg
|
||||
close = typ_times.svg
|
||||
cls_archive = typ_delete.svg
|
||||
cls_character = typ_user.svg
|
||||
@@ -41,13 +41,14 @@ forward = typ_chevron-right.svg
|
||||
maximise = typ_arrow-maximise.svg
|
||||
menu = typ_th-menu.svg
|
||||
minimise = typ_arrow-minimise.svg
|
||||
noncheckable = mixed_input-none.svg
|
||||
proj_chapter = mixed_document-chapter.svg
|
||||
proj_details = typ_th-list-grey.svg
|
||||
proj_document = typ_document-text.svg
|
||||
proj_folder = typ_folder.svg
|
||||
proj_note = mixed_document-note.svg
|
||||
proj_section = mixed_document-section.svg
|
||||
proj_scene = mixed_document-scene.svg
|
||||
proj_section = mixed_document-section.svg
|
||||
proj_stats = typ_chart-bar-grey.svg
|
||||
proj_title = mixed_document-title.svg
|
||||
reference = typ_at.svg
|
||||
@@ -70,6 +71,7 @@ status_stats = typ_chart-bar-grey.svg
|
||||
status_time = typ_stopwatch-grey.svg
|
||||
sticky-off = typ_pin-outline.svg
|
||||
sticky-on = typ_pin.svg
|
||||
unchecked = mixed_input-unchecked.svg
|
||||
up = typ_chevron-up.svg
|
||||
view_build = typ_export.svg
|
||||
view_editor = mixed_edit.svg
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
version="1.2"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg2287">
|
||||
<defs
|
||||
id="defs2291" />
|
||||
<metadata
|
||||
id="metadata2281">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
d="m17.714 22h-11.429c-2.3629 0-4.2857-1.9229-4.2857-4.2857v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h7.1429c0.79 0 1.4286 0.64 1.4286 1.4286 0 0.78857-0.63857 1.4286-1.4286 1.4286h-7.1429c-0.78857 0-1.4286 0.64143-1.4286 1.4286v11.429c0 0.78714 0.64 1.4286 1.4286 1.4286h11.429c0.78857 0 1.4286-0.64143 1.4286-1.4286v-4.2857c0-0.78857 0.63857-1.4286 1.4286-1.4286s1.4286 0.64 1.4286 1.4286v4.2857c0 2.3629-1.9229 4.2857-4.2857 4.2857z"
|
||||
fill="#848484"
|
||||
stroke-width="1.4286"
|
||||
id="path2283" />
|
||||
<path
|
||||
d="m19.02 3.3503c-0.60933 0.010623-1.2003 0.31545-1.5586 0.86719l-5.0977 7.8477-2.3672-3.6445c-0.57329-0.88278-1.7442-1.1319-2.627-0.55859-0.88278 0.57329-1.1319 1.7461-0.55859 2.6289l3.9199 6.0391c0.38514 0.59307 1.0409 0.88965 1.6973 0.85352 0.03023-2.78e-4 0.05972-0.0041 0.08984-0.0059 0.0556-0.0057 0.11074-0.0088 0.16602-0.01953 0.52104-0.07621 1.0077-0.36147 1.3184-0.83984l6.6445-10.23c0.57329-0.88278 0.32419-2.0556-0.55859-2.6289-0.33104-0.21498-0.70276-0.31497-1.0684-0.30859z"
|
||||
fill="#9c9"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.111"
|
||||
id="path2285" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
version="1.2"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg2931">
|
||||
<defs
|
||||
id="defs2935" />
|
||||
<metadata
|
||||
id="metadata2925">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
d="m2 17.714v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h11.429c2.3629 0 4.2857 1.9229 4.2857 4.2857v7.1429c0 0.79-0.64 1.4286-1.4286 1.4286-0.78857 0-1.4286-0.63857-1.4286-1.4286v-7.1429c0-0.78857-0.64143-1.4286-1.4286-1.4286h-11.429c-0.78714 0-1.4286 0.64-1.4286 1.4286v11.429c0 0.78857 0.64143 1.4286 1.4286 1.4286h4.2857c0.78857 0 1.4286 0.63857 1.4286 1.4286s-0.64 1.4286-1.4286 1.4286h-4.2857c-2.3629 0-4.2857-1.9229-4.2857-4.2857zm15.714 4.2857h-11.429c-2.3629 0-4.2857-1.9229-4.2857-4.2857v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h7.1429c0.79 0 1.4286 0.64 1.4286 1.4286 0 0.78857-0.63857 1.4286-1.4286 1.4286h-7.1429c-0.78857 0-1.4286 0.64143-1.4286 1.4286v11.429c0 0.78714 0.64 1.4286 1.4286 1.4286h11.429c0.78857 0 1.4286-0.64143 1.4286-1.4286v-4.2857c0-0.78857 0.63857-1.4286 1.4286-1.4286s1.4286 0.64 1.4286 1.4286v4.2857c0 2.3629-1.9229 4.2857-4.2857 4.2857z"
|
||||
fill="#848484"
|
||||
stroke-width="1.4286"
|
||||
id="path2927" />
|
||||
<rect
|
||||
x="7"
|
||||
y="10.1"
|
||||
width="10"
|
||||
height="3.8"
|
||||
rx="1.9"
|
||||
ry="1.9"
|
||||
fill="#f99157"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.111"
|
||||
id="rect2929" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
version="1.2"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg3574">
|
||||
<defs
|
||||
id="defs3578" />
|
||||
<metadata
|
||||
id="metadata3568">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
d="m17.714 22h-11.429c-2.3629 0-4.2857-1.9229-4.2857-4.2857v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h7.1429c0.79 0 1.4286 0.64 1.4286 1.4286 0 0.78857-0.63857 1.4286-1.4286 1.4286h-7.1429c-0.78857 0-1.4286 0.64143-1.4286 1.4286v11.429c0 0.78714 0.64 1.4286 1.4286 1.4286h11.429c0.78857 0 1.4286-0.64143 1.4286-1.4286v-4.2857c0-0.78857 0.63857-1.4286 1.4286-1.4286s1.4286 0.64 1.4286 1.4286v4.2857c0 2.3629-1.9229 4.2857-4.2857 4.2857z"
|
||||
fill="#848484"
|
||||
stroke-width="1.4286"
|
||||
id="path3570" />
|
||||
<path
|
||||
d="m17.99 3.3379c-0.48501 0.025418-0.96034 0.23584-1.3125 0.62695l-4.6777 5.1953-1.332-1.4785c-0.70433-0.78223-1.9014-0.84495-2.6836-0.14062-0.78223 0.70433-0.84495 1.9014-0.14062 2.6836l1.5996 1.7754-1.5977 1.7754c-0.70433 0.78223-0.64161 1.9793 0.14062 2.6836 0.78223 0.70433 1.9773 0.64161 2.6816-0.14062l1.332-1.4785 1.332 1.4785c0.70433 0.78224 1.9014 0.84495 2.6836 0.14062 0.78223-0.70433 0.84495-1.9014 0.14062-2.6836l-1.5996-1.7754 4.9453-5.4922c0.70433-0.78223 0.64161-1.9793-0.14062-2.6836-0.39112-0.35216-0.88608-0.51175-1.3711-0.48633z"
|
||||
fill="#d64848"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.111"
|
||||
id="path3572" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg896">
|
||||
<metadata
|
||||
id="metadata902">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs900" />
|
||||
<path
|
||||
d="M 19.576035,3.3487939 C 18.237131,2.6038813 16.550693,3.0884898 15.809935,4.4246248 L 10.66893,13.676495 7.726664,10.734229 c -1.0813694,-1.0813704 -2.8342677,-1.0813704 -3.9156371,0 -1.0813693,1.081369 -1.0813693,2.834267 0,3.915637 l 5.538383,5.538383 c 0.523378,0.524762 1.2295221,0.812758 1.9578191,0.812758 l 0.383533,-0.02769 c 0.859834,-0.12046 1.614439,-0.636914 2.03674,-1.397057 L 20.650482,7.1148966 C 21.39401,5.777375 20.91217,4.0923218 19.576035,3.3487939 Z"
|
||||
id="path894"
|
||||
style="stroke-width:1.3846;fill:#99cc99;fill-opacity:1" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -21,7 +21,7 @@ backward = typ_chevron-left.svg
|
||||
bookmark = typ_bookmark.svg
|
||||
bullet-off = typ_media-record-outline.svg
|
||||
bullet-on = typ_media-record.svg
|
||||
check = typ_tick.svg
|
||||
checked = mixed_input-checked.svg
|
||||
close = typ_times.svg
|
||||
cls_archive = typ_delete.svg
|
||||
cls_character = typ_user.svg
|
||||
@@ -41,13 +41,14 @@ forward = typ_chevron-right.svg
|
||||
maximise = typ_arrow-maximise.svg
|
||||
menu = typ_th-menu.svg
|
||||
minimise = typ_arrow-minimise.svg
|
||||
noncheckable = mixed_input-none.svg
|
||||
proj_chapter = mixed_document-chapter.svg
|
||||
proj_details = typ_th-list-grey.svg
|
||||
proj_document = typ_document-text.svg
|
||||
proj_folder = typ_folder.svg
|
||||
proj_note = mixed_document-note.svg
|
||||
proj_section = mixed_document-section.svg
|
||||
proj_scene = mixed_document-scene.svg
|
||||
proj_section = mixed_document-section.svg
|
||||
proj_stats = typ_chart-bar-grey.svg
|
||||
proj_title = mixed_document-title.svg
|
||||
reference = typ_at.svg
|
||||
@@ -70,6 +71,7 @@ status_stats = typ_chart-bar-grey.svg
|
||||
status_time = typ_stopwatch-grey.svg
|
||||
sticky-off = typ_pin-outline.svg
|
||||
sticky-on = typ_pin.svg
|
||||
unchecked = mixed_input-unchecked.svg
|
||||
up = typ_chevron-up.svg
|
||||
view_build = typ_export.svg
|
||||
view_editor = mixed_edit.svg
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
version="1.2"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg879">
|
||||
<defs
|
||||
id="defs883" />
|
||||
<metadata
|
||||
id="metadata873">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
d="m17.714 22h-11.429c-2.3629 0-4.2857-1.9229-4.2857-4.2857v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h7.1429c0.79 0 1.4286 0.64 1.4286 1.4286 0 0.78857-0.63857 1.4286-1.4286 1.4286h-7.1429c-0.78857 0-1.4286 0.64143-1.4286 1.4286v11.429c0 0.78714 0.64 1.4286 1.4286 1.4286h11.429c0.78857 0 1.4286-0.64143 1.4286-1.4286v-4.2857c0-0.78857 0.63857-1.4286 1.4286-1.4286s1.4286 0.64 1.4286 1.4286v4.2857c0 2.3629-1.9229 4.2857-4.2857 4.2857z"
|
||||
fill="#333"
|
||||
stroke-width="1.4286"
|
||||
id="path875" />
|
||||
<path
|
||||
d="m19.02 3.3503c-0.60933 0.010623-1.2003 0.31545-1.5586 0.86719l-5.0977 7.8477-2.3672-3.6445c-0.57329-0.88278-1.7442-1.1319-2.627-0.55859-0.88278 0.57329-1.1319 1.7461-0.55859 2.6289l3.9199 6.0391c0.38514 0.59307 1.0409 0.88965 1.6973 0.85352 0.03023-2.78e-4 0.05972-0.0041 0.08984-0.0059 0.0556-0.0057 0.11074-0.0088 0.16602-0.01953 0.52104-0.07621 1.0077-0.36147 1.3184-0.83984l6.6445-10.23c0.57329-0.88278 0.32419-2.0556-0.55859-2.6289-0.33104-0.21498-0.70276-0.31497-1.0684-0.30859z"
|
||||
fill="#718c00"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.111"
|
||||
id="path877" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
version="1.2"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg973">
|
||||
<defs
|
||||
id="defs977" />
|
||||
<metadata
|
||||
id="metadata967">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
d="m2 17.714v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h11.429c2.3629 0 4.2857 1.9229 4.2857 4.2857v7.1429c0 0.79-0.64 1.4286-1.4286 1.4286-0.78857 0-1.4286-0.63857-1.4286-1.4286v-7.1429c0-0.78857-0.64143-1.4286-1.4286-1.4286h-11.429c-0.78714 0-1.4286 0.64-1.4286 1.4286v11.429c0 0.78857 0.64143 1.4286 1.4286 1.4286h4.2857c0.78857 0 1.4286 0.63857 1.4286 1.4286s-0.64 1.4286-1.4286 1.4286h-4.2857c-2.3629 0-4.2857-1.9229-4.2857-4.2857zm15.714 4.2857h-11.429c-2.3629 0-4.2857-1.9229-4.2857-4.2857v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h7.1429c0.79 0 1.4286 0.64 1.4286 1.4286 0 0.78857-0.63857 1.4286-1.4286 1.4286h-7.1429c-0.78857 0-1.4286 0.64143-1.4286 1.4286v11.429c0 0.78714 0.64 1.4286 1.4286 1.4286h11.429c0.78857 0 1.4286-0.64143 1.4286-1.4286v-4.2857c0-0.78857 0.63857-1.4286 1.4286-1.4286s1.4286 0.64 1.4286 1.4286v4.2857c0 2.3629-1.9229 4.2857-4.2857 4.2857z"
|
||||
fill="#333"
|
||||
stroke-width="1.4286"
|
||||
id="path969" />
|
||||
<rect
|
||||
x="7"
|
||||
y="10.1"
|
||||
width="10"
|
||||
height="3.8"
|
||||
rx="1.9"
|
||||
ry="1.9"
|
||||
fill="#f5871f"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.111"
|
||||
id="rect971" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
version="1.2"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg1616">
|
||||
<defs
|
||||
id="defs1620" />
|
||||
<metadata
|
||||
id="metadata1610">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
d="m17.714 22h-11.429c-2.3629 0-4.2857-1.9229-4.2857-4.2857v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h7.1429c0.79 0 1.4286 0.64 1.4286 1.4286 0 0.78857-0.63857 1.4286-1.4286 1.4286h-7.1429c-0.78857 0-1.4286 0.64143-1.4286 1.4286v11.429c0 0.78714 0.64 1.4286 1.4286 1.4286h11.429c0.78857 0 1.4286-0.64143 1.4286-1.4286v-4.2857c0-0.78857 0.63857-1.4286 1.4286-1.4286s1.4286 0.64 1.4286 1.4286v4.2857c0 2.3629-1.9229 4.2857-4.2857 4.2857z"
|
||||
fill="#333"
|
||||
stroke-width="1.4286"
|
||||
id="path1612" />
|
||||
<path
|
||||
d="m17.99 3.3379c-0.48501 0.025418-0.96034 0.23584-1.3125 0.62695l-4.6777 5.1953-1.332-1.4785c-0.70433-0.78223-1.9014-0.84495-2.6836-0.14062-0.78223 0.70433-0.84495 1.9014-0.14062 2.6836l1.5996 1.7754-1.5977 1.7754c-0.70433 0.78223-0.64161 1.9793 0.14062 2.6836 0.78223 0.70433 1.9773 0.64161 2.6816-0.14062l1.332-1.4785 1.332 1.4785c0.70433 0.78224 1.9014 0.84495 2.6836 0.14062 0.78223-0.70433 0.84495-1.9014 0.14062-2.6836l-1.5996-1.7754 4.9453-5.4922c0.70433-0.78223 0.64161-1.9793-0.14062-2.6836-0.39112-0.35216-0.88608-0.51175-1.3711-0.48633z"
|
||||
fill="#c82829"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.111"
|
||||
id="path1614" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg896">
|
||||
<metadata
|
||||
id="metadata902">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs900" />
|
||||
<path
|
||||
d="M 19.576035,3.3487939 C 18.237131,2.6038813 16.550693,3.0884898 15.809935,4.4246248 L 10.66893,13.676495 7.726664,10.734229 c -1.0813694,-1.0813704 -2.8342677,-1.0813704 -3.9156371,0 -1.0813693,1.081369 -1.0813693,2.834267 0,3.915637 l 5.538383,5.538383 c 0.523378,0.524762 1.2295221,0.812758 1.9578191,0.812758 l 0.383533,-0.02769 c 0.859834,-0.12046 1.614439,-0.636914 2.03674,-1.397057 L 20.650482,7.1148966 C 21.39401,5.777375 20.91217,4.0923218 19.576035,3.3487939 Z"
|
||||
id="path894"
|
||||
style="stroke-width:1.3846;fill:#718c00;fill-opacity:1" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,2 +1,4 @@
|
||||
[Main]
|
||||
name = Default System Theme
|
||||
name = Default Theme
|
||||
description = Qt standard colours
|
||||
icontheme = typicons_light
|
||||
@@ -1,10 +1,12 @@
|
||||
[Main]
|
||||
name = Default Dark Theme
|
||||
author = Veronica Berglyd Olsen
|
||||
credit = Veronica Berglyd Olsen
|
||||
url = https://github.com/vkbo/novelWriter
|
||||
license = CC BY-SA 4.0
|
||||
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
|
||||
name = Default Dark Theme
|
||||
description = The novelWriter standard dark theme
|
||||
author = Veronica Berglyd Olsen
|
||||
credit = Veronica Berglyd Olsen
|
||||
url = https://github.com/vkbo/novelWriter
|
||||
license = CC BY-SA 4.0
|
||||
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
|
||||
icontheme = typicons_dark
|
||||
|
||||
[Palette]
|
||||
window = 54, 54, 54
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Default Theme: Dark
|
||||
* This theme doesn't use any custom styles, so the file is only here as
|
||||
* an example. There doesn't have to be a styles.qss file in the folder.
|
||||
*/
|
||||
@@ -5,6 +5,7 @@ credit = Ethan Schoonover
|
||||
url = https://ethanschoonover.com/solarized/
|
||||
license = MIT
|
||||
licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
|
||||
icontheme = typicons_dark
|
||||
|
||||
[Palette]
|
||||
window = 0, 43, 54
|
||||
|
||||
@@ -5,6 +5,7 @@ credit = Ethan Schoonover
|
||||
url = https://ethanschoonover.com/solarized/
|
||||
license = MIT
|
||||
licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
|
||||
icontheme = typicons_light
|
||||
|
||||
[Palette]
|
||||
window = 238, 232, 213
|
||||
|
||||
@@ -73,7 +73,6 @@ class Config:
|
||||
# General
|
||||
self.guiTheme = "" # GUI theme
|
||||
self.guiSyntax = "" # Syntax theme
|
||||
self.guiIcons = "" # Icon theme
|
||||
self.guiFont = "" # Defaults to system default font
|
||||
self.guiFontSize = 11 # Is overridden if system default is loaded
|
||||
self.guiScale = 1.0 # Set automatically by Theme class
|
||||
@@ -81,7 +80,6 @@ class Config:
|
||||
|
||||
self.setDefaultGuiTheme()
|
||||
self.setDefaultSyntaxTheme()
|
||||
self.setDefaultIconTheme()
|
||||
|
||||
# Localisation
|
||||
self.qLocal = QLocale.system()
|
||||
@@ -410,7 +408,6 @@ class Config:
|
||||
cnfSec = "Main"
|
||||
self.guiTheme = theConf.rdStr(cnfSec, "theme", self.guiTheme)
|
||||
self.guiSyntax = theConf.rdStr(cnfSec, "syntax", self.guiSyntax)
|
||||
self.guiIcons = theConf.rdStr(cnfSec, "icons", self.guiIcons)
|
||||
self.guiFont = theConf.rdStr(cnfSec, "guifont", self.guiFont)
|
||||
self.guiFontSize = theConf.rdInt(cnfSec, "guifontsize", self.guiFontSize)
|
||||
self.lastNotes = theConf.rdStr(cnfSec, "lastnotes", self.lastNotes)
|
||||
@@ -508,12 +505,6 @@ class Config:
|
||||
logger.info("Using straight double quotes, so disabling auto-replace")
|
||||
self.doReplaceDQuote = False
|
||||
|
||||
# Check deprecated settings
|
||||
if self.guiIcons in ("typicons_colour_dark", "typicons_grey_dark"):
|
||||
self.guiIcons = "typicons_dark"
|
||||
elif self.guiIcons in ("typicons_colour_light", "typicons_grey_light"):
|
||||
self.guiIcons = "typicons_light"
|
||||
|
||||
return True
|
||||
|
||||
def saveConfig(self):
|
||||
@@ -529,7 +520,6 @@ class Config:
|
||||
"timestamp": formatTimeStamp(time()),
|
||||
"theme": str(self.guiTheme),
|
||||
"syntax": str(self.guiSyntax),
|
||||
"icons": str(self.guiIcons),
|
||||
"guifont": str(self.guiFont),
|
||||
"guifontsize": str(self.guiFontSize),
|
||||
"lastnotes": str(self.lastNotes),
|
||||
@@ -816,11 +806,6 @@ class Config:
|
||||
"""
|
||||
self.guiSyntax = "default_light"
|
||||
|
||||
def setDefaultIconTheme(self):
|
||||
"""Reset the icon theme to default value.
|
||||
"""
|
||||
self.guiIcons = "typicons_light"
|
||||
|
||||
##
|
||||
# Getters
|
||||
##
|
||||
|
||||
@@ -192,7 +192,7 @@ class DocSplitter:
|
||||
"""An iterator that will write each document in the buffer, and
|
||||
return its new handle, parent handle, and sibling handle.
|
||||
"""
|
||||
if self._srcHandle is None:
|
||||
if self._srcHandle is None or self._srcItem is None:
|
||||
return
|
||||
|
||||
pHandle = self._parHandle
|
||||
@@ -224,6 +224,10 @@ class DocSplitter:
|
||||
dHandle = self.theProject.newFile(docLabel, pHandle)
|
||||
hHandle[hLevel] = dHandle
|
||||
|
||||
newItem = self.theProject.tree[dHandle]
|
||||
newItem.setStatus(self._srcItem.itemStatus)
|
||||
newItem.setImport(self._srcItem.itemImport)
|
||||
|
||||
outDoc = NWDoc(self.theProject, dHandle)
|
||||
status = outDoc.writeDocument("\n".join(docText))
|
||||
if not status:
|
||||
|
||||
@@ -30,7 +30,8 @@ import novelwriter
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from PyQt5.QtGui import QIcon, QPixmap, QColor
|
||||
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor
|
||||
from PyQt5.QtCore import QRectF, Qt
|
||||
|
||||
from novelwriter.common import checkInt, minmax, simplified
|
||||
|
||||
@@ -49,10 +50,15 @@ class NWStatus:
|
||||
self._reverse = {}
|
||||
self._default = None
|
||||
|
||||
self._iconSize = novelwriter.CONFIG.pxInt(32)
|
||||
pixmap = QPixmap(self._iconSize, self._iconSize)
|
||||
pixmap.fill(QColor(100, 100, 100))
|
||||
self._defaultIcon = QIcon(pixmap)
|
||||
self._iPX = novelwriter.CONFIG.pxInt(24)
|
||||
|
||||
pA = novelwriter.CONFIG.pxInt(2)
|
||||
pB = novelwriter.CONFIG.pxInt(20)
|
||||
pR = float(novelwriter.CONFIG.pxInt(4))
|
||||
self._iconPath = QPainterPath()
|
||||
self._iconPath.addRoundedRect(QRectF(pA, pA, pB, pB), pR, pR)
|
||||
|
||||
self._defaultIcon = self._createIcon([100, 100, 100])
|
||||
|
||||
if self._type == self.STATUS:
|
||||
self._prefix = "s"
|
||||
@@ -63,19 +69,16 @@ class NWStatus:
|
||||
|
||||
return
|
||||
|
||||
def write(self, key, name, cols, count=None):
|
||||
def write(self, key, name, col, count=None):
|
||||
"""Add or update a status entry. If the key is invalid, a new
|
||||
key is generated.
|
||||
"""
|
||||
if not self._isKey(key):
|
||||
key = self._newKey()
|
||||
if not isinstance(cols, tuple):
|
||||
cols = (100, 100, 100)
|
||||
if len(cols) != 3:
|
||||
cols = (100, 100, 100)
|
||||
|
||||
pixmap = QPixmap(self._iconSize, self._iconSize)
|
||||
pixmap.fill(QColor(*cols))
|
||||
if not isinstance(col, tuple):
|
||||
col = (100, 100, 100)
|
||||
if len(col) != 3:
|
||||
col = (100, 100, 100)
|
||||
|
||||
name = simplified(name)
|
||||
if count is None:
|
||||
@@ -83,8 +86,8 @@ class NWStatus:
|
||||
|
||||
self._store[key] = {
|
||||
"name": name,
|
||||
"icon": QIcon(pixmap),
|
||||
"cols": cols,
|
||||
"icon": self._createIcon(col),
|
||||
"cols": col,
|
||||
"count": count,
|
||||
}
|
||||
self._reverse[name] = key
|
||||
@@ -267,6 +270,19 @@ class NWStatus:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _createIcon(self, col):
|
||||
"""Generate an icon for a status label.
|
||||
"""
|
||||
pixmap = QPixmap(self._iPX, self._iPX)
|
||||
pixmap.fill(Qt.transparent)
|
||||
|
||||
painter = QPainter(pixmap)
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
painter.fillPath(self._iconPath, QColor(*col))
|
||||
painter.end()
|
||||
|
||||
return QIcon(pixmap)
|
||||
|
||||
##
|
||||
# Iterator Bits
|
||||
##
|
||||
|
||||
@@ -421,6 +421,12 @@ class PagedDialog(QDialog):
|
||||
self._buttonBox.addWidget(buttonBar)
|
||||
return
|
||||
|
||||
def setCurrentWidget(self, widget):
|
||||
"""Forward the changing of tab to the QTabWidget.
|
||||
"""
|
||||
self._tabBox.setCurrentWidget(widget)
|
||||
return
|
||||
|
||||
# END Class PagedDialog
|
||||
|
||||
|
||||
|
||||
@@ -103,14 +103,19 @@ class GuiDocSplit(QDialog):
|
||||
self.hierarchySwitch = QSwitch(width=2*iPx, height=iPx)
|
||||
self.hierarchySwitch.setChecked(docHierarchy)
|
||||
|
||||
self.trashLabel = QLabel(self.tr("Move split document to Trash"))
|
||||
self.trashSwitch = QSwitch(width=2*iPx, height=iPx)
|
||||
|
||||
self.optBox = QGridLayout()
|
||||
self.optBox.addWidget(self.folderLabel, 0, 0)
|
||||
self.optBox.addWidget(self.folderSwitch, 0, 1)
|
||||
self.optBox.addWidget(self.hierarchyLabel, 1, 0)
|
||||
self.optBox.addWidget(self.hierarchySwitch, 1, 1)
|
||||
self.optBox.addWidget(self.trashLabel, 2, 0)
|
||||
self.optBox.addWidget(self.trashSwitch, 2, 1)
|
||||
self.optBox.setVerticalSpacing(vSp)
|
||||
self.optBox.setHorizontalSpacing(hSp)
|
||||
self.optBox.setColumnStretch(2, 1)
|
||||
self.optBox.setColumnStretch(3, 1)
|
||||
|
||||
# Buttons
|
||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
@@ -155,11 +160,13 @@ class GuiDocSplit(QDialog):
|
||||
spLevel = self.splitLevel.currentData()
|
||||
intoFolder = self.folderSwitch.isChecked()
|
||||
docHierarchy = self.hierarchySwitch.isChecked()
|
||||
moveToTrash = self.trashSwitch.isChecked()
|
||||
|
||||
self._data["spLevel"] = spLevel
|
||||
self._data["headerList"] = headerList
|
||||
self._data["intoFolder"] = intoFolder
|
||||
self._data["docHierarchy"] = docHierarchy
|
||||
self._data["moveToTrash"] = moveToTrash
|
||||
|
||||
pOptions = self.theProject.options
|
||||
pOptions.setValue("GuiDocSplit", "spLevel", spLevel)
|
||||
|
||||
@@ -34,7 +34,6 @@ from PyQt5.QtWidgets import (
|
||||
QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox
|
||||
)
|
||||
|
||||
from novelwriter.enum import nwAlert
|
||||
from novelwriter.custom import QSwitch, QConfigLayout, PagedDialog
|
||||
from novelwriter.dialogs.quotes import GuiQuoteSelect
|
||||
|
||||
@@ -78,10 +77,36 @@ class GuiPreferences(PagedDialog):
|
||||
|
||||
self.resize(*self.mainConf.getPreferencesSize())
|
||||
|
||||
# Settings
|
||||
self._updateTheme = False
|
||||
self._updateSyntax = False
|
||||
self._needsRestart = False
|
||||
self._refreshTree = False
|
||||
|
||||
logger.debug("GuiPreferences initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
|
||||
@property
|
||||
def updateTheme(self):
|
||||
return self._updateTheme
|
||||
|
||||
@property
|
||||
def updateSyntax(self):
|
||||
return self._updateSyntax
|
||||
|
||||
@property
|
||||
def needsRestart(self):
|
||||
return self._needsRestart
|
||||
|
||||
@property
|
||||
def refreshTree(self):
|
||||
return self._refreshTree
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
@@ -92,8 +117,7 @@ class GuiPreferences(PagedDialog):
|
||||
"""
|
||||
logger.debug("Saving new preferences")
|
||||
|
||||
needsRestart, refreshTree = self.tabGeneral.saveValues()
|
||||
|
||||
self.tabGeneral.saveValues()
|
||||
self.tabProjects.saveValues()
|
||||
self.tabDocs.saveValues()
|
||||
self.tabEditor.saveValues()
|
||||
@@ -101,14 +125,6 @@ class GuiPreferences(PagedDialog):
|
||||
self.tabAuto.saveValues()
|
||||
self.tabQuote.saveValues()
|
||||
|
||||
if needsRestart:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Some changes will not be applied until novelWriter has been restarted."
|
||||
), nwAlert.INFO)
|
||||
|
||||
if refreshTree:
|
||||
self.mainGui.projView.populateTree()
|
||||
|
||||
self._saveWindowSize()
|
||||
self.accept()
|
||||
|
||||
@@ -140,6 +156,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
super().__init__(parent=prefsGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.prefsGui = prefsGui
|
||||
self.mainGui = prefsGui.mainGui
|
||||
self.mainTheme = prefsGui.mainGui.mainTheme
|
||||
|
||||
@@ -166,7 +183,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
self.mainForm.addRow(
|
||||
self.tr("Main GUI language"),
|
||||
self.guiLang,
|
||||
self.tr("Requires restart.")
|
||||
self.tr("Requires restart to take effect.")
|
||||
)
|
||||
|
||||
# Select Theme
|
||||
@@ -182,23 +199,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
self.mainForm.addRow(
|
||||
self.tr("Main GUI theme"),
|
||||
self.guiTheme,
|
||||
self.tr("Requires restart.")
|
||||
)
|
||||
|
||||
# Select Icon Theme
|
||||
self.guiIcons = QComboBox()
|
||||
self.guiIcons.setMinimumWidth(minWidth)
|
||||
self.iconCache = self.mainTheme.iconCache.listThemes()
|
||||
for iconDir, iconName in self.iconCache:
|
||||
self.guiIcons.addItem(iconName, iconDir)
|
||||
iconIdx = self.guiIcons.findData(self.mainConf.guiIcons)
|
||||
if iconIdx != -1:
|
||||
self.guiIcons.setCurrentIndex(iconIdx)
|
||||
|
||||
self.mainForm.addRow(
|
||||
self.tr("Main icon theme"),
|
||||
self.guiIcons,
|
||||
self.tr("Requires restart.")
|
||||
self.tr("General colour theme and icons.")
|
||||
)
|
||||
|
||||
# Editor Theme
|
||||
@@ -228,7 +229,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
self.mainForm.addRow(
|
||||
self.tr("Font family"),
|
||||
self.guiFont,
|
||||
self.tr("Requires restart."),
|
||||
self.tr("Requires restart to take effect."),
|
||||
theButton=self.fontButton
|
||||
)
|
||||
|
||||
@@ -241,7 +242,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
self.mainForm.addRow(
|
||||
self.tr("Font size"),
|
||||
self.guiFontSize,
|
||||
self.tr("Requires restart."),
|
||||
self.tr("Requires restart to take effect."),
|
||||
theUnit=self.tr("pt")
|
||||
)
|
||||
|
||||
@@ -288,27 +289,21 @@ class GuiPreferencesGeneral(QWidget):
|
||||
"""
|
||||
guiLang = self.guiLang.currentData()
|
||||
guiTheme = self.guiTheme.currentData()
|
||||
guiIcons = self.guiIcons.currentData()
|
||||
guiSyntax = self.guiSyntax.currentData()
|
||||
guiFont = self.guiFont.text()
|
||||
guiFontSize = self.guiFontSize.value()
|
||||
emphLabels = self.emphLabels.isChecked()
|
||||
|
||||
# Check if restart is needed
|
||||
needsRestart = False
|
||||
needsRestart |= self.mainConf.guiLang != guiLang
|
||||
needsRestart |= self.mainConf.guiTheme != guiTheme
|
||||
needsRestart |= self.mainConf.guiIcons != guiIcons
|
||||
needsRestart |= self.mainConf.guiFont != guiFont
|
||||
needsRestart |= self.mainConf.guiFontSize != guiFontSize
|
||||
|
||||
# Check if refreshing project tree is needed
|
||||
refreshTree = False
|
||||
refreshTree |= self.mainConf.emphLabels != emphLabels
|
||||
# Update Flags
|
||||
self.prefsGui._updateTheme |= self.mainConf.guiTheme != guiTheme
|
||||
self.prefsGui._updateSyntax |= self.mainConf.guiSyntax != guiSyntax
|
||||
self.prefsGui._needsRestart |= self.mainConf.guiLang != guiLang
|
||||
self.prefsGui._needsRestart |= self.mainConf.guiFont != guiFont
|
||||
self.prefsGui._needsRestart |= self.mainConf.guiFontSize != guiFontSize
|
||||
self.prefsGui._refreshTree |= self.mainConf.emphLabels != emphLabels
|
||||
|
||||
self.mainConf.guiLang = guiLang
|
||||
self.mainConf.guiTheme = guiTheme
|
||||
self.mainConf.guiIcons = guiIcons
|
||||
self.mainConf.guiSyntax = guiSyntax
|
||||
self.mainConf.guiFont = guiFont
|
||||
self.mainConf.guiFontSize = guiFontSize
|
||||
@@ -319,7 +314,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
|
||||
self.mainConf.confChanged = True
|
||||
|
||||
return needsRestart, refreshTree
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
|
||||
@@ -43,7 +43,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiProjectSettings(PagedDialog):
|
||||
|
||||
def __init__(self, mainGui):
|
||||
TAB_MAIN = 0
|
||||
TAB_STATUS = 1
|
||||
TAB_IMPORT = 2
|
||||
TAB_REPLACE = 3
|
||||
|
||||
def __init__(self, mainGui, focusTab=TAB_MAIN):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiProjectSettings ...")
|
||||
@@ -83,12 +88,19 @@ class GuiProjectSettings(PagedDialog):
|
||||
self.addControls(self.buttonBox)
|
||||
|
||||
# Flags
|
||||
self.spellChanged = False
|
||||
self._spellChanged = False
|
||||
|
||||
# Focus Tab
|
||||
self._focusTab(focusTab)
|
||||
|
||||
logger.debug("GuiProjectSettings initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def spellChanged(self):
|
||||
return self._spellChanged
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
@@ -108,7 +120,7 @@ class GuiProjectSettings(PagedDialog):
|
||||
self.theProject.setProjBackup(doBackup)
|
||||
|
||||
# Remember this as updating spell dictionary can be expensive
|
||||
self.spellChanged = self.theProject.setSpellLang(spellLang)
|
||||
self._spellChanged = self.theProject.setSpellLang(spellLang)
|
||||
|
||||
if self.tabStatus.colChanged:
|
||||
newList, delList = self.tabStatus.getNewList()
|
||||
@@ -141,6 +153,19 @@ class GuiProjectSettings(PagedDialog):
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _focusTab(self, tab):
|
||||
"""Change which is the focused tab.
|
||||
"""
|
||||
if tab == self.TAB_MAIN:
|
||||
self.setCurrentWidget(self.tabMain)
|
||||
elif tab == self.TAB_STATUS:
|
||||
self.setCurrentWidget(self.tabStatus)
|
||||
elif tab == self.TAB_IMPORT:
|
||||
self.setCurrentWidget(self.tabImport)
|
||||
elif tab == self.TAB_REPLACE:
|
||||
self.setCurrentWidget(self.tabReplace)
|
||||
return
|
||||
|
||||
def _saveGuiSettings(self):
|
||||
"""Save GUI settings.
|
||||
"""
|
||||
|
||||
@@ -112,9 +112,10 @@ class nwDocInsert(Enum):
|
||||
QUOTE_RS = 2
|
||||
QUOTE_LD = 3
|
||||
QUOTE_RD = 4
|
||||
NEW_PAGE = 5
|
||||
VSPACE_S = 6
|
||||
VSPACE_M = 7
|
||||
SYNOPSIS = 5
|
||||
NEW_PAGE = 6
|
||||
VSPACE_S = 7
|
||||
VSPACE_M = 8
|
||||
|
||||
# END Enum nwDocInsert
|
||||
|
||||
|
||||
@@ -138,24 +138,20 @@ class GuiDocEditor(QTextEdit):
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
|
||||
# Custom Shortcuts
|
||||
QShortcut(
|
||||
QKeySequence("Ctrl+."),
|
||||
self,
|
||||
context=Qt.WidgetShortcut,
|
||||
activated=self._openSpellContext
|
||||
)
|
||||
QShortcut(
|
||||
Qt.Key_Return | Qt.ControlModifier,
|
||||
self,
|
||||
context=Qt.WidgetShortcut,
|
||||
activated=self._followTag
|
||||
)
|
||||
QShortcut(
|
||||
Qt.Key_Enter | Qt.ControlModifier,
|
||||
self,
|
||||
context=Qt.WidgetShortcut,
|
||||
activated=self._followTag
|
||||
)
|
||||
self.keyContext = QShortcut(self)
|
||||
self.keyContext.setKey("Ctrl+.")
|
||||
self.keyContext.setContext(Qt.WidgetShortcut)
|
||||
self.keyContext.activated.connect(self._openSpellContext)
|
||||
|
||||
self.followTag1 = QShortcut(self)
|
||||
self.followTag1.setKey(Qt.Key_Return | Qt.ControlModifier)
|
||||
self.followTag1.setContext(Qt.WidgetShortcut)
|
||||
self.followTag1.activated.connect(self._followTag)
|
||||
|
||||
self.followTag2 = QShortcut(self)
|
||||
self.followTag2.setKey(Qt.Key_Enter | Qt.ControlModifier)
|
||||
self.followTag2.setContext(Qt.WidgetShortcut)
|
||||
self.followTag2.activated.connect(self._followTag)
|
||||
|
||||
# Set Up Document Word Counter
|
||||
self.wcTimerDoc = QTimer()
|
||||
@@ -177,6 +173,7 @@ class GuiDocEditor(QTextEdit):
|
||||
self.wCounterSel.signals.countsReady.connect(self._updateSelCounts)
|
||||
|
||||
# Finalise
|
||||
self.updateSyntaxColours()
|
||||
self.initEditor()
|
||||
|
||||
logger.debug("GuiDocEditor initialisation complete")
|
||||
@@ -210,6 +207,35 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
return True
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements
|
||||
"""
|
||||
self.docSearch.updateTheme()
|
||||
self.docHeader.updateTheme()
|
||||
self.docFooter.updateTheme()
|
||||
return
|
||||
|
||||
def updateSyntaxColours(self):
|
||||
"""Update the syntax highlighting theme.
|
||||
"""
|
||||
mainPalette = self.palette()
|
||||
mainPalette.setColor(QPalette.Window, QColor(*self.mainTheme.colBack))
|
||||
mainPalette.setColor(QPalette.Base, QColor(*self.mainTheme.colBack))
|
||||
mainPalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText))
|
||||
self.setPalette(mainPalette)
|
||||
|
||||
docPalette = self.viewport().palette()
|
||||
docPalette.setColor(QPalette.Base, QColor(*self.mainTheme.colBack))
|
||||
docPalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText))
|
||||
self.viewport().setPalette(docPalette)
|
||||
|
||||
self.docHeader.matchColours()
|
||||
self.docFooter.matchColours()
|
||||
|
||||
self.highLight.initHighlighter()
|
||||
|
||||
return
|
||||
|
||||
def initEditor(self):
|
||||
"""Initialise or re-initialise the editor with the user's
|
||||
settings. This function is both called when the editor is
|
||||
@@ -256,21 +282,6 @@ class GuiDocEditor(QTextEdit):
|
||||
theFont.setPointSize(self.mainConf.textSize)
|
||||
self.setFont(theFont)
|
||||
|
||||
# Set the widget colours to match syntax theme
|
||||
mainPalette = self.palette()
|
||||
mainPalette.setColor(QPalette.Window, QColor(*self.mainTheme.colBack))
|
||||
mainPalette.setColor(QPalette.Base, QColor(*self.mainTheme.colBack))
|
||||
mainPalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText))
|
||||
self.setPalette(mainPalette)
|
||||
|
||||
docPalette = self.viewport().palette()
|
||||
docPalette.setColor(QPalette.Base, QColor(*self.mainTheme.colBack))
|
||||
docPalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText))
|
||||
self.viewport().setPalette(docPalette)
|
||||
|
||||
self.docHeader.matchColours()
|
||||
self.docFooter.matchColours()
|
||||
|
||||
# Set default text margins
|
||||
# Due to cursor visibility, a part of the margin must be
|
||||
# allocated to the document itself. See issue #1112.
|
||||
@@ -305,9 +316,6 @@ class GuiDocEditor(QTextEdit):
|
||||
# Refresh the tab stops
|
||||
self.setTabStopDistance(self.mainConf.getTabWidth())
|
||||
|
||||
# Initialise the syntax highlighter
|
||||
self.highLight.initHighlighter()
|
||||
|
||||
# Configure word count timer
|
||||
self.wcInterval = self.mainConf.wordCountTimer
|
||||
self.wcTimerDoc.setInterval(int(self.wcInterval*1000))
|
||||
@@ -869,6 +877,7 @@ class GuiDocEditor(QTextEdit):
|
||||
return False
|
||||
|
||||
newBlock = False
|
||||
goAfter = False
|
||||
|
||||
if isinstance(theInsert, str):
|
||||
theText = theInsert
|
||||
@@ -881,22 +890,29 @@ class GuiDocEditor(QTextEdit):
|
||||
theText = self._typDQOpen
|
||||
elif theInsert == nwDocInsert.QUOTE_RD:
|
||||
theText = self._typDQClose
|
||||
elif theInsert == nwDocInsert.SYNOPSIS:
|
||||
theText = "% Synopsis: "
|
||||
newBlock = True
|
||||
goAfter = True
|
||||
elif theInsert == nwDocInsert.NEW_PAGE:
|
||||
theText = "[NEW PAGE]"
|
||||
newBlock = True
|
||||
goAfter = False
|
||||
elif theInsert == nwDocInsert.VSPACE_S:
|
||||
theText = "[VSPACE]"
|
||||
newBlock = True
|
||||
goAfter = False
|
||||
elif theInsert == nwDocInsert.VSPACE_M:
|
||||
theText = "[VSPACE:2]"
|
||||
newBlock = True
|
||||
goAfter = False
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
if newBlock:
|
||||
self.insertNewBlock(theText, defaultAfter=False)
|
||||
self.insertNewBlock(theText, defaultAfter=goAfter)
|
||||
else:
|
||||
theCursor = self.textCursor()
|
||||
theCursor.beginEditBlock()
|
||||
@@ -1162,6 +1178,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
posCursor = self.cursorForPosition(thePos)
|
||||
spellCheck = self._spellCheck
|
||||
theWord = ""
|
||||
|
||||
if posCursor.block().text().startswith("@"):
|
||||
spellCheck = False
|
||||
@@ -1430,6 +1447,7 @@ class GuiDocEditor(QTextEdit):
|
||||
origB = theCursor.selectionEnd()
|
||||
else:
|
||||
origA = theCursor.position()
|
||||
origB = theCursor.position()
|
||||
|
||||
findOpt = QTextDocument.FindFlag(0)
|
||||
if self.docSearch.isCaseSense:
|
||||
@@ -1699,12 +1717,8 @@ class GuiDocEditor(QTextEdit):
|
||||
logger.debug("Invalid block selected for action '%s'", str(docAction))
|
||||
return False
|
||||
|
||||
theText = theBlock.text()
|
||||
if len(theText.strip()) == 0:
|
||||
logger.debug("Empty block selected for action '%s'", str(docAction))
|
||||
return False
|
||||
|
||||
# Remove existing format first, if any
|
||||
theText = theBlock.text()
|
||||
if theText.startswith("@"):
|
||||
logger.error("Cannot apply block format to keyword/value line")
|
||||
return False
|
||||
@@ -1874,10 +1888,10 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
def _followTag(self, theCursor=None, loadTag=True):
|
||||
"""Activated by Ctrl+Enter. Checks that we're in a block
|
||||
starting with '@'. We then find the word under the cursor and
|
||||
check that it is after the ':'. If all this is fine, we have a
|
||||
tag and can tell the document viewer to try and find and load
|
||||
the file where the tag is defined.
|
||||
starting with '@'. We then find the tag under the cursor and
|
||||
check that it is not the tag itself. If all this is fine, we
|
||||
have a tag and can tell the document viewer to try and find and
|
||||
load the file where the tag is defined.
|
||||
"""
|
||||
if theCursor is None:
|
||||
theCursor = self.textCursor()
|
||||
@@ -1890,18 +1904,29 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
if theText.startswith("@"):
|
||||
|
||||
theCursor.select(QTextCursor.WordUnderCursor)
|
||||
theWord = theCursor.selectedText()
|
||||
cPos = theText.find(":")
|
||||
wPos = theCursor.selectionStart() - theBlock.position()
|
||||
if wPos <= cPos:
|
||||
isGood, tBits, tPos = self.theProject.index.scanThis(theText)
|
||||
if not isGood:
|
||||
return False
|
||||
|
||||
theTag = ""
|
||||
cPos = theCursor.selectionStart() - theBlock.position()
|
||||
for sTag, sPos in zip(reversed(tBits), reversed(tPos)):
|
||||
if cPos >= sPos:
|
||||
# The cursor is between the start of two tags
|
||||
if cPos <= sPos + len(sTag):
|
||||
# The cursor is inside or at the edge of the tag
|
||||
theTag = sTag
|
||||
break
|
||||
|
||||
if not theTag or theTag.startswith("@"):
|
||||
# The keyword cannot be looked up, so we ignore that
|
||||
return False
|
||||
|
||||
if loadTag:
|
||||
logger.debug("Attempting to follow tag '%s'", theWord)
|
||||
self.loadDocumentTagRequest.emit(theWord, nwDocMode.VIEW)
|
||||
logger.debug("Attempting to follow tag '%s'", theTag)
|
||||
self.loadDocumentTagRequest.emit(theTag, nwDocMode.VIEW)
|
||||
else:
|
||||
logger.debug("Potential tag '%s'", theWord)
|
||||
logger.debug("Potential tag '%s'", theTag)
|
||||
|
||||
return True
|
||||
|
||||
@@ -2242,7 +2267,6 @@ class GuiDocEditSearch(QFrame):
|
||||
self.searchOpt.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.searchOpt.setIconSize(QSize(tPx, tPx))
|
||||
self.searchOpt.setContentsMargins(0, 0, 0, 0)
|
||||
self.searchOpt.setStyleSheet("QToolBar {padding: 0;}")
|
||||
|
||||
self.searchLabel = QLabel(self.tr("Search"))
|
||||
self.searchLabel.setFont(self.boxFont)
|
||||
@@ -2253,35 +2277,30 @@ class GuiDocEditSearch(QFrame):
|
||||
self.resultLabel.setMinimumWidth(self.mainTheme.getTextWidth("?/?", self.boxFont))
|
||||
|
||||
self.toggleCase = QAction(self.tr("Case Sensitive"), self)
|
||||
self.toggleCase.setIcon(self.mainTheme.getIcon("search_case"))
|
||||
self.toggleCase.setCheckable(True)
|
||||
self.toggleCase.setChecked(self.isCaseSense)
|
||||
self.toggleCase.toggled.connect(self._doToggleCase)
|
||||
self.searchOpt.addAction(self.toggleCase)
|
||||
|
||||
self.toggleWord = QAction(self.tr("Whole Words Only"), self)
|
||||
self.toggleWord.setIcon(self.mainTheme.getIcon("search_word"))
|
||||
self.toggleWord.setCheckable(True)
|
||||
self.toggleWord.setChecked(self.isWholeWord)
|
||||
self.toggleWord.toggled.connect(self._doToggleWord)
|
||||
self.searchOpt.addAction(self.toggleWord)
|
||||
|
||||
self.toggleRegEx = QAction(self.tr("RegEx Mode"), self)
|
||||
self.toggleRegEx.setIcon(self.mainTheme.getIcon("search_regex"))
|
||||
self.toggleRegEx.setCheckable(True)
|
||||
self.toggleRegEx.setChecked(self.isRegEx)
|
||||
self.toggleRegEx.toggled.connect(self._doToggleRegEx)
|
||||
self.searchOpt.addAction(self.toggleRegEx)
|
||||
|
||||
self.toggleLoop = QAction(self.tr("Loop Search"), self)
|
||||
self.toggleLoop.setIcon(self.mainTheme.getIcon("search_loop"))
|
||||
self.toggleLoop.setCheckable(True)
|
||||
self.toggleLoop.setChecked(self.doLoop)
|
||||
self.toggleLoop.toggled.connect(self._doToggleLoop)
|
||||
self.searchOpt.addAction(self.toggleLoop)
|
||||
|
||||
self.toggleProject = QAction(self.tr("Search Next File"), self)
|
||||
self.toggleProject.setIcon(self.mainTheme.getIcon("search_project"))
|
||||
self.toggleProject.setCheckable(True)
|
||||
self.toggleProject.setChecked(self.doNextFile)
|
||||
self.toggleProject.toggled.connect(self._doToggleProject)
|
||||
@@ -2290,7 +2309,6 @@ class GuiDocEditSearch(QFrame):
|
||||
self.searchOpt.addSeparator()
|
||||
|
||||
self.toggleMatchCap = QAction(self.tr("Preserve Case"), self)
|
||||
self.toggleMatchCap.setIcon(self.mainTheme.getIcon("search_preserve"))
|
||||
self.toggleMatchCap.setCheckable(True)
|
||||
self.toggleMatchCap.setChecked(self.doMatchCap)
|
||||
self.toggleMatchCap.toggled.connect(self._doToggleMatchCap)
|
||||
@@ -2299,7 +2317,6 @@ class GuiDocEditSearch(QFrame):
|
||||
self.searchOpt.addSeparator()
|
||||
|
||||
self.cancelSearch = QAction(self.tr("Close Search"), self)
|
||||
self.cancelSearch.setIcon(self.mainTheme.getIcon("search_cancel"))
|
||||
self.cancelSearch.triggered.connect(self._doClose)
|
||||
self.searchOpt.addAction(self.cancelSearch)
|
||||
|
||||
@@ -2311,15 +2328,14 @@ class GuiDocEditSearch(QFrame):
|
||||
self.showReplace = QToolButton(self)
|
||||
self.showReplace.setArrowType(Qt.RightArrow)
|
||||
self.showReplace.setCheckable(True)
|
||||
self.showReplace.setStyleSheet("QToolButton {border: none; background: transparent;}")
|
||||
self.showReplace.toggled.connect(self._doToggleReplace)
|
||||
|
||||
self.searchButton = QPushButton(self.mainTheme.getIcon("search"), "")
|
||||
self.searchButton = QPushButton("")
|
||||
self.searchButton.setFixedSize(QSize(bPx, bPx))
|
||||
self.searchButton.setToolTip(self.tr("Find in current document"))
|
||||
self.searchButton.clicked.connect(self._doSearch)
|
||||
|
||||
self.replaceButton = QPushButton(self.mainTheme.getIcon("search_replace"), "")
|
||||
self.replaceButton = QPushButton("")
|
||||
self.replaceButton.setFixedSize(QSize(bPx, bPx))
|
||||
self.replaceButton.setToolTip(self.tr("Find and replace in current document"))
|
||||
self.replaceButton.clicked.connect(self._doReplace)
|
||||
@@ -2349,6 +2365,35 @@ class GuiDocEditSearch(QFrame):
|
||||
self.replaceButton.setVisible(False)
|
||||
self.adjustSize()
|
||||
|
||||
self.updateTheme()
|
||||
|
||||
logger.debug("GuiDocEditSearch initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
qPalette = qApp.palette()
|
||||
self.setPalette(qPalette)
|
||||
self.searchBox.setPalette(qPalette)
|
||||
self.replaceBox.setPalette(qPalette)
|
||||
|
||||
# Set icons
|
||||
self.toggleCase.setIcon(self.mainTheme.getIcon("search_case"))
|
||||
self.toggleWord.setIcon(self.mainTheme.getIcon("search_word"))
|
||||
self.toggleRegEx.setIcon(self.mainTheme.getIcon("search_regex"))
|
||||
self.toggleLoop.setIcon(self.mainTheme.getIcon("search_loop"))
|
||||
self.toggleProject.setIcon(self.mainTheme.getIcon("search_project"))
|
||||
self.toggleMatchCap.setIcon(self.mainTheme.getIcon("search_preserve"))
|
||||
self.cancelSearch.setIcon(self.mainTheme.getIcon("search_cancel"))
|
||||
self.searchButton.setIcon(self.mainTheme.getIcon("search"))
|
||||
self.replaceButton.setIcon(self.mainTheme.getIcon("search_replace"))
|
||||
|
||||
# Set stylesheets
|
||||
self.searchOpt.setStyleSheet("QToolBar {padding: 0;}")
|
||||
self.showReplace.setStyleSheet("QToolButton {border: none; background: transparent;}")
|
||||
|
||||
# Construct Box Colours
|
||||
qPalette = self.searchBox.palette()
|
||||
baseCol = qPalette.base().color()
|
||||
@@ -2367,8 +2412,6 @@ class GuiDocEditSearch(QFrame):
|
||||
False: errCol
|
||||
}
|
||||
|
||||
logger.debug("GuiDocEditSearch initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
def closeSearch(self):
|
||||
@@ -2394,10 +2437,10 @@ class GuiDocEditSearch(QFrame):
|
||||
"""
|
||||
if self.replaceBox.isVisible():
|
||||
if self.searchBox.hasFocus():
|
||||
self.replaceBox.setFocus(True)
|
||||
self.replaceBox.setFocus()
|
||||
return True
|
||||
elif self.replaceBox.hasFocus():
|
||||
self.searchBox.setFocus(True)
|
||||
self.searchBox.setFocus()
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -2487,12 +2530,14 @@ class GuiDocEditSearch(QFrame):
|
||||
# Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _doClose(self):
|
||||
"""Hide the search/replace bar.
|
||||
"""
|
||||
self.closeSearch()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doSearch(self):
|
||||
"""Call the search action function for the document editor.
|
||||
"""
|
||||
@@ -2503,12 +2548,14 @@ class GuiDocEditSearch(QFrame):
|
||||
self.docEditor.findNext()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doReplace(self):
|
||||
"""Call the replace action function for the document editor.
|
||||
"""
|
||||
self.docEditor.replaceNext()
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleReplace(self, theState):
|
||||
"""Toggle the show/hide of the replace box.
|
||||
"""
|
||||
@@ -2523,36 +2570,42 @@ class GuiDocEditSearch(QFrame):
|
||||
self.docEditor.updateDocMargins()
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleCase(self, theState):
|
||||
"""Enable/disable case sensitive mode.
|
||||
"""
|
||||
self.isCaseSense = theState
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleWord(self, theState):
|
||||
"""Enable/disable whole word search mode.
|
||||
"""
|
||||
self.isWholeWord = theState
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleRegEx(self, theState):
|
||||
"""Enable/disable regular expression search mode.
|
||||
"""
|
||||
self.isRegEx = theState
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleLoop(self, theState):
|
||||
"""Enable/disable looping the search.
|
||||
"""
|
||||
self.doLoop = theState
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleProject(self, theState):
|
||||
"""Enable/disable continuing search in next project file.
|
||||
"""
|
||||
self.doNextFile = theState
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleMatchCap(self, theState):
|
||||
"""Enable/disable preserving capitalisation when replacing.
|
||||
"""
|
||||
@@ -2615,51 +2668,38 @@ class GuiDocEditHeader(QWidget):
|
||||
lblFont.setPointSizeF(0.9*self.mainTheme.fontPointSize)
|
||||
self.theTitle.setFont(lblFont)
|
||||
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*self.mainTheme.colText)
|
||||
|
||||
# Buttons
|
||||
self.editButton = QToolButton(self)
|
||||
self.editButton.setIcon(self.mainTheme.getIcon("edit"))
|
||||
self.editButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.editButton.setIconSize(QSize(fPx, fPx))
|
||||
self.editButton.setFixedSize(fPx, fPx)
|
||||
self.editButton.setStyleSheet(buttonStyle)
|
||||
self.editButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.editButton.setVisible(False)
|
||||
self.editButton.setToolTip(self.tr("Edit document label"))
|
||||
self.editButton.clicked.connect(self._editDocument)
|
||||
|
||||
self.searchButton = QToolButton(self)
|
||||
self.searchButton.setIcon(self.mainTheme.getIcon("search"))
|
||||
self.searchButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.searchButton.setIconSize(QSize(fPx, fPx))
|
||||
self.searchButton.setFixedSize(fPx, fPx)
|
||||
self.searchButton.setStyleSheet(buttonStyle)
|
||||
self.searchButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.searchButton.setVisible(False)
|
||||
self.searchButton.setToolTip(self.tr("Search document"))
|
||||
self.searchButton.clicked.connect(self._searchDocument)
|
||||
|
||||
self.minmaxButton = QToolButton(self)
|
||||
self.minmaxButton.setIcon(self.mainTheme.getIcon("maximise"))
|
||||
self.minmaxButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.minmaxButton.setIconSize(QSize(fPx, fPx))
|
||||
self.minmaxButton.setFixedSize(fPx, fPx)
|
||||
self.minmaxButton.setStyleSheet(buttonStyle)
|
||||
self.minmaxButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.minmaxButton.setVisible(False)
|
||||
self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode"))
|
||||
self.minmaxButton.clicked.connect(self._minmaxDocument)
|
||||
|
||||
self.closeButton = QToolButton(self)
|
||||
self.closeButton.setIcon(self.mainTheme.getIcon("close"))
|
||||
self.closeButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.closeButton.setIconSize(QSize(fPx, fPx))
|
||||
self.closeButton.setFixedSize(fPx, fPx)
|
||||
self.closeButton.setStyleSheet(buttonStyle)
|
||||
self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.closeButton.setVisible(False)
|
||||
self.closeButton.setToolTip(self.tr("Close the document"))
|
||||
@@ -2682,8 +2722,7 @@ class GuiDocEditHeader(QWidget):
|
||||
self.outerBox.setContentsMargins(cM, cM, cM, cM)
|
||||
self.setMinimumHeight(fPx + 2*cM)
|
||||
|
||||
# Fix the Colours
|
||||
self.matchColours()
|
||||
self.updateTheme()
|
||||
|
||||
logger.debug("GuiDocEditHeader initialisation complete")
|
||||
|
||||
@@ -2693,6 +2732,28 @@ class GuiDocEditHeader(QWidget):
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
self.editButton.setIcon(self.mainTheme.getIcon("edit"))
|
||||
self.searchButton.setIcon(self.mainTheme.getIcon("search"))
|
||||
self.minmaxButton.setIcon(self.mainTheme.getIcon("maximise"))
|
||||
self.closeButton.setIcon(self.mainTheme.getIcon("close"))
|
||||
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*self.mainTheme.colText)
|
||||
|
||||
self.editButton.setStyleSheet(buttonStyle)
|
||||
self.searchButton.setStyleSheet(buttonStyle)
|
||||
self.minmaxButton.setStyleSheet(buttonStyle)
|
||||
self.closeButton.setStyleSheet(buttonStyle)
|
||||
|
||||
self.matchColours()
|
||||
|
||||
return
|
||||
|
||||
def matchColours(self):
|
||||
"""Update the colours of the widget to match those of the syntax
|
||||
theme rather than the main GUI.
|
||||
@@ -2757,18 +2818,21 @@ class GuiDocEditHeader(QWidget):
|
||||
# Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _editDocument(self):
|
||||
"""Open the edit item dialog from the main GUI.
|
||||
"""
|
||||
self.mainGui.editItemLabel(self._docHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _searchDocument(self):
|
||||
"""Toggle the visibility of the search box.
|
||||
"""
|
||||
self.docEditor.toggleSearch()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _closeDocument(self):
|
||||
"""Trigger the close editor on the main window.
|
||||
"""
|
||||
@@ -2779,6 +2843,7 @@ class GuiDocEditHeader(QWidget):
|
||||
self.minmaxButton.setVisible(False)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _minmaxDocument(self):
|
||||
"""Switch on or off Focus Mode.
|
||||
"""
|
||||
@@ -2851,7 +2916,6 @@ class GuiDocEditFooter(QWidget):
|
||||
|
||||
# Lines
|
||||
self.linesIcon = QLabel("")
|
||||
self.linesIcon.setPixmap(self.mainTheme.getPixmap("status_lines", (self.sPx, self.sPx)))
|
||||
self.linesIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.linesIcon.setFixedHeight(self.sPx)
|
||||
self.linesIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
@@ -2867,7 +2931,6 @@ class GuiDocEditFooter(QWidget):
|
||||
|
||||
# Words
|
||||
self.wordsIcon = QLabel("")
|
||||
self.wordsIcon.setPixmap(self.mainTheme.getPixmap("status_stats", (self.sPx, self.sPx)))
|
||||
self.wordsIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.wordsIcon.setFixedHeight(self.sPx)
|
||||
self.wordsIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
@@ -2902,7 +2965,7 @@ class GuiDocEditFooter(QWidget):
|
||||
self.setMinimumHeight(fPx + 2*cM)
|
||||
|
||||
# Fix the Colours
|
||||
self.matchColours()
|
||||
self.updateTheme()
|
||||
self.updateLineCount()
|
||||
self.updateCounts()
|
||||
|
||||
@@ -2914,6 +2977,16 @@ class GuiDocEditFooter(QWidget):
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
self.linesIcon.setPixmap(self.mainTheme.getPixmap("status_lines", (self.sPx, self.sPx)))
|
||||
self.wordsIcon.setPixmap(self.mainTheme.getPixmap("status_stats", (self.sPx, self.sPx)))
|
||||
|
||||
self.matchColours()
|
||||
|
||||
return
|
||||
|
||||
def matchColours(self):
|
||||
"""Update the colours of the widget to match those of the syntax
|
||||
theme rather than the main GUI.
|
||||
|
||||
@@ -102,6 +102,13 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||
return True
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
self.docHeader.updateTheme()
|
||||
self.docFooter.updateTheme()
|
||||
return
|
||||
|
||||
def initViewer(self):
|
||||
"""Set editor settings from main config.
|
||||
"""
|
||||
@@ -735,51 +742,38 @@ class GuiDocViewHeader(QWidget):
|
||||
lblFont.setPointSizeF(0.9*self.mainTheme.fontPointSize)
|
||||
self.theTitle.setFont(lblFont)
|
||||
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*self.mainTheme.colText)
|
||||
|
||||
# Buttons
|
||||
self.backButton = QToolButton(self)
|
||||
self.backButton.setIcon(self.mainTheme.getIcon("backward"))
|
||||
self.backButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.backButton.setIconSize(QSize(fPx, fPx))
|
||||
self.backButton.setFixedSize(fPx, fPx)
|
||||
self.backButton.setStyleSheet(buttonStyle)
|
||||
self.backButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.backButton.setVisible(False)
|
||||
self.backButton.setToolTip(self.tr("Go backward"))
|
||||
self.backButton.clicked.connect(self.docViewer.navBackward)
|
||||
|
||||
self.forwardButton = QToolButton(self)
|
||||
self.forwardButton.setIcon(self.mainTheme.getIcon("forward"))
|
||||
self.forwardButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.forwardButton.setIconSize(QSize(fPx, fPx))
|
||||
self.forwardButton.setFixedSize(fPx, fPx)
|
||||
self.forwardButton.setStyleSheet(buttonStyle)
|
||||
self.forwardButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.forwardButton.setVisible(False)
|
||||
self.forwardButton.setToolTip(self.tr("Go forward"))
|
||||
self.forwardButton.clicked.connect(self.docViewer.navForward)
|
||||
|
||||
self.refreshButton = QToolButton(self)
|
||||
self.refreshButton.setIcon(self.mainTheme.getIcon("refresh"))
|
||||
self.refreshButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.refreshButton.setIconSize(QSize(fPx, fPx))
|
||||
self.refreshButton.setFixedSize(fPx, fPx)
|
||||
self.refreshButton.setStyleSheet(buttonStyle)
|
||||
self.refreshButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.refreshButton.setVisible(False)
|
||||
self.refreshButton.setToolTip(self.tr("Reload the document"))
|
||||
self.refreshButton.clicked.connect(self._refreshDocument)
|
||||
|
||||
self.closeButton = QToolButton(self)
|
||||
self.closeButton.setIcon(self.mainTheme.getIcon("close"))
|
||||
self.closeButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.closeButton.setIconSize(QSize(fPx, fPx))
|
||||
self.closeButton.setFixedSize(fPx, fPx)
|
||||
self.closeButton.setStyleSheet(buttonStyle)
|
||||
self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.closeButton.setVisible(False)
|
||||
self.closeButton.setToolTip(self.tr("Close the document"))
|
||||
@@ -803,7 +797,7 @@ class GuiDocViewHeader(QWidget):
|
||||
self.setMinimumHeight(fPx + 2*cM)
|
||||
|
||||
# Fix the Colours
|
||||
self.matchColours()
|
||||
self.updateTheme()
|
||||
|
||||
logger.debug("GuiDocViewHeader initialisation complete")
|
||||
|
||||
@@ -813,6 +807,28 @@ class GuiDocViewHeader(QWidget):
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
self.backButton.setIcon(self.mainTheme.getIcon("backward"))
|
||||
self.forwardButton.setIcon(self.mainTheme.getIcon("forward"))
|
||||
self.refreshButton.setIcon(self.mainTheme.getIcon("refresh"))
|
||||
self.closeButton.setIcon(self.mainTheme.getIcon("close"))
|
||||
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*self.mainTheme.colText)
|
||||
|
||||
self.backButton.setStyleSheet(buttonStyle)
|
||||
self.forwardButton.setStyleSheet(buttonStyle)
|
||||
self.refreshButton.setStyleSheet(buttonStyle)
|
||||
self.closeButton.setStyleSheet(buttonStyle)
|
||||
|
||||
self.matchColours()
|
||||
|
||||
return
|
||||
|
||||
def matchColours(self):
|
||||
"""Update the colours of the widget to match those of the syntax
|
||||
theme rather than the main GUI.
|
||||
@@ -873,12 +889,14 @@ class GuiDocViewHeader(QWidget):
|
||||
# Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _closeDocument(self):
|
||||
"""Trigger the close editor/viewer on the main window.
|
||||
"""
|
||||
self.mainGui.closeDocViewer()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _refreshDocument(self):
|
||||
"""Reload the content of the document.
|
||||
"""
|
||||
@@ -926,33 +944,13 @@ class GuiDocViewFooter(QWidget):
|
||||
bSp = self.mainConf.pxInt(2)
|
||||
hSp = self.mainConf.pxInt(8)
|
||||
|
||||
# Icons
|
||||
stickyOn = self.mainTheme.getPixmap("sticky-on", (fPx, fPx))
|
||||
stickyOff = self.mainTheme.getPixmap("sticky-off", (fPx, fPx))
|
||||
stickyIcon = QIcon()
|
||||
stickyIcon.addPixmap(stickyOn, QIcon.Normal, QIcon.On)
|
||||
stickyIcon.addPixmap(stickyOff, QIcon.Normal, QIcon.Off)
|
||||
|
||||
bulletOn = self.mainTheme.getPixmap("bullet-on", (fPx, fPx))
|
||||
bulletOff = self.mainTheme.getPixmap("bullet-off", (fPx, fPx))
|
||||
bulletIcon = QIcon()
|
||||
bulletIcon.addPixmap(bulletOn, QIcon.Normal, QIcon.On)
|
||||
bulletIcon.addPixmap(bulletOff, QIcon.Normal, QIcon.Off)
|
||||
|
||||
# Main Widget Settings
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setAutoFillBackground(True)
|
||||
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*self.mainTheme.colText)
|
||||
|
||||
# Show/Hide Details
|
||||
self.showHide = QToolButton(self)
|
||||
self.showHide.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.showHide.setStyleSheet(buttonStyle)
|
||||
self.showHide.setIcon(self.mainTheme.getIcon("reference"))
|
||||
self.showHide.setIconSize(QSize(fPx, fPx))
|
||||
self.showHide.setFixedSize(QSize(fPx, fPx))
|
||||
self.showHide.clicked.connect(self._doShowHide)
|
||||
@@ -962,8 +960,6 @@ class GuiDocViewFooter(QWidget):
|
||||
self.stickyRefs = QToolButton(self)
|
||||
self.stickyRefs.setCheckable(True)
|
||||
self.stickyRefs.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.stickyRefs.setStyleSheet(buttonStyle)
|
||||
self.stickyRefs.setIcon(stickyIcon)
|
||||
self.stickyRefs.setIconSize(QSize(fPx, fPx))
|
||||
self.stickyRefs.setFixedSize(QSize(fPx, fPx))
|
||||
self.stickyRefs.toggled.connect(self._doToggleSticky)
|
||||
@@ -976,8 +972,6 @@ class GuiDocViewFooter(QWidget):
|
||||
self.showComments.setCheckable(True)
|
||||
self.showComments.setChecked(self.mainConf.viewComments)
|
||||
self.showComments.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.showComments.setStyleSheet(buttonStyle)
|
||||
self.showComments.setIcon(bulletIcon)
|
||||
self.showComments.setIconSize(QSize(fPx, fPx))
|
||||
self.showComments.setFixedSize(QSize(fPx, fPx))
|
||||
self.showComments.toggled.connect(self._doToggleComments)
|
||||
@@ -988,8 +982,6 @@ class GuiDocViewFooter(QWidget):
|
||||
self.showSynopsis.setCheckable(True)
|
||||
self.showSynopsis.setChecked(self.mainConf.viewSynopsis)
|
||||
self.showSynopsis.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.showSynopsis.setStyleSheet(buttonStyle)
|
||||
self.showSynopsis.setIcon(bulletIcon)
|
||||
self.showSynopsis.setIconSize(QSize(fPx, fPx))
|
||||
self.showSynopsis.setFixedSize(QSize(fPx, fPx))
|
||||
self.showSynopsis.toggled.connect(self._doToggleSynopsis)
|
||||
@@ -1063,7 +1055,7 @@ class GuiDocViewFooter(QWidget):
|
||||
self.setMinimumHeight(fPx + 2*cM)
|
||||
|
||||
# Fix the Colours
|
||||
self.matchColours()
|
||||
self.updateTheme()
|
||||
|
||||
logger.debug("GuiDocViewFooter initialisation complete")
|
||||
|
||||
@@ -1073,6 +1065,46 @@ class GuiDocViewFooter(QWidget):
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
# Icons
|
||||
|
||||
fPx = int(0.9*self.mainTheme.fontPixelSize)
|
||||
|
||||
stickyOn = self.mainTheme.getPixmap("sticky-on", (fPx, fPx))
|
||||
stickyOff = self.mainTheme.getPixmap("sticky-off", (fPx, fPx))
|
||||
stickyIcon = QIcon()
|
||||
stickyIcon.addPixmap(stickyOn, QIcon.Normal, QIcon.On)
|
||||
stickyIcon.addPixmap(stickyOff, QIcon.Normal, QIcon.Off)
|
||||
|
||||
bulletOn = self.mainTheme.getPixmap("bullet-on", (fPx, fPx))
|
||||
bulletOff = self.mainTheme.getPixmap("bullet-off", (fPx, fPx))
|
||||
bulletIcon = QIcon()
|
||||
bulletIcon.addPixmap(bulletOn, QIcon.Normal, QIcon.On)
|
||||
bulletIcon.addPixmap(bulletOff, QIcon.Normal, QIcon.Off)
|
||||
|
||||
self.showHide.setIcon(self.mainTheme.getIcon("reference"))
|
||||
self.stickyRefs.setIcon(stickyIcon)
|
||||
self.showComments.setIcon(bulletIcon)
|
||||
self.showSynopsis.setIcon(bulletIcon)
|
||||
|
||||
# StyleSheets
|
||||
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*self.mainTheme.colText)
|
||||
|
||||
self.showHide.setStyleSheet(buttonStyle)
|
||||
self.stickyRefs.setStyleSheet(buttonStyle)
|
||||
self.showComments.setStyleSheet(buttonStyle)
|
||||
self.showSynopsis.setStyleSheet(buttonStyle)
|
||||
|
||||
self.matchColours()
|
||||
|
||||
return
|
||||
|
||||
def matchColours(self):
|
||||
"""Update the colours of the widget to match those of the syntax
|
||||
theme rather than the main GUI.
|
||||
@@ -1094,6 +1126,7 @@ class GuiDocViewFooter(QWidget):
|
||||
# Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _doShowHide(self):
|
||||
"""Toggle the expand/collapse of the panel.
|
||||
"""
|
||||
@@ -1101,6 +1134,7 @@ class GuiDocViewFooter(QWidget):
|
||||
self.viewMeta.setVisible(not isVisible)
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleSticky(self, theState):
|
||||
"""Toggle the sticky flag for the reference panel.
|
||||
"""
|
||||
@@ -1110,6 +1144,7 @@ class GuiDocViewFooter(QWidget):
|
||||
self.viewMeta.refreshReferences(self.docViewer.docHandle())
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleComments(self, theState):
|
||||
"""Toggle the view comment button and reload the document.
|
||||
"""
|
||||
@@ -1117,6 +1152,7 @@ class GuiDocViewFooter(QWidget):
|
||||
self.docViewer.reloadText()
|
||||
return
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _doToggleSynopsis(self, theState):
|
||||
"""Toggle the view synopsis button and reload the document.
|
||||
"""
|
||||
|
||||
@@ -53,12 +53,8 @@ class GuiItemDetails(QWidget):
|
||||
hSp = self.mainConf.pxInt(6)
|
||||
vSp = self.mainConf.pxInt(1)
|
||||
mPx = self.mainConf.pxInt(6)
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
fPt = self.mainTheme.fontPointSize
|
||||
|
||||
self._expCheck = self.mainTheme.getPixmap("check", (iPx, iPx))
|
||||
self._expCross = self.mainTheme.getPixmap("cross", (iPx, iPx))
|
||||
|
||||
fntLabel = QFont()
|
||||
fntLabel.setBold(True)
|
||||
fntLabel.setPointSizeF(0.9*fPt)
|
||||
@@ -179,6 +175,8 @@ class GuiItemDetails(QWidget):
|
||||
|
||||
self.setLayout(self.mainBox)
|
||||
|
||||
self.updateTheme()
|
||||
|
||||
# Make sure the columns for flags and counts don't resize too often
|
||||
flagWidth = self.mainTheme.getTextWidth("Mm", fntValue)
|
||||
countWidth = self.mainTheme.getTextWidth("99,999", fntValue)
|
||||
@@ -219,6 +217,15 @@ class GuiItemDetails(QWidget):
|
||||
"""
|
||||
self.updateViewBox(self._itemHandle)
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
self._expCheck = self.mainTheme.getPixmap("check", (iPx, iPx))
|
||||
self._expCross = self.mainTheme.getPixmap("cross", (iPx, iPx))
|
||||
self.updateViewBox(self._itemHandle)
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@@ -554,6 +554,15 @@ class GuiMainMenu(QMenuBar):
|
||||
)
|
||||
self.mInsKeywords.addAction(self.mInsKWItems[keyWord][0])
|
||||
|
||||
# Insert > Special Comments
|
||||
self.mInsComments = self.insertMenu.addMenu(self.tr("Special Comments"))
|
||||
|
||||
# Insert > Synopsis Comment
|
||||
self.aInsSynopsis = QAction(self.tr("Synopsis Comment"), self)
|
||||
self.aInsSynopsis.setShortcut("Ctrl+K, S")
|
||||
self.aInsSynopsis.triggered.connect(lambda: self._docInsert(nwDocInsert.SYNOPSIS))
|
||||
self.mInsComments.addAction(self.aInsSynopsis)
|
||||
|
||||
# Insert > Symbols
|
||||
self.mInsBreaks = self.insertMenu.addMenu(self.tr("Page Break and Space"))
|
||||
|
||||
|
||||
@@ -92,6 +92,14 @@ class GuiNovelView(QWidget):
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
self.novelBar.updateTheme()
|
||||
self.novelTree.updateTheme()
|
||||
self.refreshTree()
|
||||
return
|
||||
|
||||
def initSettings(self):
|
||||
"""Initialise GUI elements that depend on specific settings.
|
||||
"""
|
||||
@@ -181,16 +189,6 @@ class GuiNovelToolBar(QWidget):
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setAutoFillBackground(True)
|
||||
|
||||
qPalette = self.palette()
|
||||
qPalette.setBrush(QPalette.Window, qPalette.base())
|
||||
self.setPalette(qPalette)
|
||||
|
||||
fadeCol = qPalette.text().color()
|
||||
buttonStyle = (
|
||||
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
||||
).format(mPx, fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
||||
|
||||
# Widget Label
|
||||
self.viewLabel = QLabel("<b>%s</b>" % self.tr("Novel Outline"))
|
||||
self.viewLabel.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -199,9 +197,7 @@ class GuiNovelToolBar(QWidget):
|
||||
# Refresh Button
|
||||
self.tbRefresh = QToolButton(self)
|
||||
self.tbRefresh.setToolTip(self.tr("Refresh"))
|
||||
self.tbRefresh.setIcon(self.mainTheme.getIcon("refresh"))
|
||||
self.tbRefresh.setIconSize(QSize(iPx, iPx))
|
||||
self.tbRefresh.setStyleSheet(buttonStyle)
|
||||
self.tbRefresh.clicked.connect(self._refreshNovelTree)
|
||||
|
||||
# Novel Root Menu
|
||||
@@ -211,9 +207,7 @@ class GuiNovelToolBar(QWidget):
|
||||
|
||||
self.tbRoot = QToolButton(self)
|
||||
self.tbRoot.setToolTip(self.tr("Novel Root"))
|
||||
self.tbRoot.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL]))
|
||||
self.tbRoot.setIconSize(QSize(iPx, iPx))
|
||||
self.tbRoot.setStyleSheet(buttonStyle)
|
||||
self.tbRoot.setMenu(self.mRoot)
|
||||
self.tbRoot.setPopupMode(QToolButton.InstantPopup)
|
||||
|
||||
@@ -230,9 +224,7 @@ class GuiNovelToolBar(QWidget):
|
||||
|
||||
self.tbMore = QToolButton(self)
|
||||
self.tbMore.setToolTip(self.tr("More Options"))
|
||||
self.tbMore.setIcon(self.mainTheme.getIcon("menu"))
|
||||
self.tbMore.setIconSize(QSize(iPx, iPx))
|
||||
self.tbMore.setStyleSheet(buttonStyle)
|
||||
self.tbMore.setMenu(self.mMore)
|
||||
self.tbMore.setPopupMode(QToolButton.InstantPopup)
|
||||
|
||||
@@ -247,6 +239,8 @@ class GuiNovelToolBar(QWidget):
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
self.updateTheme()
|
||||
|
||||
logger.debug("GuiNovelToolBar initialisation complete")
|
||||
|
||||
return
|
||||
@@ -255,6 +249,31 @@ class GuiNovelToolBar(QWidget):
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
# Icons
|
||||
self.tbRefresh.setIcon(self.mainTheme.getIcon("refresh"))
|
||||
self.tbRoot.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL]))
|
||||
self.tbMore.setIcon(self.mainTheme.getIcon("menu"))
|
||||
|
||||
qPalette = self.palette()
|
||||
qPalette.setBrush(QPalette.Window, qPalette.base())
|
||||
self.setPalette(qPalette)
|
||||
|
||||
# StyleSheets
|
||||
fadeCol = qPalette.text().color()
|
||||
buttonStyle = (
|
||||
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
||||
).format(self.mainConf.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
||||
|
||||
self.tbRefresh.setStyleSheet(buttonStyle)
|
||||
self.tbRoot.setStyleSheet(buttonStyle)
|
||||
self.tbMore.setStyleSheet(buttonStyle)
|
||||
|
||||
return
|
||||
|
||||
def clearContent(self):
|
||||
"""Run clearing project tasks.
|
||||
"""
|
||||
@@ -390,7 +409,6 @@ class GuiNovelTree(QTreeWidget):
|
||||
fH2.setBold(True)
|
||||
|
||||
self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()]
|
||||
self._pMore = self.mainTheme.loadDecoration("deco_doc_more", pxH=iPx)
|
||||
|
||||
# Connect signals
|
||||
self.clicked.connect(self._treeItemClicked)
|
||||
@@ -399,6 +417,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
|
||||
# Set custom settings
|
||||
self.initSettings()
|
||||
self.updateTheme()
|
||||
|
||||
logger.debug("GuiNovelTree initialisation complete")
|
||||
|
||||
@@ -420,6 +439,13 @@ class GuiNovelTree(QTreeWidget):
|
||||
|
||||
return
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
self._pMore = self.mainTheme.loadDecoration("deco_doc_more", pxH=iPx)
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
|
||||
@@ -97,6 +97,13 @@ class GuiOutlineView(QWidget):
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
self.outlineBar.updateTheme()
|
||||
self.refreshTree()
|
||||
return
|
||||
|
||||
def initSettings(self):
|
||||
"""Initialise GUI elements that depend on specific settings.
|
||||
"""
|
||||
@@ -213,7 +220,6 @@ class GuiOutlineToolBar(QToolBar):
|
||||
self.setMovable(False)
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setStyleSheet("QToolBar {border: 0px;}")
|
||||
|
||||
stretch = QWidget(self)
|
||||
stretch.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
@@ -228,7 +234,6 @@ class GuiOutlineToolBar(QToolBar):
|
||||
|
||||
# Actions
|
||||
self.aRefresh = QAction(self.tr("Refresh"), self)
|
||||
self.aRefresh.setIcon(self.mainTheme.getIcon("refresh"))
|
||||
self.aRefresh.triggered.connect(self._refreshRequested)
|
||||
|
||||
# Column Menu
|
||||
@@ -238,7 +243,6 @@ class GuiOutlineToolBar(QToolBar):
|
||||
)
|
||||
|
||||
self.tbColumns = QToolButton(self)
|
||||
self.tbColumns.setIcon(self.mainTheme.getIcon("menu"))
|
||||
self.tbColumns.setMenu(self.mColumns)
|
||||
self.tbColumns.setPopupMode(QToolButton.InstantPopup)
|
||||
|
||||
@@ -250,6 +254,8 @@ class GuiOutlineToolBar(QToolBar):
|
||||
self.addWidget(self.tbColumns)
|
||||
self.addWidget(stretch)
|
||||
|
||||
self.updateTheme()
|
||||
|
||||
logger.debug("GuiOutlineToolBar initialisation complete")
|
||||
|
||||
return
|
||||
@@ -258,6 +264,16 @@ class GuiOutlineToolBar(QToolBar):
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
self.setStyleSheet("QToolBar {border: 0px;}")
|
||||
|
||||
self.aRefresh.setIcon(self.mainTheme.getIcon("refresh"))
|
||||
self.tbColumns.setIcon(self.mainTheme.getIcon("menu"))
|
||||
|
||||
return
|
||||
|
||||
def populateNovelList(self):
|
||||
"""Fill the novel combo box with a list of all novel folders.
|
||||
"""
|
||||
|
||||
@@ -41,8 +41,8 @@ from PyQt5.QtWidgets import (
|
||||
|
||||
from novelwriter.core import DocMerger, DocSplitter
|
||||
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||
from novelwriter.dialogs import GuiDocMerge, GuiDocSplit, GuiEditLabel
|
||||
from novelwriter.constants import nwHeaders, trConst, nwLabels
|
||||
from novelwriter.dialogs import GuiDocMerge, GuiDocSplit, GuiEditLabel, GuiProjectSettings
|
||||
from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -62,6 +62,9 @@ class GuiProjectView(QWidget):
|
||||
selectedItemChanged = pyqtSignal(str)
|
||||
openDocumentRequest = pyqtSignal(str, Enum, int, str)
|
||||
|
||||
# Requests for the main GUI
|
||||
projectSettingsRequest = pyqtSignal(int)
|
||||
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
@@ -102,9 +105,6 @@ class GuiProjectView(QWidget):
|
||||
self.keyContext.activated.connect(lambda: self.projTree.openContextOnSelected())
|
||||
|
||||
# Function Mappings
|
||||
self.revealNewTreeItem = self.projTree.revealNewTreeItem
|
||||
self.renameTreeItem = self.projTree.renameTreeItem
|
||||
self.getTreeFromHandle = self.projTree.getTreeFromHandle
|
||||
self.emptyTrash = self.projTree.emptyTrash
|
||||
self.requestDeleteItem = self.projTree.requestDeleteItem
|
||||
self.setTreeItemValues = self.projTree.setTreeItemValues
|
||||
@@ -119,6 +119,13 @@ class GuiProjectView(QWidget):
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
self.projBar.updateTheme()
|
||||
self.populateTree()
|
||||
return
|
||||
|
||||
def initSettings(self):
|
||||
"""Initialise GUI elements that depend on specific settings.
|
||||
"""
|
||||
@@ -161,6 +168,16 @@ class GuiProjectView(QWidget):
|
||||
"""
|
||||
return self.projTree.hasFocus()
|
||||
|
||||
def renameTreeItem(self, tHandle=None):
|
||||
"""External request to rename an item or the currently selected
|
||||
item. This is triggered by the global menu or keyboard shortcut.
|
||||
"""
|
||||
if tHandle is None:
|
||||
tHandle = self.projTree.getSelectedHandle()
|
||||
if tHandle:
|
||||
return self.projTree.renameTreeItem(tHandle)
|
||||
return
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
@@ -203,16 +220,6 @@ class GuiProjectToolBar(QWidget):
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setAutoFillBackground(True)
|
||||
|
||||
qPalette = self.palette()
|
||||
qPalette.setBrush(QPalette.Window, qPalette.base())
|
||||
self.setPalette(qPalette)
|
||||
|
||||
fadeCol = qPalette.text().color()
|
||||
buttonStyle = (
|
||||
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
||||
).format(mPx, fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
||||
|
||||
# Widget Label
|
||||
self.viewLabel = QLabel("<b>%s</b>" % self.tr("Project Content"))
|
||||
self.viewLabel.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -224,78 +231,56 @@ class GuiProjectToolBar(QWidget):
|
||||
self.tbQuick = QToolButton(self)
|
||||
self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links"))
|
||||
self.tbQuick.setShortcut("Ctrl+L")
|
||||
self.tbQuick.setIcon(self.mainTheme.getIcon("bookmark"))
|
||||
self.tbQuick.setIconSize(QSize(iPx, iPx))
|
||||
self.tbQuick.setStyleSheet(buttonStyle)
|
||||
self.tbQuick.setMenu(self.mQuick)
|
||||
self.tbQuick.setPopupMode(QToolButton.InstantPopup)
|
||||
|
||||
# Move Buttons
|
||||
self.tbMoveU = QToolButton(self)
|
||||
self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up"))
|
||||
self.tbMoveU.setIcon(self.mainTheme.getIcon("up"))
|
||||
self.tbMoveU.setIconSize(QSize(iPx, iPx))
|
||||
self.tbMoveU.setStyleSheet(buttonStyle)
|
||||
self.tbMoveU.clicked.connect(lambda: self.projTree.moveTreeItem(-1))
|
||||
|
||||
self.tbMoveD = QToolButton(self)
|
||||
self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down"))
|
||||
self.tbMoveD.setIcon(self.mainTheme.getIcon("down"))
|
||||
self.tbMoveD.setIconSize(QSize(iPx, iPx))
|
||||
self.tbMoveD.setStyleSheet(buttonStyle)
|
||||
self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1))
|
||||
|
||||
# Add Item Menu
|
||||
self.mAdd = QMenu()
|
||||
|
||||
self.aAddEmpty = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["document"]))
|
||||
self.aAddEmpty.setIcon(self.mainTheme.getIcon("proj_document"))
|
||||
self.aAddEmpty.triggered.connect(
|
||||
lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=0, isNote=False)
|
||||
)
|
||||
|
||||
self.aAddChap = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h2"]))
|
||||
self.aAddChap.setIcon(self.mainTheme.getIcon("proj_chapter"))
|
||||
self.aAddChap.triggered.connect(
|
||||
lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=2, isNote=False)
|
||||
)
|
||||
|
||||
self.aAddScene = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h3"]))
|
||||
self.aAddScene.setIcon(self.mainTheme.getIcon("proj_scene"))
|
||||
self.aAddScene.triggered.connect(
|
||||
lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=3, isNote=False)
|
||||
)
|
||||
|
||||
self.aAddNote = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["note"]))
|
||||
self.aAddNote.setIcon(self.mainTheme.getIcon("proj_note"))
|
||||
self.aAddNote.triggered.connect(
|
||||
lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
|
||||
)
|
||||
|
||||
self.aAddFolder = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["folder"]))
|
||||
self.aAddFolder.setIcon(self.mainTheme.getIcon("proj_folder"))
|
||||
self.aAddFolder.triggered.connect(
|
||||
lambda: self.projTree.newTreeItem(nwItemType.FOLDER)
|
||||
)
|
||||
|
||||
self.mAddRoot = self.mAdd.addMenu(trConst(nwLabels.ITEM_DESCRIPTION["root"]))
|
||||
self._addRootFolderEntry(nwItemClass.NOVEL)
|
||||
self._addRootFolderEntry(nwItemClass.ARCHIVE)
|
||||
self.mAddRoot.addSeparator()
|
||||
self._addRootFolderEntry(nwItemClass.PLOT)
|
||||
self._addRootFolderEntry(nwItemClass.CHARACTER)
|
||||
self._addRootFolderEntry(nwItemClass.WORLD)
|
||||
self._addRootFolderEntry(nwItemClass.TIMELINE)
|
||||
self._addRootFolderEntry(nwItemClass.OBJECT)
|
||||
self._addRootFolderEntry(nwItemClass.ENTITY)
|
||||
self._addRootFolderEntry(nwItemClass.CUSTOM)
|
||||
self._buildRootMenu()
|
||||
|
||||
self.tbAdd = QToolButton(self)
|
||||
self.tbAdd.setToolTip("%s [Ctrl+N]" % self.tr("Add Item"))
|
||||
self.tbAdd.setShortcut("Ctrl+N")
|
||||
self.tbAdd.setIcon(self.mainTheme.getIcon("add"))
|
||||
self.tbAdd.setIconSize(QSize(iPx, iPx))
|
||||
self.tbAdd.setStyleSheet(buttonStyle)
|
||||
self.tbAdd.setMenu(self.mAdd)
|
||||
self.tbAdd.setPopupMode(QToolButton.InstantPopup)
|
||||
|
||||
@@ -316,9 +301,7 @@ class GuiProjectToolBar(QWidget):
|
||||
|
||||
self.tbMore = QToolButton(self)
|
||||
self.tbMore.setToolTip(self.tr("More Options"))
|
||||
self.tbMore.setIcon(self.mainTheme.getIcon("menu"))
|
||||
self.tbMore.setIconSize(QSize(iPx, iPx))
|
||||
self.tbMore.setStyleSheet(buttonStyle)
|
||||
self.tbMore.setMenu(self.mMore)
|
||||
self.tbMore.setPopupMode(QToolButton.InstantPopup)
|
||||
|
||||
@@ -334,6 +317,7 @@ class GuiProjectToolBar(QWidget):
|
||||
self.outerBox.setSpacing(0)
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
self.updateTheme()
|
||||
|
||||
logger.debug("GuiProjectToolBar initialisation complete")
|
||||
|
||||
@@ -343,6 +327,41 @@ class GuiProjectToolBar(QWidget):
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
qPalette = self.palette()
|
||||
qPalette.setBrush(QPalette.Window, qPalette.base())
|
||||
self.setPalette(qPalette)
|
||||
|
||||
fadeCol = qPalette.text().color()
|
||||
buttonStyle = (
|
||||
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
||||
).format(self.mainConf.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
||||
|
||||
self.tbQuick.setStyleSheet(buttonStyle)
|
||||
self.tbMoveU.setStyleSheet(buttonStyle)
|
||||
self.tbMoveD.setStyleSheet(buttonStyle)
|
||||
self.tbAdd.setStyleSheet(buttonStyle)
|
||||
self.tbMore.setStyleSheet(buttonStyle)
|
||||
|
||||
self.tbQuick.setIcon(self.mainTheme.getIcon("bookmark"))
|
||||
self.tbMoveU.setIcon(self.mainTheme.getIcon("up"))
|
||||
self.tbMoveD.setIcon(self.mainTheme.getIcon("down"))
|
||||
self.aAddEmpty.setIcon(self.mainTheme.getIcon("proj_document"))
|
||||
self.aAddChap.setIcon(self.mainTheme.getIcon("proj_chapter"))
|
||||
self.aAddScene.setIcon(self.mainTheme.getIcon("proj_scene"))
|
||||
self.aAddNote.setIcon(self.mainTheme.getIcon("proj_note"))
|
||||
self.aAddFolder.setIcon(self.mainTheme.getIcon("proj_folder"))
|
||||
self.tbAdd.setIcon(self.mainTheme.getIcon("add"))
|
||||
self.tbMore.setIcon(self.mainTheme.getIcon("menu"))
|
||||
|
||||
self.buildQuickLinkMenu()
|
||||
self._buildRootMenu()
|
||||
|
||||
return
|
||||
|
||||
def clearContent(self):
|
||||
"""Clear dynamic content on the tool bar.
|
||||
"""
|
||||
@@ -369,24 +388,44 @@ class GuiProjectToolBar(QWidget):
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _addRootFolderEntry(self, itemClass):
|
||||
"""Add a menu entry for a root folder of a given class.
|
||||
def _buildRootMenu(self):
|
||||
"""Build the rood folder menu.
|
||||
"""
|
||||
aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass]))
|
||||
aNew.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[itemClass]))
|
||||
aNew.triggered.connect(lambda: self.projTree.newTreeItem(nwItemType.ROOT, itemClass))
|
||||
self.mAddRoot.addAction(aNew)
|
||||
def addClass(itemClass):
|
||||
aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass]))
|
||||
aNew.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[itemClass]))
|
||||
aNew.triggered.connect(lambda: self.projTree.newTreeItem(nwItemType.ROOT, itemClass))
|
||||
self.mAddRoot.addAction(aNew)
|
||||
return
|
||||
|
||||
self.mAddRoot.clear()
|
||||
addClass(nwItemClass.NOVEL)
|
||||
addClass(nwItemClass.ARCHIVE)
|
||||
self.mAddRoot.addSeparator()
|
||||
addClass(nwItemClass.PLOT)
|
||||
addClass(nwItemClass.CHARACTER)
|
||||
addClass(nwItemClass.WORLD)
|
||||
addClass(nwItemClass.TIMELINE)
|
||||
addClass(nwItemClass.OBJECT)
|
||||
addClass(nwItemClass.ENTITY)
|
||||
addClass(nwItemClass.CUSTOM)
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiProjectToolBar
|
||||
|
||||
|
||||
class GuiProjectTree(QTreeWidget):
|
||||
|
||||
C_DATA = 0
|
||||
C_NAME = 0
|
||||
C_COUNT = 1
|
||||
C_ACTIVE = 2
|
||||
C_STATUS = 3
|
||||
|
||||
D_HANDLE = Qt.UserRole
|
||||
D_WORDS = Qt.UserRole + 1
|
||||
|
||||
def __init__(self, projView):
|
||||
super().__init__(parent=projView)
|
||||
|
||||
@@ -912,10 +951,13 @@ class GuiProjectTree(QTreeWidget):
|
||||
trItem.setToolTip(self.C_STATUS, itemStatus)
|
||||
|
||||
if nwItem.isFileType():
|
||||
iconName = "check" if nwItem.isActive else "cross"
|
||||
iconName = "checked" if nwItem.isActive else "unchecked"
|
||||
toolTip = self._lblActive if nwItem.isActive else self._lblInactive
|
||||
trItem.setIcon(self.C_ACTIVE, self.mainTheme.getIcon(iconName))
|
||||
trItem.setToolTip(self.C_ACTIVE, toolTip)
|
||||
else:
|
||||
iconName = "noncheckable"
|
||||
|
||||
trItem.setIcon(self.C_ACTIVE, self.mainTheme.getIcon(iconName))
|
||||
|
||||
if self.mainConf.emphLabels and nwItem.isDocumentLayout():
|
||||
trFont = trItem.font(self.C_NAME)
|
||||
@@ -939,10 +981,10 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
if countChildren:
|
||||
for i in range(tItem.childCount()):
|
||||
newCount += int(tItem.child(i).data(self.C_COUNT, Qt.UserRole))
|
||||
newCount += int(tItem.child(i).data(self.C_DATA, self.D_WORDS))
|
||||
|
||||
tItem.setText(self.C_COUNT, f"{newCount:n}")
|
||||
tItem.setData(self.C_COUNT, Qt.UserRole, int(newCount))
|
||||
tItem.setData(self.C_DATA, self.D_WORDS, int(newCount))
|
||||
|
||||
pItem = tItem.parent()
|
||||
if pItem is None:
|
||||
@@ -951,8 +993,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
pCount = 0
|
||||
pHandle = None
|
||||
for i in range(pItem.childCount()):
|
||||
pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole))
|
||||
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
|
||||
pCount += int(pItem.child(i).data(self.C_DATA, self.D_WORDS))
|
||||
pHandle = pItem.data(self.C_DATA, self.D_HANDLE)
|
||||
|
||||
if pHandle:
|
||||
if self.theProject.tree.checkType(pHandle, nwItemType.FILE):
|
||||
@@ -1003,8 +1045,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
return False
|
||||
|
||||
dstIndex = min(max(0, dstIndex), dstItem.childCount())
|
||||
sHandle = srcItem.data(self.C_NAME, Qt.UserRole)
|
||||
dHandle = dstItem.data(self.C_NAME, Qt.UserRole)
|
||||
sHandle = srcItem.data(self.C_DATA, self.D_HANDLE)
|
||||
dHandle = dstItem.data(self.C_DATA, self.D_HANDLE)
|
||||
logger.debug("Moving item '%s' back to '%s', index %d", sHandle, dHandle, dstIndex)
|
||||
|
||||
wCount = self._getItemWordCount(sHandle)
|
||||
@@ -1028,7 +1070,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
"""
|
||||
selItem = self.selectedItems()
|
||||
if selItem:
|
||||
return selItem[0].data(self.C_NAME, Qt.UserRole)
|
||||
return selItem[0].data(self.C_DATA, self.D_HANDLE)
|
||||
|
||||
return None
|
||||
|
||||
@@ -1111,10 +1153,12 @@ class GuiProjectTree(QTreeWidget):
|
||||
open a context menu in-place.
|
||||
"""
|
||||
tItem = None
|
||||
hasChild = False
|
||||
selItem = self.itemAt(clickPos)
|
||||
if isinstance(selItem, QTreeWidgetItem):
|
||||
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
|
||||
tHandle = selItem.data(self.C_DATA, self.D_HANDLE)
|
||||
tItem = self.theProject.tree[tHandle]
|
||||
hasChild = selItem.childCount() > 0
|
||||
|
||||
if tItem is None:
|
||||
logger.debug("No item found")
|
||||
@@ -1128,9 +1172,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
trashHandle = self.theProject.tree.trashRoot()
|
||||
if tItem.itemHandle == trashHandle and trashHandle is not None:
|
||||
# The trash folder only has one option
|
||||
ctxMenu.addAction(
|
||||
self.tr("Empty Trash"), lambda: self.emptyTrash()
|
||||
)
|
||||
aEmptyTrash = ctxMenu.addAction(self.tr("Empty Trash"))
|
||||
aEmptyTrash.triggered.connect(lambda: self.emptyTrash())
|
||||
ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
|
||||
return True
|
||||
|
||||
@@ -1140,15 +1183,14 @@ class GuiProjectTree(QTreeWidget):
|
||||
isRoot = tItem.isRootType()
|
||||
isFolder = tItem.isFolderType()
|
||||
isFile = tItem.isFileType()
|
||||
hasChild = selItem.childCount() > 0
|
||||
|
||||
if isFile:
|
||||
ctxMenu.addAction(
|
||||
self.tr("Open Document"),
|
||||
aOpenDoc = ctxMenu.addAction(self.tr("Open Document"))
|
||||
aOpenDoc.triggered.connect(
|
||||
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "")
|
||||
)
|
||||
ctxMenu.addAction(
|
||||
self.tr("View Document"),
|
||||
aViewDoc = ctxMenu.addAction(self.tr("View Document"))
|
||||
aViewDoc.triggered.connect(
|
||||
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
|
||||
)
|
||||
ctxMenu.addSeparator()
|
||||
@@ -1156,29 +1198,40 @@ class GuiProjectTree(QTreeWidget):
|
||||
# Edit Item Settings
|
||||
# ==================
|
||||
|
||||
ctxMenu.addAction(
|
||||
self.tr("Change Label"), lambda: self.renameTreeItem(tHandle)
|
||||
)
|
||||
aLabel = ctxMenu.addAction(self.tr("Change Label"))
|
||||
aLabel.triggered.connect(lambda: self.renameTreeItem(tHandle))
|
||||
|
||||
if isFile:
|
||||
ctxMenu.addAction(
|
||||
self.tr("Toggle Active"), lambda: self._toggleItemActive(tHandle)
|
||||
)
|
||||
aActive = ctxMenu.addAction(self.tr("Toggle Active"))
|
||||
aActive.triggered.connect(lambda: self._toggleItemActive(tHandle))
|
||||
|
||||
checkMark = f" ({nwUnicode.U_CHECK})"
|
||||
if tItem.isNovelLike():
|
||||
mStatus = ctxMenu.addMenu(self.tr("Set Status to ..."))
|
||||
for n, (key, entry) in enumerate(self.theProject.statusItems.items()):
|
||||
aStatus = mStatus.addAction(entry["icon"], entry["name"])
|
||||
entryName = entry["name"] + (checkMark if tItem.itemStatus == key else "")
|
||||
aStatus = mStatus.addAction(entry["icon"], entryName)
|
||||
aStatus.triggered.connect(
|
||||
lambda n, key=key: self._changeItemStatus(tHandle, key)
|
||||
)
|
||||
mStatus.addSeparator()
|
||||
aManage1 = mStatus.addAction("Manage Labels ...")
|
||||
aManage1.triggered.connect(
|
||||
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_STATUS)
|
||||
)
|
||||
else:
|
||||
mImport = ctxMenu.addMenu(self.tr("Set Importance to ..."))
|
||||
for n, (key, entry) in enumerate(self.theProject.importItems.items()):
|
||||
aImport = mImport.addAction(entry["icon"], entry["name"])
|
||||
entryName = entry["name"] + (checkMark if tItem.itemImport == key else "")
|
||||
aImport = mImport.addAction(entry["icon"], entryName)
|
||||
aImport.triggered.connect(
|
||||
lambda n, key=key: self._changeItemImport(tHandle, key)
|
||||
)
|
||||
mImport.addSeparator()
|
||||
aManage2 = mImport.addAction("Manage Labels ...")
|
||||
aManage2.triggered.connect(
|
||||
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_IMPORT)
|
||||
)
|
||||
|
||||
# Transform Item
|
||||
# ==============
|
||||
@@ -1193,38 +1246,30 @@ class GuiProjectTree(QTreeWidget):
|
||||
isNoteFile = isFile and tItem.isNoteLayout()
|
||||
|
||||
if (isNoteFile or isFolder) and tItem.documentAllowed():
|
||||
mTrans.addAction(
|
||||
self.tr("Convert to {0}").format(trDoc),
|
||||
aConvert1 = mTrans.addAction(self.tr("Convert to {0}").format(trDoc))
|
||||
aConvert1.triggered.connect(
|
||||
lambda: self._changeItemLayout(tHandle, nwItemLayout.DOCUMENT)
|
||||
)
|
||||
|
||||
if isDocFile or isFolder:
|
||||
mTrans.addAction(
|
||||
self.tr("Convert to {0}").format(trNote),
|
||||
aConvert2 = mTrans.addAction(self.tr("Convert to {0}").format(trNote))
|
||||
aConvert2.triggered.connect(
|
||||
lambda: self._changeItemLayout(tHandle, nwItemLayout.NOTE)
|
||||
)
|
||||
|
||||
if hasChild and isFile:
|
||||
mTrans.addAction(
|
||||
self.tr("Merge Child Items into Self"),
|
||||
lambda: self._mergeDocuments(tHandle, False)
|
||||
)
|
||||
mTrans.addAction(
|
||||
self.tr("Merge Child Items into New"),
|
||||
lambda: self._mergeDocuments(tHandle, True)
|
||||
)
|
||||
aMerge1 = mTrans.addAction(self.tr("Merge Child Items into Self"))
|
||||
aMerge1.triggered.connect(lambda: self._mergeDocuments(tHandle, False))
|
||||
aMerge2 = mTrans.addAction(self.tr("Merge Child Items into New"))
|
||||
aMerge2.triggered.connect(lambda: self._mergeDocuments(tHandle, True))
|
||||
|
||||
if hasChild and isFolder:
|
||||
mTrans.addAction(
|
||||
self.tr("Merge Documents in Folder"),
|
||||
lambda: self._mergeDocuments(tHandle, True)
|
||||
)
|
||||
aMerge3 = mTrans.addAction(self.tr("Merge Documents in Folder"))
|
||||
aMerge3.triggered.connect(lambda: self._mergeDocuments(tHandle, True))
|
||||
|
||||
if isFile:
|
||||
mTrans.addAction(
|
||||
self.tr("Split Document by Headers"),
|
||||
lambda: self._splitDocument(tHandle)
|
||||
)
|
||||
aSplit1 = mTrans.addAction(self.tr("Split Document by Headers"))
|
||||
aSplit1.triggered.connect(lambda: self._splitDocument(tHandle))
|
||||
|
||||
# Expand/Collapse/Delete
|
||||
# ======================
|
||||
@@ -1232,23 +1277,17 @@ class GuiProjectTree(QTreeWidget):
|
||||
ctxMenu.addSeparator()
|
||||
|
||||
if hasChild:
|
||||
ctxMenu.addAction(
|
||||
self.tr("Expand All"),
|
||||
lambda: self.setExpandedFromHandle(tHandle, True)
|
||||
)
|
||||
ctxMenu.addAction(
|
||||
self.tr("Collapse All"),
|
||||
lambda: self.setExpandedFromHandle(tHandle, False)
|
||||
)
|
||||
aExpand = ctxMenu.addAction(self.tr("Expand All"))
|
||||
aExpand.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, True))
|
||||
aCollapse = ctxMenu.addAction(self.tr("Collapse All"))
|
||||
aCollapse.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, False))
|
||||
|
||||
if tItem.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
|
||||
ctxMenu.addAction(
|
||||
self.tr("Delete Permanently"), lambda: self.permanentlyDeleteItem(tHandle)
|
||||
)
|
||||
aDelete = ctxMenu.addAction(self.tr("Delete Permanently"))
|
||||
aDelete.triggered.connect(lambda: self.permanentlyDeleteItem(tHandle))
|
||||
else:
|
||||
ctxMenu.addAction(
|
||||
self.tr("Move to Trash"), lambda: self.moveItemToTrash(tHandle)
|
||||
)
|
||||
aMoveTrash = ctxMenu.addAction(self.tr("Move to Trash"))
|
||||
aMoveTrash.triggered.connect(lambda: self.moveItemToTrash(tHandle))
|
||||
|
||||
# Show Context Menu
|
||||
ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
|
||||
@@ -1276,7 +1315,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
if not isinstance(selItem, QTreeWidgetItem):
|
||||
return
|
||||
|
||||
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
|
||||
tHandle = selItem.data(self.C_DATA, self.D_HANDLE)
|
||||
tItem = self.theProject.tree[tHandle]
|
||||
if tItem is None:
|
||||
return
|
||||
@@ -1335,7 +1374,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
# Update item parent handle in the project, make sure meta data
|
||||
# is updated accordingly, and update word count
|
||||
pHandle = trItemP.data(self.C_NAME, Qt.UserRole)
|
||||
pHandle = trItemP.data(self.C_DATA, self.D_HANDLE)
|
||||
nwItemS.setParent(pHandle)
|
||||
trItemP.setExpanded(True)
|
||||
logger.debug("The parent of item '%s' has been changed to '%s'", tHandle, pHandle)
|
||||
@@ -1365,7 +1404,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
tItem = self._getTreeItem(tHandle)
|
||||
if tItem is None:
|
||||
return 0
|
||||
return int(tItem.data(self.C_COUNT, Qt.UserRole))
|
||||
return int(tItem.data(self.C_DATA, self.D_WORDS))
|
||||
|
||||
def _getTreeItem(self, tHandle):
|
||||
"""Return the QTreeWidgetItem of a given item handle.
|
||||
@@ -1573,6 +1612,9 @@ class GuiProjectTree(QTreeWidget):
|
||||
self.tr("Could not write document content."), docSplit.getError()
|
||||
], nwAlert.ERROR)
|
||||
|
||||
if splitData.get("moveToTrash", False):
|
||||
self.moveItemToTrash(tHandle, askFirst=False, flush=True)
|
||||
|
||||
self.saveTreeOrder()
|
||||
|
||||
else:
|
||||
@@ -1585,7 +1627,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
"""This is a recursive function returning all items in a tree
|
||||
starting at a given QTreeWidgetItem.
|
||||
"""
|
||||
tHandle = tItem.data(self.C_NAME, Qt.UserRole)
|
||||
tHandle = tItem.data(self.C_DATA, self.D_HANDLE)
|
||||
cCount = tItem.childCount()
|
||||
|
||||
# Update tree-related meta data
|
||||
@@ -1618,8 +1660,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
newItem.setTextAlignment(self.C_ACTIVE, Qt.AlignLeft)
|
||||
newItem.setTextAlignment(self.C_STATUS, Qt.AlignLeft)
|
||||
|
||||
newItem.setData(self.C_NAME, Qt.UserRole, tHandle)
|
||||
newItem.setData(self.C_COUNT, Qt.UserRole, 0)
|
||||
newItem.setData(self.C_DATA, self.D_HANDLE, tHandle)
|
||||
newItem.setData(self.C_DATA, self.D_WORDS, 0)
|
||||
|
||||
self._treeMap[tHandle] = newItem
|
||||
if pHandle is None:
|
||||
|
||||
@@ -66,7 +66,6 @@ class GuiMainStatus(QStatusBar):
|
||||
# The Spell Checker Language
|
||||
self.langIcon = QLabel("")
|
||||
self.langText = QLabel(self.tr("None"))
|
||||
self.langIcon.setPixmap(self.mainTheme.getPixmap("status_lang", (iPx, iPx)))
|
||||
self.langIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.langText.setContentsMargins(0, 0, xM, 0)
|
||||
self.addPermanentWidget(self.langIcon)
|
||||
@@ -91,7 +90,6 @@ class GuiMainStatus(QStatusBar):
|
||||
# The Project and Session Stats
|
||||
self.statsIcon = QLabel()
|
||||
self.statsText = QLabel("")
|
||||
self.statsIcon.setPixmap(self.mainTheme.getPixmap("status_stats", (iPx, iPx)))
|
||||
self.statsIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.statsText.setContentsMargins(0, 0, xM, 0)
|
||||
self.addPermanentWidget(self.statsIcon)
|
||||
@@ -99,12 +97,8 @@ class GuiMainStatus(QStatusBar):
|
||||
|
||||
# The Session Clock
|
||||
# Set the mimimum width so the label doesn't rescale every second
|
||||
self.timePixmap = self.mainTheme.getPixmap("status_time", (iPx, iPx))
|
||||
self.idlePixmap = self.mainTheme.getPixmap("status_idle", (iPx, iPx))
|
||||
|
||||
self.timeIcon = QLabel()
|
||||
self.timeText = QLabel("")
|
||||
self.timeIcon.setPixmap(self.timePixmap)
|
||||
self.timeText.setToolTip(self.tr("Session Time"))
|
||||
self.timeText.setMinimumWidth(self.mainTheme.getTextWidth("00:00:00:"))
|
||||
self.timeIcon.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -117,6 +111,7 @@ class GuiMainStatus(QStatusBar):
|
||||
|
||||
logger.debug("GuiMainStatus initialisation complete")
|
||||
|
||||
self.updateTheme()
|
||||
self.clearStatus()
|
||||
|
||||
return
|
||||
@@ -132,6 +127,21 @@ class GuiMainStatus(QStatusBar):
|
||||
self.updateTime()
|
||||
return True
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update theme elements.
|
||||
"""
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
|
||||
self.langIcon.setPixmap(self.mainTheme.getPixmap("status_lang", (iPx, iPx)))
|
||||
self.statsIcon.setPixmap(self.mainTheme.getPixmap("status_stats", (iPx, iPx)))
|
||||
|
||||
self.timePixmap = self.mainTheme.getPixmap("status_time", (iPx, iPx))
|
||||
self.idlePixmap = self.mainTheme.getPixmap("status_idle", (iPx, iPx))
|
||||
|
||||
self.timeIcon.setPixmap(self.timePixmap)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
|
||||
@@ -38,7 +38,7 @@ from PyQt5.QtGui import (
|
||||
|
||||
from novelwriter.enum import nwItemLayout, nwItemType
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.common import NWConfigParser, minmax, readTextFile
|
||||
from novelwriter.common import NWConfigParser, minmax
|
||||
from novelwriter.constants import nwLabels
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -67,6 +67,7 @@ class GuiTheme:
|
||||
self.themeUrl = ""
|
||||
self.themeLicense = ""
|
||||
self.themeLicenseUrl = ""
|
||||
self.themeIcons = ""
|
||||
|
||||
# GUI
|
||||
self.statNone = [120, 120, 120]
|
||||
@@ -104,16 +105,14 @@ class GuiTheme:
|
||||
self.colRepTag = [0, 0, 0]
|
||||
self.colMod = [0, 0, 0]
|
||||
|
||||
# Changeable Settings
|
||||
self.guiTheme = None
|
||||
self.guiSyntax = None
|
||||
self.syntaxFile = None
|
||||
self.cssFile = None
|
||||
self.guiFontDB = QFontDatabase()
|
||||
|
||||
# Class Setup
|
||||
# ===========
|
||||
|
||||
# Init GUI Font
|
||||
self.guiFontDB = QFontDatabase()
|
||||
self._setGuiFont()
|
||||
|
||||
# Load Themes
|
||||
self._guiPalette = QPalette()
|
||||
self._themeList = []
|
||||
self._syntaxList = []
|
||||
@@ -127,9 +126,8 @@ class GuiTheme:
|
||||
self._listConf(self._availSyntax, os.path.join(self.mainConf.dataPath, "syntax"))
|
||||
self._listConf(self._availThemes, os.path.join(self.mainConf.dataPath, "themes"))
|
||||
|
||||
self.updateFont()
|
||||
self.updateTheme()
|
||||
self.iconCache.updateTheme()
|
||||
self.loadTheme()
|
||||
self.loadSyntax()
|
||||
|
||||
# Icon Functions
|
||||
self.getIcon = self.iconCache.getIcon
|
||||
@@ -183,77 +181,26 @@ class GuiTheme:
|
||||
return int(ceil(qMetrics.boundingRect(theText).width()))
|
||||
|
||||
##
|
||||
# Actions
|
||||
# Theme Methods
|
||||
##
|
||||
|
||||
def updateFont(self):
|
||||
"""Update the GUI's font style from settings.
|
||||
"""
|
||||
theFont = QFont()
|
||||
if self.mainConf.guiFont not in self.guiFontDB.families():
|
||||
if self.mainConf.osWindows and "Arial" in self.guiFontDB.families():
|
||||
# On Windows we default to Arial if possible
|
||||
theFont.setFamily("Arial")
|
||||
theFont.setPointSize(10)
|
||||
else:
|
||||
theFont = self.guiFontDB.systemFont(QFontDatabase.GeneralFont)
|
||||
self.mainConf.guiFont = theFont.family()
|
||||
self.mainConf.guiFontSize = theFont.pointSize()
|
||||
else:
|
||||
theFont.setFamily(self.mainConf.guiFont)
|
||||
theFont.setPointSize(self.mainConf.guiFontSize)
|
||||
|
||||
qApp.setFont(theFont)
|
||||
|
||||
return
|
||||
|
||||
def updateTheme(self):
|
||||
"""Update the GUI theme from theme files.
|
||||
"""
|
||||
self.guiTheme = self.mainConf.guiTheme
|
||||
self.guiSyntax = self.mainConf.guiSyntax
|
||||
|
||||
self.themeFile = self._availThemes.get(self.guiTheme, None)
|
||||
if self.themeFile is None:
|
||||
logger.error("Could not find GUI theme '%s'", self.guiTheme)
|
||||
else:
|
||||
self.cssFile = self.themeFile[:-5]+".css"
|
||||
self.loadTheme()
|
||||
|
||||
self.syntaxFile = self._availSyntax.get(self.guiSyntax, None)
|
||||
if self.syntaxFile is None:
|
||||
logger.error("Could not find syntax theme '%s'", self.guiSyntax)
|
||||
else:
|
||||
self.loadSyntax()
|
||||
|
||||
# Update dependant colours
|
||||
backCol = qApp.palette().window().color()
|
||||
textCol = qApp.palette().windowText().color()
|
||||
|
||||
backLCol = backCol.lightnessF()
|
||||
textLCol = textCol.lightnessF()
|
||||
|
||||
if backLCol > textLCol:
|
||||
helpLCol = textLCol + 0.65*(backLCol - textLCol)
|
||||
else:
|
||||
helpLCol = backLCol + 0.65*(textLCol - backLCol)
|
||||
|
||||
self.helpText = [int(255*helpLCol)]*3
|
||||
|
||||
return True
|
||||
|
||||
def loadTheme(self):
|
||||
"""Load the currently specified GUI theme.
|
||||
"""
|
||||
logger.info("Loading GUI theme '%s'", self.guiTheme)
|
||||
guiTheme = self.mainConf.guiTheme
|
||||
themeFile = self._availThemes.get(guiTheme, None)
|
||||
if themeFile is None:
|
||||
logger.error("Could not find GUI theme '%s'", guiTheme)
|
||||
return False
|
||||
|
||||
# Config File
|
||||
logger.info("Loading GUI theme '%s'", guiTheme)
|
||||
confParser = NWConfigParser()
|
||||
try:
|
||||
with open(self.themeFile, mode="r", encoding="utf-8") as inFile:
|
||||
with open(themeFile, mode="r", encoding="utf-8") as inFile:
|
||||
confParser.read_file(inFile)
|
||||
except Exception:
|
||||
logger.error("Could not load theme settings from: %s", self.themeFile)
|
||||
logger.error("Could not load theme settings from: %s", themeFile)
|
||||
logException()
|
||||
return False
|
||||
|
||||
@@ -267,6 +214,7 @@ class GuiTheme:
|
||||
self.themeUrl = confParser.rdStr(cnfSec, "url", "")
|
||||
self.themeLicense = confParser.rdStr(cnfSec, "license", "N/A")
|
||||
self.themeLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")
|
||||
self.themeIcons = confParser.rdStr(cnfSec, "icontheme", "")
|
||||
|
||||
# Palette
|
||||
cnfSec = "Palette"
|
||||
@@ -285,18 +233,32 @@ class GuiTheme:
|
||||
self._setPalette(confParser, cnfSec, "highlightedtext", QPalette.HighlightedText)
|
||||
self._setPalette(confParser, cnfSec, "link", QPalette.Link)
|
||||
self._setPalette(confParser, cnfSec, "linkvisited", QPalette.LinkVisited)
|
||||
else:
|
||||
self._guiPalette = qApp.style().standardPalette()
|
||||
|
||||
# GUI
|
||||
cnfSec = "GUI"
|
||||
if confParser.has_section(cnfSec):
|
||||
self.statNone = self._loadColour(confParser, cnfSec, "statusnone")
|
||||
self.statUnsaved = self._loadColour(confParser, cnfSec, "statusunsaved")
|
||||
self.statSaved = self._loadColour(confParser, cnfSec, "statussaved")
|
||||
self.statNone = self._parseColour(confParser, cnfSec, "statusnone")
|
||||
self.statUnsaved = self._parseColour(confParser, cnfSec, "statusunsaved")
|
||||
self.statSaved = self._parseColour(confParser, cnfSec, "statussaved")
|
||||
|
||||
# CSS File
|
||||
cssData = readTextFile(self.cssFile)
|
||||
if cssData:
|
||||
qApp.setStyleSheet(cssData)
|
||||
# Icons
|
||||
self.iconCache.loadTheme(self.themeIcons)
|
||||
|
||||
# Update Dependant Colours
|
||||
backCol = self._guiPalette.window().color()
|
||||
textCol = self._guiPalette.windowText().color()
|
||||
|
||||
backLCol = backCol.lightnessF()
|
||||
textLCol = textCol.lightnessF()
|
||||
|
||||
if backLCol > textLCol:
|
||||
helpLCol = textLCol + 0.65*(backLCol - textLCol)
|
||||
else:
|
||||
helpLCol = backLCol + 0.65*(textLCol - backLCol)
|
||||
|
||||
self.helpText = [int(255*helpLCol)]*3
|
||||
|
||||
# Apply Styles
|
||||
qApp.setPalette(self._guiPalette)
|
||||
@@ -306,14 +268,20 @@ class GuiTheme:
|
||||
def loadSyntax(self):
|
||||
"""Load the currently specified syntax highlighter theme.
|
||||
"""
|
||||
logger.info("Loading syntax theme '%s'", self.guiSyntax)
|
||||
guiSyntax = self.mainConf.guiSyntax
|
||||
syntaxFile = self._availSyntax.get(guiSyntax, None)
|
||||
if syntaxFile is None:
|
||||
logger.error("Could not find syntax theme '%s'", guiSyntax)
|
||||
return False
|
||||
|
||||
logger.info("Loading syntax theme '%s'", guiSyntax)
|
||||
|
||||
confParser = NWConfigParser()
|
||||
try:
|
||||
with open(self.syntaxFile, mode="r", encoding="utf-8") as inFile:
|
||||
with open(syntaxFile, mode="r", encoding="utf-8") as inFile:
|
||||
confParser.read_file(inFile)
|
||||
except Exception:
|
||||
logger.error("Could not load syntax colours from: %s", self.syntaxFile)
|
||||
logger.error("Could not load syntax colours from: %s", syntaxFile)
|
||||
logException()
|
||||
return False
|
||||
|
||||
@@ -321,32 +289,32 @@ class GuiTheme:
|
||||
cnfSec = "Main"
|
||||
if confParser.has_section(cnfSec):
|
||||
self.syntaxName = confParser.rdStr(cnfSec, "name", "")
|
||||
self.syntaxDescription = confParser.rdStr(cnfSec, "description", "")
|
||||
self.syntaxAuthor = confParser.rdStr(cnfSec, "author", "")
|
||||
self.syntaxCredit = confParser.rdStr(cnfSec, "credit", "")
|
||||
self.syntaxDescription = confParser.rdStr(cnfSec, "description", "N/A")
|
||||
self.syntaxAuthor = confParser.rdStr(cnfSec, "author", "N/A")
|
||||
self.syntaxCredit = confParser.rdStr(cnfSec, "credit", "N/A")
|
||||
self.syntaxUrl = confParser.rdStr(cnfSec, "url", "")
|
||||
self.syntaxLicense = confParser.rdStr(cnfSec, "license", "")
|
||||
self.syntaxLicense = confParser.rdStr(cnfSec, "license", "N/A")
|
||||
self.syntaxLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")
|
||||
|
||||
# Syntax
|
||||
cnfSec = "Syntax"
|
||||
if confParser.has_section(cnfSec):
|
||||
self.colBack = self._loadColour(confParser, cnfSec, "background")
|
||||
self.colText = self._loadColour(confParser, cnfSec, "text")
|
||||
self.colLink = self._loadColour(confParser, cnfSec, "link")
|
||||
self.colHead = self._loadColour(confParser, cnfSec, "headertext")
|
||||
self.colHeadH = self._loadColour(confParser, cnfSec, "headertag")
|
||||
self.colEmph = self._loadColour(confParser, cnfSec, "emphasis")
|
||||
self.colDialN = self._loadColour(confParser, cnfSec, "straightquotes")
|
||||
self.colDialD = self._loadColour(confParser, cnfSec, "doublequotes")
|
||||
self.colDialS = self._loadColour(confParser, cnfSec, "singlequotes")
|
||||
self.colHidden = self._loadColour(confParser, cnfSec, "hidden")
|
||||
self.colKey = self._loadColour(confParser, cnfSec, "keyword")
|
||||
self.colVal = self._loadColour(confParser, cnfSec, "value")
|
||||
self.colSpell = self._loadColour(confParser, cnfSec, "spellcheckline")
|
||||
self.colError = self._loadColour(confParser, cnfSec, "errorline")
|
||||
self.colRepTag = self._loadColour(confParser, cnfSec, "replacetag")
|
||||
self.colMod = self._loadColour(confParser, cnfSec, "modifier")
|
||||
self.colBack = self._parseColour(confParser, cnfSec, "background")
|
||||
self.colText = self._parseColour(confParser, cnfSec, "text")
|
||||
self.colLink = self._parseColour(confParser, cnfSec, "link")
|
||||
self.colHead = self._parseColour(confParser, cnfSec, "headertext")
|
||||
self.colHeadH = self._parseColour(confParser, cnfSec, "headertag")
|
||||
self.colEmph = self._parseColour(confParser, cnfSec, "emphasis")
|
||||
self.colDialN = self._parseColour(confParser, cnfSec, "straightquotes")
|
||||
self.colDialD = self._parseColour(confParser, cnfSec, "doublequotes")
|
||||
self.colDialS = self._parseColour(confParser, cnfSec, "singlequotes")
|
||||
self.colHidden = self._parseColour(confParser, cnfSec, "hidden")
|
||||
self.colKey = self._parseColour(confParser, cnfSec, "keyword")
|
||||
self.colVal = self._parseColour(confParser, cnfSec, "value")
|
||||
self.colSpell = self._parseColour(confParser, cnfSec, "spellcheckline")
|
||||
self.colError = self._parseColour(confParser, cnfSec, "errorline")
|
||||
self.colRepTag = self._parseColour(confParser, cnfSec, "replacetag")
|
||||
self.colMod = self._parseColour(confParser, cnfSec, "modifier")
|
||||
|
||||
return True
|
||||
|
||||
@@ -388,52 +356,64 @@ class GuiTheme:
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _setGuiFont(self):
|
||||
"""Update the GUI's font style from settings.
|
||||
"""
|
||||
theFont = QFont()
|
||||
if self.mainConf.guiFont not in self.guiFontDB.families():
|
||||
if self.mainConf.osWindows and "Arial" in self.guiFontDB.families():
|
||||
# On Windows we default to Arial if possible
|
||||
theFont.setFamily("Arial")
|
||||
theFont.setPointSize(10)
|
||||
else:
|
||||
theFont = self.guiFontDB.systemFont(QFontDatabase.GeneralFont)
|
||||
self.mainConf.guiFont = theFont.family()
|
||||
self.mainConf.guiFontSize = theFont.pointSize()
|
||||
else:
|
||||
theFont.setFamily(self.mainConf.guiFont)
|
||||
theFont.setPointSize(self.mainConf.guiFontSize)
|
||||
|
||||
qApp.setFont(theFont)
|
||||
|
||||
return
|
||||
|
||||
def _listConf(self, targetDict, checkDir):
|
||||
"""Scan for syntax and gui themes and populate the dictionary.
|
||||
"""Scan for theme config files and populate the dictionary.
|
||||
"""
|
||||
if not os.path.isdir(checkDir):
|
||||
return
|
||||
return False
|
||||
|
||||
for checkFile in os.listdir(checkDir):
|
||||
confPath = os.path.join(checkDir, checkFile)
|
||||
if os.path.isfile(confPath) and confPath.endswith(".conf"):
|
||||
targetDict[checkFile[:-5]] = confPath
|
||||
|
||||
return
|
||||
return True
|
||||
|
||||
def _loadColour(self, confParser, cnfSec, cnfName):
|
||||
"""Load a colour value from a config string.
|
||||
def _parseColour(self, confParser, cnfSec, cnfName):
|
||||
"""Parse a colour value from a config string.
|
||||
"""
|
||||
if confParser.has_option(cnfSec, cnfName):
|
||||
inData = confParser.get(cnfSec, cnfName).split(",")
|
||||
outData = []
|
||||
values = confParser.get(cnfSec, cnfName).split(",")
|
||||
result = []
|
||||
try:
|
||||
outData.append(int(inData[0]))
|
||||
outData.append(int(inData[1]))
|
||||
outData.append(int(inData[2]))
|
||||
result.append(minmax(int(values[0]), 0, 255))
|
||||
result.append(minmax(int(values[1]), 0, 255))
|
||||
result.append(minmax(int(values[2]), 0, 255))
|
||||
except Exception:
|
||||
logger.error("Could not load theme colours for '%s' from config file", cnfName)
|
||||
outData = [0, 0, 0]
|
||||
result = [0, 0, 0]
|
||||
else:
|
||||
logger.warning("Could not find theme colours for '%s' in config file", cnfName)
|
||||
outData = [0, 0, 0]
|
||||
return outData
|
||||
result = [0, 0, 0]
|
||||
return result
|
||||
|
||||
def _setPalette(self, confParser, cnfSec, cnfName, paletteVal):
|
||||
"""Set a palette colour value from a config string.
|
||||
"""
|
||||
readCol = []
|
||||
if confParser.has_option(cnfSec, cnfName):
|
||||
inData = confParser.get(cnfSec, cnfName).split(",")
|
||||
try:
|
||||
readCol.append(int(inData[0]))
|
||||
readCol.append(int(inData[1]))
|
||||
readCol.append(int(inData[2]))
|
||||
except Exception:
|
||||
logger.error("Could not load theme colours for '%s' from config file", cnfName)
|
||||
return
|
||||
if len(readCol) == 3:
|
||||
self._guiPalette.setColor(paletteVal, QColor(*readCol))
|
||||
self._guiPalette.setColor(
|
||||
paletteVal, QColor(*self._parseColour(confParser, cnfSec, cnfName))
|
||||
)
|
||||
return
|
||||
|
||||
# End Class GuiTheme
|
||||
@@ -468,9 +448,9 @@ class GuiIcons:
|
||||
"view_editor", "view_novel", "view_outline",
|
||||
|
||||
# General Button Icons
|
||||
"add", "backward", "bookmark", "check", "close", "cross", "down", "edit", "forward",
|
||||
"maximise", "menu", "minimise", "reference", "refresh", "remove", "search_replace",
|
||||
"search", "settings", "up",
|
||||
"add", "backward", "bookmark", "checked", "close", "cross", "down", "edit", "forward",
|
||||
"maximise", "menu", "minimise", "noncheckable", "reference", "refresh", "remove",
|
||||
"search_replace", "search", "settings", "unchecked", "up",
|
||||
|
||||
# Switches
|
||||
"sticky-on", "sticky-off",
|
||||
@@ -492,13 +472,11 @@ class GuiIcons:
|
||||
# Storage
|
||||
self._qIcons = {}
|
||||
self._themeMap = {}
|
||||
self._themeList = []
|
||||
self._headerDec = []
|
||||
self._confName = "icons.conf"
|
||||
|
||||
# Icon Theme Path
|
||||
self._iconPath = os.path.join(self.mainConf.assetPath, "icons")
|
||||
self._themePath = os.path.join(self._iconPath, "system")
|
||||
self._iconPath = os.path.join(self.mainConf.assetPath, "icons")
|
||||
|
||||
# Icon Theme Meta
|
||||
self.themeName = ""
|
||||
@@ -515,20 +493,19 @@ class GuiIcons:
|
||||
# Actions
|
||||
##
|
||||
|
||||
def updateTheme(self):
|
||||
def loadTheme(self, iconTheme):
|
||||
"""Update the theme map. This is more of an init, since many of
|
||||
the GUI icons cannot really be replaced without writing specific
|
||||
update functions for the classes where they're used.
|
||||
"""
|
||||
self._themeMap = {}
|
||||
themePath = self._getThemePath()
|
||||
if themePath is None:
|
||||
logger.warning("No icons loaded")
|
||||
themePath = os.path.join(self.mainConf.assetPath, "icons", iconTheme)
|
||||
if not os.path.isdir(themePath):
|
||||
logger.warning("No icons loaded for '%s'", iconTheme)
|
||||
return False
|
||||
|
||||
self._themePath = themePath
|
||||
themeConf = os.path.join(themePath, self._confName)
|
||||
logger.info("Loading icon theme '%s'", self.mainConf.guiIcons)
|
||||
logger.info("Loading icon theme '%s'", iconTheme)
|
||||
|
||||
# Config File
|
||||
confParser = NWConfigParser()
|
||||
@@ -558,7 +535,7 @@ class GuiIcons:
|
||||
if iconName not in self.ICON_KEYS:
|
||||
logger.error("Unknown icon name '%s' in config file", iconName)
|
||||
else:
|
||||
iconPath = os.path.join(self._themePath, iconFile)
|
||||
iconPath = os.path.join(themePath, iconFile)
|
||||
if os.path.isfile(iconPath):
|
||||
self._themeMap[iconName] = iconPath
|
||||
logger.debug("Icon slot '%s' using file '%s'", iconName, iconFile)
|
||||
@@ -574,6 +551,14 @@ class GuiIcons:
|
||||
if iconKey not in self._themeMap:
|
||||
logger.error("No icon file specified for '%s'", iconKey)
|
||||
|
||||
# Refresh icons
|
||||
for iconKey in self._qIcons:
|
||||
logger.debug("Reloading icon: '%s'", iconKey)
|
||||
qIcon = self._loadIcon(iconKey)
|
||||
self._qIcons[iconKey] = qIcon
|
||||
|
||||
self._headerDec = []
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
@@ -595,7 +580,7 @@ class GuiIcons:
|
||||
return QPixmap()
|
||||
|
||||
if not os.path.isfile(imgPath):
|
||||
logger.error("Asset '%s' not found", self.IMAGE_MAP[decoKey])
|
||||
logger.error("Asset not found: %s", imgPath)
|
||||
return QPixmap()
|
||||
|
||||
theDeco = QPixmap(imgPath)
|
||||
@@ -608,7 +593,7 @@ class GuiIcons:
|
||||
|
||||
return theDeco
|
||||
|
||||
def getIcon(self, iconKey, iconSize=None):
|
||||
def getIcon(self, iconKey):
|
||||
"""Return an icon from the icon buffer. If it doesn't exist,
|
||||
return, load it, and if it still doesn't exist, return an empty
|
||||
icon.
|
||||
@@ -668,49 +653,10 @@ class GuiIcons:
|
||||
]
|
||||
return self._headerDec[minmax(hLevel, 0, 4)]
|
||||
|
||||
def listThemes(self):
|
||||
"""Scan the icons themes folder and list all themes.
|
||||
"""
|
||||
if self._themeList:
|
||||
return self._themeList
|
||||
|
||||
confParser = NWConfigParser()
|
||||
for themeDir in os.listdir(self._iconPath):
|
||||
themePath = os.path.join(self._iconPath, themeDir)
|
||||
if not os.path.isdir(themePath):
|
||||
continue
|
||||
|
||||
logger.debug("Checking icon theme config for '%s'", themeDir)
|
||||
themeConf = os.path.join(themePath, self._confName)
|
||||
themeName = _loadInternalName(confParser, themeConf)
|
||||
if themeName:
|
||||
self._themeList.append((themeDir, themeName))
|
||||
|
||||
self._themeList = sorted(self._themeList, key=lambda x: x[1])
|
||||
|
||||
return self._themeList
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _getThemePath(self):
|
||||
"""Get a valid theme path. Returns None if it fails.
|
||||
"""
|
||||
themePath = os.path.join(self.mainConf.assetPath, "icons", self.mainConf.guiIcons)
|
||||
if not os.path.isdir(themePath):
|
||||
logger.warning(
|
||||
"Icon theme '%s' not found, resetting to default", self.mainConf.guiIcons
|
||||
)
|
||||
self.mainConf.setDefaultIconTheme()
|
||||
|
||||
themePath = os.path.join(self.mainConf.assetPath, "icons", self.mainConf.guiIcons)
|
||||
if not os.path.isdir(themePath):
|
||||
logger.error("Default icon theme not found")
|
||||
return None
|
||||
|
||||
return themePath
|
||||
|
||||
def _loadIcon(self, iconKey):
|
||||
"""Load an icon from the assets themes folder. Is guaranteed to
|
||||
return a QIcon.
|
||||
|
||||
@@ -61,7 +61,6 @@ class GuiViewsBar(QToolBar):
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
self.setMaximumWidth(mPx)
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setStyleSheet("QToolBar {border: 0px;}")
|
||||
|
||||
stretch = QWidget(self)
|
||||
stretch.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
@@ -70,37 +69,31 @@ class GuiViewsBar(QToolBar):
|
||||
self.aProject = QAction(self.tr("Project"), self)
|
||||
self.aProject.setFont(lblFont)
|
||||
self.aProject.setToolTip(self.tr("Project Tree View"))
|
||||
self.aProject.setIcon(self.mainTheme.getIcon("view_editor"))
|
||||
self.aProject.triggered.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))
|
||||
|
||||
self.aNovel = QAction(self.tr("Novel"), self)
|
||||
self.aNovel.setFont(lblFont)
|
||||
self.aNovel.setToolTip(self.tr("Novel Tree View"))
|
||||
self.aNovel.setIcon(self.mainTheme.getIcon("view_novel"))
|
||||
self.aNovel.triggered.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))
|
||||
|
||||
self.aOutline = QAction(self.tr("Outline"), self)
|
||||
self.aOutline.setFont(lblFont)
|
||||
self.aOutline.setToolTip(self.tr("Novel Outline View"))
|
||||
self.aOutline.setIcon(self.mainTheme.getIcon("view_outline"))
|
||||
self.aOutline.triggered.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))
|
||||
|
||||
self.aBuild = QAction(self.tr("Build"), self)
|
||||
self.aBuild.setFont(lblFont)
|
||||
self.aBuild.setToolTip(self.tr("Build Novel Project"))
|
||||
self.aBuild.setIcon(self.mainTheme.getIcon("view_build"))
|
||||
self.aBuild.triggered.connect(lambda: self.mainGui.showBuildProjectDialog())
|
||||
|
||||
self.aDetails = QAction(self.tr("Details"), self)
|
||||
self.aDetails.setFont(lblFont)
|
||||
self.aDetails.setToolTip(self.tr("Project Details"))
|
||||
self.aDetails.setIcon(self.mainTheme.getIcon("proj_details"))
|
||||
self.aDetails.triggered.connect(lambda: self.mainGui.showProjectDetailsDialog())
|
||||
|
||||
self.aStats = QAction(self.tr("Stats"), self)
|
||||
self.aStats.setFont(lblFont)
|
||||
self.aStats.setToolTip(self.tr("Writing Statistics"))
|
||||
self.aStats.setIcon(self.mainTheme.getIcon("proj_stats"))
|
||||
self.aStats.triggered.connect(lambda: self.mainGui.showWritingStatsDialog())
|
||||
|
||||
# Settings Menu
|
||||
@@ -114,7 +107,6 @@ class GuiViewsBar(QToolBar):
|
||||
self.tbSettings = QToolButton(self)
|
||||
self.tbSettings.setFont(lblFont)
|
||||
self.tbSettings.setText(self.tr("Settings"))
|
||||
self.tbSettings.setIcon(self.mainTheme.getIcon("settings"))
|
||||
self.tbSettings.setMenu(self.mSettings)
|
||||
self.tbSettings.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
||||
self.tbSettings.setPopupMode(QToolButton.InstantPopup)
|
||||
@@ -129,8 +121,25 @@ class GuiViewsBar(QToolBar):
|
||||
self.addAction(self.aStats)
|
||||
self.addWidget(self.tbSettings)
|
||||
|
||||
self.updateTheme()
|
||||
|
||||
logger.debug("GuiViewsBar initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
def updateTheme(self):
|
||||
"""Initialise GUI elements that depend on specific settings.
|
||||
"""
|
||||
self.setStyleSheet("QToolBar {border: 0px;}")
|
||||
|
||||
self.aProject.setIcon(self.mainTheme.getIcon("view_editor"))
|
||||
self.aNovel.setIcon(self.mainTheme.getIcon("view_novel"))
|
||||
self.aOutline.setIcon(self.mainTheme.getIcon("view_outline"))
|
||||
self.aBuild.setIcon(self.mainTheme.getIcon("view_build"))
|
||||
self.aDetails.setIcon(self.mainTheme.getIcon("proj_details"))
|
||||
self.aStats.setIcon(self.mainTheme.getIcon("proj_stats"))
|
||||
self.tbSettings.setIcon(self.mainTheme.getIcon("settings"))
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiViewsBar
|
||||
|
||||
@@ -208,6 +208,7 @@ class GuiMain(QMainWindow):
|
||||
self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
|
||||
self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
|
||||
self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
|
||||
self.projView.projectSettingsRequest.connect(self.showProjectSettingsDialog)
|
||||
|
||||
self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
|
||||
self.novelView.openDocumentRequest.connect(self._openDocument)
|
||||
@@ -806,15 +807,11 @@ class GuiMain(QMainWindow):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
if tHandle is None:
|
||||
if self.docEditor.anyFocus() or self.isFocusMode:
|
||||
tHandle = self.docEditor.docHandle()
|
||||
else:
|
||||
tHandle = self.projView.getSelectedHandle()
|
||||
if tHandle:
|
||||
return self.projView.renameTreeItem(tHandle)
|
||||
if tHandle is None and (self.docEditor.anyFocus() or self.isFocusMode):
|
||||
tHandle = self.docEditor.docHandle()
|
||||
self.projView.renameTreeItem(tHandle)
|
||||
|
||||
return False
|
||||
return True
|
||||
|
||||
def rebuildTrees(self):
|
||||
"""Rebuild the project tree.
|
||||
@@ -910,25 +907,52 @@ class GuiMain(QMainWindow):
|
||||
if dlgConf.result() == QDialog.Accepted:
|
||||
logger.debug("Applying new preferences")
|
||||
self.initMain()
|
||||
self.mainTheme.updateTheme()
|
||||
self.saveDocument()
|
||||
|
||||
if dlgConf.needsRestart:
|
||||
self.makeAlert(self.tr(
|
||||
"Some changes will not be applied until novelWriter has been restarted."
|
||||
), nwAlert.INFO)
|
||||
|
||||
if dlgConf.refreshTree:
|
||||
self.projView.populateTree()
|
||||
|
||||
if dlgConf.updateTheme:
|
||||
# We are doing this manually instead of connecting to
|
||||
# qApp.paletteChanged since the processing order matters
|
||||
self.mainTheme.loadTheme()
|
||||
self.docEditor.updateTheme()
|
||||
self.docViewer.updateTheme()
|
||||
self.viewsBar.updateTheme()
|
||||
self.projView.updateTheme()
|
||||
self.novelView.updateTheme()
|
||||
self.outlineView.updateTheme()
|
||||
self.itemDetails.updateTheme()
|
||||
self.mainStatus.updateTheme()
|
||||
|
||||
if dlgConf.updateSyntax:
|
||||
self.mainTheme.loadSyntax()
|
||||
self.docEditor.updateSyntaxColours()
|
||||
|
||||
self.docEditor.initEditor()
|
||||
self.docViewer.initViewer()
|
||||
self.projView.initSettings()
|
||||
self.novelView.initSettings()
|
||||
self.outlineView.initSettings()
|
||||
|
||||
self._updateStatusWordCount()
|
||||
|
||||
return
|
||||
|
||||
def showProjectSettingsDialog(self):
|
||||
@pyqtSlot(int)
|
||||
def showProjectSettingsDialog(self, focusTab=GuiProjectSettings.TAB_MAIN):
|
||||
"""Open the project settings dialog.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
dlgProj = GuiProjectSettings(self)
|
||||
dlgProj = GuiProjectSettings(self, focusTab=focusTab)
|
||||
dlgProj.exec_()
|
||||
|
||||
if dlgProj.result() == QDialog.Accepted:
|
||||
@@ -1316,6 +1340,7 @@ class GuiMain(QMainWindow):
|
||||
self.addAction(self.mainMenu.aInsMinus)
|
||||
self.addAction(self.mainMenu.aInsTimes)
|
||||
self.addAction(self.mainMenu.aInsDivide)
|
||||
self.addAction(self.mainMenu.aInsSynopsis)
|
||||
|
||||
for mAction, _ in self.mainMenu.mInsKWItems.values():
|
||||
self.addAction(mAction)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pyqt5>=5.3
|
||||
pyqt5>=5.10
|
||||
lxml>=4.2.0
|
||||
pyenchant>=3.0.0
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-20 18:17:33">
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-25 18:45:45">
|
||||
<project>
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
<author>Jay Doh</author>
|
||||
<saveCount>1385</saveCount>
|
||||
<saveCount>1386</saveCount>
|
||||
<autoCount>236</autoCount>
|
||||
<editTime>69348</editTime>
|
||||
<editTime>69352</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>False</doBackup>
|
||||
|
||||
@@ -15,6 +15,7 @@ classifiers =
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
Programming Language :: Python :: Implementation :: CPython
|
||||
License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
||||
Development Status :: 5 - Production/Stable
|
||||
@@ -32,7 +33,7 @@ python_requires = >=3.7
|
||||
include_package_data = True
|
||||
packages = find:
|
||||
install_requires =
|
||||
pyqt5>=5.3
|
||||
pyqt5>=5.10
|
||||
lxml>=4.2.0
|
||||
pyenchant>=3.0.0
|
||||
|
||||
|
||||
@@ -131,6 +131,7 @@ def makeCheckSum(sumFile, cwd=None):
|
||||
except Exception as exc:
|
||||
print("Could not generate sha256 file")
|
||||
print(str(exc))
|
||||
return ""
|
||||
|
||||
return shaFile
|
||||
|
||||
@@ -905,7 +906,6 @@ def makeForLaunchpad(doSign=False, isFirst=False, isSnapshot=False):
|
||||
def makeAppImage(sysArgs):
|
||||
"""Build an Appimage
|
||||
"""
|
||||
|
||||
import glob
|
||||
import argparse
|
||||
import platform
|
||||
@@ -1088,8 +1088,9 @@ def makeAppImage(sysArgs):
|
||||
shutil.copyfile("setup/icons/novelwriter.svg", f"{imageDir}/novelwriter.svg")
|
||||
print("Copied: setup/icons/novelwriter.svg")
|
||||
|
||||
shutil.copyfile("setup/data/hicolor/256x256/apps/novelwriter.png",
|
||||
f"{imageDir}/novelwriter.png")
|
||||
shutil.copyfile(
|
||||
"setup/data/hicolor/256x256/apps/novelwriter.png", f"{imageDir}/novelwriter.png"
|
||||
)
|
||||
print("Copied: setup/data/hicolor/256x256/apps/novelwriter.png")
|
||||
|
||||
# Build Appimage
|
||||
@@ -1110,7 +1111,9 @@ def makeAppImage(sysArgs):
|
||||
print("")
|
||||
sys.exit(1)
|
||||
|
||||
outFile = glob.glob(f"{bldDir}/*.AppImage")[0]
|
||||
bldFile = glob.glob(f"{bldDir}/*.AppImage")[0]
|
||||
outFile = f"{bldDir}/novelWriter-{pkgVers}-py{pythonVer}-{linuxTag}.AppImage"
|
||||
os.rename(bldFile, outFile)
|
||||
shaFile = makeCheckSum(os.path.basename(outFile), cwd=bldDir)
|
||||
|
||||
toUpload(outFile)
|
||||
@@ -1118,11 +1121,11 @@ def makeAppImage(sysArgs):
|
||||
|
||||
return unparsedArgs
|
||||
|
||||
|
||||
##
|
||||
# Make Windows Setup EXE (build-win-exe)
|
||||
##
|
||||
|
||||
|
||||
def makeWindowsEmbedded(sysArgs):
|
||||
"""Set up a package with embedded Python and dependencies for
|
||||
Windows installation.
|
||||
|
||||
@@ -162,7 +162,11 @@ def mockGUI(monkeypatch, tmpConf):
|
||||
def nwGUI(qtbot, monkeypatch, fncDir, fncConf):
|
||||
"""Create an instance of the novelWriter GUI.
|
||||
"""
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Ok)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Ok)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
|
||||
monkeypatch.setattr("novelwriter.CONFIG", fncConf)
|
||||
nwGUI = novelwriter.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir])
|
||||
qtbot.addWidget(nwGUI)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
[Main]
|
||||
timestamp = 2021-12-31 16:45:32
|
||||
timestamp = 2022-10-26 11:19:49
|
||||
theme = default
|
||||
syntax = default_light
|
||||
icons = typicons_light
|
||||
guifont =
|
||||
guifontsize = 11
|
||||
lastnotes = 0x0
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
[Main]
|
||||
timestamp = 2021-12-31 16:45:34
|
||||
timestamp = 2022-10-26 11:19:51
|
||||
theme = default
|
||||
syntax = default_light
|
||||
icons = typicons_light
|
||||
guifont = Sans
|
||||
guifont = Cantarell
|
||||
guifontsize = 12
|
||||
lastnotes = 0x0
|
||||
guilang = en_GB
|
||||
@@ -12,7 +11,7 @@ hidehscroll = True
|
||||
|
||||
[Sizes]
|
||||
geometry = 1200, 650
|
||||
preferences = 670, 589
|
||||
preferences = 699, 614
|
||||
projcols = 200, 60, 140
|
||||
mainpane = 300, 800
|
||||
docpane = 400, 400
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-20 18:21:59">
|
||||
<project>
|
||||
<name>Project Name</name>
|
||||
<title>Project Title</title>
|
||||
<author>Jane Doe</author>
|
||||
<author>John Doh</author>
|
||||
<saveCount>1</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>0</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>True</doBackup>
|
||||
<language>None</language>
|
||||
<spellCheck>False</spellCheck>
|
||||
<spellLang>en</spellLang>
|
||||
<lastEdited>None</lastEdited>
|
||||
<lastViewed>None</lastViewed>
|
||||
<lastNovel>None</lastNovel>
|
||||
<lastOutline>None</lastOutline>
|
||||
<lastWordCount>9</lastWordCount>
|
||||
<novelWordCount>9</novelWordCount>
|
||||
<notesWordCount>0</notesWordCount>
|
||||
<autoReplace>
|
||||
<entry key="A">B</entry>
|
||||
<entry key="C">D</entry>
|
||||
<entry key="This">With This Stuff</entry>
|
||||
</autoReplace>
|
||||
<titleFormat>
|
||||
<title>%title%</title>
|
||||
<chapter>%title%</chapter>
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry key="s000000" count="5" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry>
|
||||
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||
<entry key="s000010" count="0" red="20" green="30" blue="40">Final</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000004" count="3" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry>
|
||||
<entry key="i000011" count="0" red="100" green="100" blue="100">Final</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="8">
|
||||
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="s000000" import="i000004">Novel</name>
|
||||
</item>
|
||||
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" mainHeading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
|
||||
<name status="s000000" import="i000004" active="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="1" type="FOLDER" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="s000000" import="i000004">New Chapter</name>
|
||||
</item>
|
||||
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" mainHeading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000000" import="i000004" active="True">New Chapter</name>
|
||||
</item>
|
||||
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" mainHeading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000000" import="i000004" active="True">New Scene</name>
|
||||
</item>
|
||||
<item handle="0000000000009" parent="None" root="0000000000009" order="1" type="ROOT" class="PLOT">
|
||||
<meta expanded="False"/>
|
||||
<name status="s000000" import="i000004">Plot</name>
|
||||
</item>
|
||||
<item handle="000000000000a" parent="None" root="000000000000a" order="2" type="ROOT" class="CHARACTER">
|
||||
<meta expanded="False"/>
|
||||
<name status="s000000" import="i000004">Characters</name>
|
||||
</item>
|
||||
<item handle="000000000000b" parent="None" root="000000000000b" order="3" type="ROOT" class="WORLD">
|
||||
<meta expanded="False"/>
|
||||
<name status="s000000" import="i000004">World</name>
|
||||
</item>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
@@ -192,32 +192,6 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
|
||||
tstConf.doReplaceSQuote = orDoSng
|
||||
assert tstConf.saveConfig() is True
|
||||
|
||||
# Test Correcting icon theme
|
||||
origIcons = tstConf.guiIcons
|
||||
|
||||
tstConf.guiIcons = "typicons_colour_dark"
|
||||
assert tstConf.saveConfig() is True
|
||||
assert tstConf.loadConfig() is True
|
||||
assert tstConf.guiIcons == "typicons_dark"
|
||||
|
||||
tstConf.guiIcons = "typicons_grey_dark"
|
||||
assert tstConf.saveConfig() is True
|
||||
assert tstConf.loadConfig() is True
|
||||
assert tstConf.guiIcons == "typicons_dark"
|
||||
|
||||
tstConf.guiIcons = "typicons_colour_light"
|
||||
assert tstConf.saveConfig() is True
|
||||
assert tstConf.loadConfig() is True
|
||||
assert tstConf.guiIcons == "typicons_light"
|
||||
|
||||
tstConf.guiIcons = "typicons_grey_light"
|
||||
assert tstConf.saveConfig() is True
|
||||
assert tstConf.loadConfig() is True
|
||||
assert tstConf.guiIcons == "typicons_light"
|
||||
|
||||
tstConf.guiIcons = origIcons
|
||||
assert tstConf.saveConfig()
|
||||
|
||||
# Localisation
|
||||
# ============
|
||||
|
||||
|
||||
@@ -162,6 +162,8 @@ def testCoreDocTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, m
|
||||
docText = "\n\n".join(docData)
|
||||
docRaw = docText.splitlines()
|
||||
assert NWDoc(theProject, hSplitDoc).writeDocument(docText) is True
|
||||
theProject.tree[hSplitDoc].setStatus(C.sFinished)
|
||||
theProject.tree[hSplitDoc].setImport(C.iMain)
|
||||
|
||||
docSplitter = DocSplitter(theProject, hSplitDoc)
|
||||
assert docSplitter._srcItem.isFileType()
|
||||
@@ -242,6 +244,11 @@ def testCoreDocTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, m
|
||||
"000000000002e", # Scene Five is after Scene Four
|
||||
]
|
||||
|
||||
# Check that status and importance has been preserved
|
||||
for rHandle in resDocHandle:
|
||||
assert theProject.tree[rHandle].itemStatus == C.sFinished
|
||||
assert theProject.tree[rHandle].itemImport == C.iMain
|
||||
|
||||
# Check handling of improper initialisation
|
||||
docSplitter = DocSplitter(theProject, C.hInvalid)
|
||||
assert docSplitter._srcHandle is None
|
||||
|
||||
@@ -29,12 +29,9 @@ from novelwriter.dialogs.about import GuiAbout
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI):
|
||||
def testDlgAbout_NWDialog(qtbot, nwGUI):
|
||||
"""Test the novelWriter about dialogs.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# NW About
|
||||
nwGUI.mainTheme.themeName = "A Theme"
|
||||
nwGUI.mainTheme.themeAuthor = "An Author"
|
||||
@@ -74,8 +71,6 @@ def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI):
|
||||
def testDlgAbout_QtDialog(monkeypatch, nwGUI):
|
||||
"""Test the Qt about dialogs.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "aboutQt", lambda *a, **k: None)
|
||||
|
||||
# Open About
|
||||
|
||||
@@ -22,7 +22,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import pytest
|
||||
|
||||
from PyQt5.QtCore import QItemSelectionModel
|
||||
from PyQt5.QtWidgets import QAction, QListWidgetItem, QDialog, QMessageBox
|
||||
from PyQt5.QtWidgets import QAction, QListWidgetItem, QDialog
|
||||
|
||||
from novelwriter.dialogs.quotes import GuiQuoteSelect
|
||||
from novelwriter.dialogs.updates import GuiUpdates
|
||||
@@ -30,12 +30,9 @@ from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgOther_QuoteSelect(qtbot, monkeypatch, nwGUI):
|
||||
def testDlgOther_QuoteSelect(qtbot, nwGUI):
|
||||
"""Test the quote symbols dialog.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
|
||||
nwQuot = GuiQuoteSelect(nwGUI)
|
||||
nwQuot.show()
|
||||
|
||||
@@ -52,7 +49,7 @@ def testDlgOther_QuoteSelect(qtbot, monkeypatch, nwGUI):
|
||||
assert nwQuot.result() == QDialog.Accepted
|
||||
assert nwQuot.selectedQuote == lastItem
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
nwQuot._doReject()
|
||||
nwQuot.close()
|
||||
|
||||
@@ -63,9 +60,6 @@ def testDlgOther_QuoteSelect(qtbot, monkeypatch, nwGUI):
|
||||
def testDlgOther_Updates(qtbot, monkeypatch, nwGUI):
|
||||
"""Test the check for updates dialog.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
|
||||
nwUpdate = GuiUpdates(nwGUI)
|
||||
nwUpdate.show()
|
||||
|
||||
@@ -95,7 +89,7 @@ def testDlgOther_Updates(qtbot, monkeypatch, nwGUI):
|
||||
# Trigger from Menu
|
||||
nwGUI.mainMenu.aUpdates.activate(QAction.Trigger)
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
nwUpdate._doClose()
|
||||
|
||||
# END Test testDlgOther_Updates
|
||||
|
||||
@@ -24,19 +24,14 @@ import pytest
|
||||
from tools import buildTestProject, C
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from novelwriter.dialogs.docmerge import GuiDocMerge
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
def testDlgMerge_Main(qtbot, nwGUI, fncProj, mockRnd):
|
||||
"""Test the merge documents tool.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
|
||||
|
||||
# Create a new project
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
|
||||
|
||||
@@ -23,8 +23,6 @@ import pytest
|
||||
|
||||
from tools import C, buildTestProject
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from novelwriter.dialogs.docsplit import GuiDocSplit
|
||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
|
||||
@@ -33,9 +31,6 @@ from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
"""Test the split document tool.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
# Create a new project
|
||||
|
||||
@@ -35,9 +35,7 @@ from novelwriter.config import Config
|
||||
from novelwriter.dialogs.quotes import GuiQuoteSelect
|
||||
from novelwriter.dialogs.preferences import GuiPreferences
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
KEY_DELAY = 1
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
@@ -63,7 +61,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
|
||||
nwGUI = novelwriter.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir])
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
theConf = nwGUI.mainConf
|
||||
assert theConf.confPath == fncDir
|
||||
@@ -72,30 +69,40 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
|
||||
monkeypatch.setattr(GuiPreferences, "result", lambda *a: QDialog.Accepted)
|
||||
monkeypatch.setattr(nwGUI.docEditor.spEnchant, "listDictionaries", lambda: [("en", "none")])
|
||||
|
||||
nwGUI.mainMenu.aPreferences.activate(QAction.Trigger)
|
||||
qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000)
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(GuiPreferences, "updateTheme", lambda *a: True)
|
||||
mp.setattr(GuiPreferences, "updateSyntax", lambda *a: True)
|
||||
mp.setattr(GuiPreferences, "needsRestart", lambda *a: True)
|
||||
mp.setattr(GuiPreferences, "refreshTree", lambda *a: True)
|
||||
nwGUI.mainMenu.aPreferences.activate(QAction.Trigger)
|
||||
qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000)
|
||||
|
||||
nwPrefs = getGuiItem("GuiPreferences")
|
||||
assert isinstance(nwPrefs, GuiPreferences)
|
||||
nwPrefs.show()
|
||||
assert nwPrefs.mainConf.confPath == fncDir
|
||||
|
||||
assert nwPrefs.updateTheme is False
|
||||
assert nwPrefs.updateSyntax is False
|
||||
assert nwPrefs.needsRestart is False
|
||||
assert nwPrefs.refreshTree is False
|
||||
|
||||
# General Settings
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
tabGeneral = nwPrefs.tabGeneral
|
||||
nwPrefs._tabBox.setCurrentWidget(tabGeneral)
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert tabGeneral.showFullPath.isChecked()
|
||||
qtbot.mouseClick(tabGeneral.showFullPath, Qt.LeftButton)
|
||||
assert not tabGeneral.showFullPath.isChecked()
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert not tabGeneral.hideVScroll.isChecked()
|
||||
qtbot.mouseClick(tabGeneral.hideVScroll, Qt.LeftButton)
|
||||
assert tabGeneral.hideVScroll.isChecked()
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert not tabGeneral.hideHScroll.isChecked()
|
||||
qtbot.mouseClick(tabGeneral.hideHScroll, Qt.LeftButton)
|
||||
assert tabGeneral.hideHScroll.isChecked()
|
||||
@@ -104,21 +111,21 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
|
||||
monkeypatch.setattr(QFontDialog, "getFont", lambda font, obj: (font, True))
|
||||
qtbot.mouseClick(tabGeneral.fontButton, Qt.LeftButton)
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
tabGeneral.guiFontSize.setValue(12)
|
||||
|
||||
# Projects Settings
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
tabProjects = nwPrefs.tabProjects
|
||||
nwPrefs._tabBox.setCurrentWidget(tabProjects)
|
||||
tabProjects.backupPath = "no/where"
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert not tabProjects.backupOnClose.isChecked()
|
||||
qtbot.mouseClick(tabProjects.backupOnClose, Qt.LeftButton)
|
||||
assert tabProjects.backupOnClose.isChecked()
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# Check Browse button
|
||||
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "")
|
||||
@@ -126,104 +133,104 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
|
||||
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "some/dir")
|
||||
qtbot.mouseClick(tabProjects.backupGetPath, Qt.LeftButton)
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
tabProjects.autoSaveDoc.setValue(20)
|
||||
tabProjects.autoSaveProj.setValue(40)
|
||||
|
||||
# Document Settings
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
tabDocs = nwPrefs.tabDocs
|
||||
nwPrefs._tabBox.setCurrentWidget(tabDocs)
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
qtbot.mouseClick(tabDocs.fontButton, Qt.LeftButton)
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
tabDocs.textSize.setValue(13)
|
||||
tabDocs.textWidth.setValue(700)
|
||||
tabDocs.focusWidth.setValue(900)
|
||||
tabDocs.textMargin.setValue(45)
|
||||
tabDocs.tabWidth.setValue(45)
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert not tabDocs.hideFocusFooter.isChecked()
|
||||
qtbot.mouseClick(tabDocs.hideFocusFooter, Qt.LeftButton)
|
||||
assert tabDocs.hideFocusFooter.isChecked()
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert not tabDocs.doJustify.isChecked()
|
||||
qtbot.mouseClick(tabDocs.doJustify, Qt.LeftButton)
|
||||
assert tabDocs.doJustify.isChecked()
|
||||
|
||||
# Editor Settings
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
tabEditor = nwPrefs.tabEditor
|
||||
nwPrefs._tabBox.setCurrentWidget(tabEditor)
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert not tabEditor.showTabsNSpaces.isChecked()
|
||||
qtbot.mouseClick(tabEditor.showTabsNSpaces, Qt.LeftButton)
|
||||
assert tabEditor.showTabsNSpaces.isChecked()
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert not tabEditor.showLineEndings.isChecked()
|
||||
qtbot.mouseClick(tabEditor.showLineEndings, Qt.LeftButton)
|
||||
assert tabEditor.showLineEndings.isChecked()
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert not tabEditor.autoScroll.isChecked()
|
||||
qtbot.mouseClick(tabEditor.autoScroll, Qt.LeftButton)
|
||||
assert tabEditor.autoScroll.isChecked()
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
tabEditor.scrollPastEnd.setValue(0)
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
tabEditor.bigDocLimit.setValue(500)
|
||||
|
||||
# Syntax Settings
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
tabSyntax = nwPrefs.tabSyntax
|
||||
nwPrefs._tabBox.setCurrentWidget(tabSyntax)
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert tabSyntax.highlightQuotes.isChecked()
|
||||
qtbot.mouseClick(tabSyntax.highlightQuotes, Qt.LeftButton)
|
||||
assert not tabSyntax.highlightQuotes.isChecked()
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert tabSyntax.highlightEmph.isChecked()
|
||||
qtbot.mouseClick(tabSyntax.highlightEmph, Qt.LeftButton)
|
||||
assert not tabSyntax.highlightEmph.isChecked()
|
||||
|
||||
# Automation Settings
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
tabAuto = nwPrefs.tabAuto
|
||||
nwPrefs._tabBox.setCurrentWidget(tabAuto)
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert tabAuto.autoSelect.isChecked()
|
||||
qtbot.mouseClick(tabAuto.autoSelect, Qt.LeftButton)
|
||||
assert not tabAuto.autoSelect.isChecked()
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert tabAuto.doReplace.isChecked()
|
||||
qtbot.mouseClick(tabAuto.doReplace, Qt.LeftButton)
|
||||
assert not tabAuto.doReplace.isChecked()
|
||||
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
assert not tabAuto.doReplaceSQuote.isEnabled()
|
||||
assert not tabAuto.doReplaceDQuote.isEnabled()
|
||||
assert not tabAuto.doReplaceDash.isEnabled()
|
||||
assert not tabAuto.doReplaceDots.isEnabled()
|
||||
|
||||
# Quotation Style
|
||||
qtbot.wait(keyDelay)
|
||||
qtbot.wait(KEY_DELAY)
|
||||
tabQuote = nwPrefs.tabQuote
|
||||
nwPrefs._tabBox.setCurrentWidget(tabQuote)
|
||||
|
||||
monkeypatch.setattr(GuiQuoteSelect, "selectedQuote", "'")
|
||||
monkeypatch.setattr(GuiQuoteSelect, "exec_", lambda *args: QDialog.Accepted)
|
||||
monkeypatch.setattr(GuiQuoteSelect, "exec_", lambda *a: QDialog.Accepted)
|
||||
qtbot.mouseClick(tabQuote.btnDoubleStyleC, Qt.LeftButton)
|
||||
|
||||
# Save and Check Config
|
||||
@@ -249,6 +256,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
|
||||
novelwriter.CONFIG = origConf
|
||||
nwGUI.closeMain()
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testDlgPreferences_Main
|
||||
|
||||
@@ -23,25 +23,15 @@ import pytest
|
||||
|
||||
from tools import getGuiItem
|
||||
|
||||
from PyQt5.QtWidgets import QAction, QMessageBox
|
||||
from PyQt5.QtWidgets import QAction
|
||||
|
||||
from novelwriter.dialogs.projdetails import GuiProjectDetails
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgProjDetails_Dialog(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
def testDlgProjDetails_Dialog(qtbot, nwGUI, nwLipsum):
|
||||
"""Test the project details dialog.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Create a project to work on
|
||||
assert nwGUI.openProject(nwLipsum)
|
||||
assert nwGUI.rebuildIndex(beQuiet=True)
|
||||
@@ -54,7 +44,6 @@ def testDlgProjDetails_Dialog(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
|
||||
projDet = getGuiItem("GuiProjectDetails")
|
||||
assert isinstance(projDet, GuiProjectDetails)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Overview Page
|
||||
# =============
|
||||
@@ -105,7 +94,7 @@ def testDlgProjDetails_Dialog(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i]
|
||||
assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i]
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# Clean Up
|
||||
projDet._doClose()
|
||||
|
||||
@@ -26,29 +26,19 @@ from tools import getGuiItem
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QTreeWidgetItem, QDialog, QAction, QFileDialog,
|
||||
QMessageBox
|
||||
QDialogButtonBox, QTreeWidgetItem, QDialog, QAction, QFileDialog
|
||||
)
|
||||
|
||||
from novelwriter.dialogs.projload import GuiProjectLoad
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
"""Test the load project wizard.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
|
||||
assert nwGUI.openProject(nwMinimal)
|
||||
assert nwGUI.closeProject()
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *a: None)
|
||||
monkeypatch.setattr(GuiProjectLoad, "result", lambda *a: QDialog.Accepted)
|
||||
nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
|
||||
@@ -58,22 +48,18 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
assert isinstance(nwLoad, GuiProjectLoad)
|
||||
nwLoad.show()
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
recentCount = nwLoad.listBox.topLevelItemCount()
|
||||
assert recentCount > 0
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
selItem = nwLoad.listBox.topLevelItem(0)
|
||||
selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole)
|
||||
assert isinstance(selItem, QTreeWidgetItem)
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
nwLoad.selPath.setText("")
|
||||
nwLoad.listBox.setCurrentItem(selItem)
|
||||
nwLoad._doSelectRecent()
|
||||
assert nwLoad.selPath.text() == selPath
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton)
|
||||
assert nwLoad.openPath == selPath
|
||||
assert nwLoad.openState == nwLoad.OPEN_STATE
|
||||
@@ -81,27 +67,22 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
# Just create a new project load from scratch for the rest of the test
|
||||
del nwLoad
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
|
||||
qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000)
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
nwLoad = getGuiItem("GuiProjectLoad")
|
||||
assert isinstance(nwLoad, GuiProjectLoad)
|
||||
nwLoad.show()
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Cancel), Qt.LeftButton)
|
||||
assert nwLoad.openPath is None
|
||||
assert nwLoad.openState == nwLoad.NONE_STATE
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
nwLoad.show()
|
||||
qtbot.mouseClick(nwLoad.newButton, Qt.LeftButton)
|
||||
assert nwLoad.openPath is None
|
||||
assert nwLoad.openState == nwLoad.NEW_STATE
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
nwLoad.show()
|
||||
nwLoad._doDeleteRecent()
|
||||
assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
|
||||
@@ -113,6 +94,6 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
assert nwLoad.openState == nwLoad.OPEN_STATE
|
||||
|
||||
nwLoad.close()
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testDlgLoadProject_Main
|
||||
|
||||
@@ -19,56 +19,40 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from shutil import copyfile
|
||||
from tools import cmpFiles, getGuiItem, buildTestProject
|
||||
from novelwriter.enum import nwItemType
|
||||
from tools import C, getGuiItem, buildTestProject
|
||||
|
||||
from PyQt5.QtGui import QColor
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QDialog, QAction, QMessageBox, QColorDialog
|
||||
from PyQt5.QtWidgets import QDialog, QAction, QColorDialog
|
||||
|
||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
from novelwriter.dialogs.projsettings import GuiProjectSettings
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
statusKeys = ["s000000", "s000001", "s000002", "s000003"]
|
||||
importKeys = ["i000004", "i000005", "i000006", "i000007"]
|
||||
KEY_DELAY = 1
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgProjSettings_Dialog(
|
||||
qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDir, refDir, mockRnd
|
||||
):
|
||||
"""Test the full project settings dialog.
|
||||
def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
|
||||
"""Test the main dialog class. Saving settings is not tested in this
|
||||
test, but are instead tested in the individual tab tests.
|
||||
"""
|
||||
projFile = os.path.join(fncProj, "nwProject.nwx")
|
||||
testFile = os.path.join(outDir, "guiProjSettings_Dialog_nwProject.nwx")
|
||||
compFile = os.path.join(refDir, "guiProjSettings_Dialog_nwProject.nwx")
|
||||
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
# Block the GUI blocking thread
|
||||
monkeypatch.setattr(GuiProjectSettings, "exec_", lambda *a: None)
|
||||
monkeypatch.setattr(GuiProjectSettings, "result", lambda *a: QDialog.Accepted)
|
||||
monkeypatch.setattr(GuiProjectSettings, "spellChanged", lambda *a: True)
|
||||
|
||||
# Check that we cannot open when there is no project
|
||||
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
||||
assert getGuiItem("GuiProjectSettings") is None
|
||||
|
||||
# Create new project
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
nwGUI.mainConf.backupPath = fncDir
|
||||
|
||||
# Pretend we have a project
|
||||
nwGUI.hasProject = True
|
||||
nwGUI.theProject.setSpellLang("en")
|
||||
nwGUI.theProject.setBookAuthors("Jane Smith\nJohn Smith")
|
||||
nwGUI.theProject.setAutoReplace({"A": "B", "C": "D"})
|
||||
|
||||
# Get the dialog object
|
||||
monkeypatch.setattr(GuiProjectSettings, "exec_", lambda *a: None)
|
||||
monkeypatch.setattr(GuiProjectSettings, "result", lambda *a: QDialog.Accepted)
|
||||
monkeypatch.setattr(nwGUI.docEditor.spEnchant, "listDictionaries", lambda: [("en", "none")])
|
||||
|
||||
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
||||
qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
|
||||
|
||||
@@ -77,83 +61,180 @@ def testDlgProjSettings_Dialog(
|
||||
projEdit.show()
|
||||
qtbot.addWidget(projEdit)
|
||||
|
||||
# Switch Tabs
|
||||
projEdit._focusTab(GuiProjectSettings.TAB_REPLACE)
|
||||
assert projEdit._tabBox.currentWidget() == projEdit.tabReplace
|
||||
|
||||
projEdit._focusTab(GuiProjectSettings.TAB_IMPORT)
|
||||
assert projEdit._tabBox.currentWidget() == projEdit.tabImport
|
||||
|
||||
projEdit._focusTab(GuiProjectSettings.TAB_STATUS)
|
||||
assert projEdit._tabBox.currentWidget() == projEdit.tabStatus
|
||||
|
||||
projEdit._focusTab(GuiProjectSettings.TAB_MAIN)
|
||||
assert projEdit._tabBox.currentWidget() == projEdit.tabMain
|
||||
|
||||
# Clean Up
|
||||
projEdit._doClose()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testDlgProjSettings_Dialog
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
"""Test the main tab of the project settings dialog.
|
||||
"""
|
||||
# Mock components
|
||||
monkeypatch.setattr(nwGUI.docEditor.spEnchant, "listDictionaries", lambda: [("en", "none")])
|
||||
|
||||
# Create new project
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
mockRnd.reset()
|
||||
nwGUI.mainConf.backupPath = fncDir
|
||||
|
||||
# Set some values
|
||||
theProject = nwGUI.theProject
|
||||
theProject.setSpellLang("en")
|
||||
theProject.setBookAuthors("Jane Smith\nJohn Smith")
|
||||
theProject.setAutoReplace({"A": "B", "C": "D"})
|
||||
|
||||
# Create Dialog
|
||||
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_MAIN)
|
||||
projSettings.show()
|
||||
qtbot.addWidget(projSettings)
|
||||
|
||||
# Settings Tab
|
||||
# ============
|
||||
|
||||
assert projEdit.tabMain.editName.text() == "New Project"
|
||||
assert projEdit.tabMain.editTitle.text() == "New Novel"
|
||||
assert projEdit.tabMain.editAuthors.toPlainText() == "Jane Smith\nJohn Smith"
|
||||
assert projEdit.tabMain.spellLang.currentData() == "en"
|
||||
assert projEdit.tabMain.doBackup.isChecked() is False
|
||||
tabMain = projSettings.tabMain
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
projEdit.tabMain.editName.setText("")
|
||||
assert tabMain.editName.text() == "New Project"
|
||||
assert tabMain.editTitle.text() == "New Novel"
|
||||
assert tabMain.editAuthors.toPlainText() == "Jane Smith\nJohn Smith"
|
||||
assert tabMain.spellLang.currentData() == "en"
|
||||
assert tabMain.doBackup.isChecked() is False
|
||||
|
||||
tabMain.editName.setText("")
|
||||
for c in "Project Name":
|
||||
qtbot.keyClick(projEdit.tabMain.editName, c, delay=typeDelay)
|
||||
projEdit.tabMain.editTitle.setText("")
|
||||
qtbot.keyClick(tabMain.editName, c, delay=KEY_DELAY)
|
||||
tabMain.editTitle.setText("")
|
||||
for c in "Project Title":
|
||||
qtbot.keyClick(projEdit.tabMain.editTitle, c, delay=typeDelay)
|
||||
qtbot.keyClick(tabMain.editTitle, c, delay=KEY_DELAY)
|
||||
|
||||
projEdit.tabMain.editAuthors.clear()
|
||||
tabMain.editAuthors.clear()
|
||||
for c in "Jane Doe":
|
||||
qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=typeDelay)
|
||||
qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(tabMain.editAuthors, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(tabMain.editAuthors, Qt.Key_Return, delay=KEY_DELAY)
|
||||
for c in "John Doh":
|
||||
qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=typeDelay)
|
||||
qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(tabMain.editAuthors, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(tabMain.editAuthors, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
assert projEdit.tabMain.editName.text() == "Project Name"
|
||||
assert projEdit.tabMain.editTitle.text() == "Project Title"
|
||||
assert projEdit.tabMain.editAuthors.toPlainText() == "Jane Doe\nJohn Doh\n"
|
||||
assert tabMain.editName.text() == "Project Name"
|
||||
assert tabMain.editTitle.text() == "Project Title"
|
||||
assert tabMain.editAuthors.toPlainText() == "Jane Doe\nJohn Doh\n"
|
||||
assert projSettings.spellChanged is False
|
||||
|
||||
projSettings._doSave()
|
||||
assert theProject.projName == "Project Name"
|
||||
assert theProject.bookTitle == "Project Title"
|
||||
assert theProject.bookAuthors == ["Jane Doe", "John Doh"]
|
||||
|
||||
# Clean up
|
||||
projSettings._doClose()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testDlgProjSettings_Main
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
"""Test the status and importance tabs of the project settings
|
||||
dialog.
|
||||
"""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
# Mock components
|
||||
monkeypatch.setattr(nwGUI.docEditor.spEnchant, "listDictionaries", lambda: [("en", "none")])
|
||||
|
||||
# Create new project
|
||||
mockRnd.reset()
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
nwGUI.mainConf.backupPath = fncDir
|
||||
|
||||
# Set some values
|
||||
theProject = nwGUI.theProject
|
||||
theProject.tree[C.hTitlePage].setStatus(C.sFinished)
|
||||
theProject.tree[C.hChapterDoc].setStatus(C.sDraft)
|
||||
theProject.tree[C.hSceneDoc].setStatus(C.sDraft)
|
||||
|
||||
nwGUI.projView.projTree.setSelectedHandle(C.hPlotRoot)
|
||||
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
|
||||
nwGUI.projView.projTree.setSelectedHandle(C.hCharRoot)
|
||||
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
|
||||
nwGUI.projView.projTree.setSelectedHandle(C.hWorldRoot)
|
||||
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
|
||||
|
||||
hPlotNote = "0000000000010"
|
||||
hCharNote = "0000000000011"
|
||||
hWorldNote = "0000000000012"
|
||||
|
||||
theProject.tree[hPlotNote].setImport(C.iMajor)
|
||||
theProject.tree[hCharNote].setImport(C.iMajor)
|
||||
theProject.tree[hWorldNote].setImport(C.iMain)
|
||||
|
||||
# Create Dialog
|
||||
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_STATUS)
|
||||
projSettings.show()
|
||||
qtbot.addWidget(projSettings)
|
||||
|
||||
# Status Tab
|
||||
# ==========
|
||||
|
||||
projEdit._tabBox.setCurrentWidget(projEdit.tabStatus)
|
||||
tabStatus = projSettings.tabStatus
|
||||
|
||||
assert projEdit.tabStatus.colChanged is False
|
||||
assert projEdit.tabStatus.getNewList() == ([], [])
|
||||
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4
|
||||
assert tabStatus.colChanged is False
|
||||
assert tabStatus.getNewList() == ([], [])
|
||||
assert tabStatus.listBox.topLevelItemCount() == 4
|
||||
|
||||
# Can't delete the first item (it's in use)
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True)
|
||||
qtbot.mouseClick(projEdit.tabStatus.delButton, Qt.LeftButton)
|
||||
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4
|
||||
tabStatus.listBox.clearSelection()
|
||||
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(0))
|
||||
qtbot.mouseClick(tabStatus.delButton, Qt.LeftButton)
|
||||
assert tabStatus.listBox.topLevelItemCount() == 4
|
||||
|
||||
# Can delete the third item
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabStatus.listBox.topLevelItem(2).setSelected(True)
|
||||
qtbot.mouseClick(projEdit.tabStatus.delButton, Qt.LeftButton)
|
||||
assert projEdit.tabStatus.listBox.topLevelItemCount() == 3
|
||||
# Can delete the second item
|
||||
tabStatus.listBox.clearSelection()
|
||||
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(1))
|
||||
qtbot.mouseClick(tabStatus.delButton, Qt.LeftButton)
|
||||
assert tabStatus.listBox.topLevelItemCount() == 3
|
||||
|
||||
# Add a new item
|
||||
monkeypatch.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
|
||||
qtbot.mouseClick(projEdit.tabStatus.addButton, Qt.LeftButton)
|
||||
projEdit.tabStatus.listBox.topLevelItem(3).setSelected(True)
|
||||
for n in range(8):
|
||||
qtbot.keyClick(projEdit.tabStatus.editName, Qt.Key_Backspace, delay=typeDelay)
|
||||
for c in "Final":
|
||||
qtbot.keyClick(projEdit.tabStatus.editName, c, delay=typeDelay)
|
||||
qtbot.mouseClick(projEdit.tabStatus.colButton, Qt.LeftButton)
|
||||
qtbot.mouseClick(projEdit.tabStatus.saveButton, Qt.LeftButton)
|
||||
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4
|
||||
qtbot.wait(stepDelay)
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
|
||||
qtbot.mouseClick(tabStatus.addButton, Qt.LeftButton)
|
||||
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(3))
|
||||
for _ in range(8):
|
||||
qtbot.keyClick(tabStatus.editName, Qt.Key_Backspace, delay=KEY_DELAY)
|
||||
for c in "Final":
|
||||
qtbot.keyClick(tabStatus.editName, c, delay=KEY_DELAY)
|
||||
qtbot.mouseClick(tabStatus.colButton, Qt.LeftButton)
|
||||
qtbot.mouseClick(tabStatus.saveButton, Qt.LeftButton)
|
||||
assert tabStatus.listBox.topLevelItemCount() == 4
|
||||
|
||||
assert projEdit.tabStatus.colChanged is True
|
||||
assert projEdit.tabStatus.getNewList() == (
|
||||
assert tabStatus.colChanged is True
|
||||
assert tabStatus.getNewList() == (
|
||||
[
|
||||
{
|
||||
"key": statusKeys[0],
|
||||
"key": C.sNew,
|
||||
"name": "New",
|
||||
"cols": (100, 100, 100)
|
||||
}, {
|
||||
"key": statusKeys[1],
|
||||
"name": "Note",
|
||||
"cols": (200, 50, 0)
|
||||
"key": C.sDraft,
|
||||
"name": "Draft",
|
||||
"cols": (200, 150, 0)
|
||||
}, {
|
||||
"key": statusKeys[3],
|
||||
"key": C.sFinished,
|
||||
"name": "Finished",
|
||||
"cols": (50, 200, 0)
|
||||
}, {
|
||||
@@ -162,121 +243,198 @@ def testDlgProjSettings_Dialog(
|
||||
"cols": (20, 30, 40)
|
||||
}
|
||||
], [
|
||||
statusKeys[2] # Deleted item
|
||||
C.sNote # Deleted item
|
||||
]
|
||||
)
|
||||
|
||||
# Move items
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabStatus._moveItem(1)
|
||||
assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
|
||||
statusKeys[0], statusKeys[1], statusKeys[3], None
|
||||
# Move items, none selected -> no change
|
||||
tabStatus.listBox.clearSelection()
|
||||
tabStatus._moveItem(1)
|
||||
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
|
||||
C.sNew, C.sDraft, C.sFinished, None
|
||||
]
|
||||
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True)
|
||||
projEdit.tabStatus._moveItem(-1)
|
||||
assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
|
||||
statusKeys[0], statusKeys[1], statusKeys[3], None
|
||||
# Move items, first selected, move up -> no change
|
||||
tabStatus.listBox.clearSelection()
|
||||
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(0))
|
||||
tabStatus._moveItem(-1)
|
||||
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
|
||||
C.sNew, C.sDraft, C.sFinished, None
|
||||
]
|
||||
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabStatus.listBox.topLevelItem(3).setSelected(True)
|
||||
projEdit.tabStatus._moveItem(-1)
|
||||
assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
|
||||
statusKeys[0], statusKeys[1], None, statusKeys[3]
|
||||
# Move items, last selected, move up -> allowed
|
||||
tabStatus.listBox.clearSelection()
|
||||
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(3))
|
||||
tabStatus._moveItem(-1)
|
||||
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
|
||||
C.sNew, C.sDraft, None, C.sFinished
|
||||
]
|
||||
projEdit.tabStatus._moveItem(1)
|
||||
assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
|
||||
statusKeys[0], statusKeys[1], statusKeys[3], None
|
||||
|
||||
# Move items, same selected, move down -> allowed
|
||||
tabStatus._moveItem(1)
|
||||
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
|
||||
C.sNew, C.sDraft, C.sFinished, None
|
||||
]
|
||||
|
||||
# Importance Tab
|
||||
# ==============
|
||||
|
||||
projEdit._tabBox.setCurrentWidget(projEdit.tabImport)
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabImport.listBox.topLevelItem(3).setSelected(True)
|
||||
qtbot.mouseClick(projEdit.tabImport.delButton, Qt.LeftButton)
|
||||
qtbot.mouseClick(projEdit.tabImport.addButton, Qt.LeftButton)
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabImport.listBox.topLevelItem(3).setSelected(True)
|
||||
for n in range(8):
|
||||
qtbot.keyClick(projEdit.tabImport.editName, Qt.Key_Backspace, delay=typeDelay)
|
||||
for c in "Final":
|
||||
qtbot.keyClick(projEdit.tabImport.editName, c, delay=typeDelay)
|
||||
qtbot.mouseClick(projEdit.tabImport.saveButton, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
tabImport = projSettings.tabImport
|
||||
projSettings._focusTab(GuiProjectSettings.TAB_IMPORT)
|
||||
|
||||
# Delete unused entry
|
||||
tabImport.listBox.clearSelection()
|
||||
tabImport.listBox.setCurrentItem(tabImport.listBox.topLevelItem(1))
|
||||
qtbot.mouseClick(tabImport.delButton, Qt.LeftButton)
|
||||
assert tabImport.listBox.topLevelItemCount() == 3
|
||||
|
||||
# Add a new entry
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
|
||||
qtbot.mouseClick(tabImport.addButton, Qt.LeftButton)
|
||||
tabImport.listBox.clearSelection()
|
||||
tabImport.listBox.setCurrentItem(tabImport.listBox.topLevelItem(3))
|
||||
for _ in range(8):
|
||||
qtbot.keyClick(tabImport.editName, Qt.Key_Backspace, delay=KEY_DELAY)
|
||||
for c in "Final":
|
||||
qtbot.keyClick(tabImport.editName, c, delay=KEY_DELAY)
|
||||
qtbot.mouseClick(tabImport.colButton, Qt.LeftButton)
|
||||
qtbot.mouseClick(tabImport.saveButton, Qt.LeftButton)
|
||||
assert tabImport.listBox.topLevelItemCount() == 4
|
||||
|
||||
assert tabImport.colChanged is True
|
||||
assert tabImport.getNewList() == (
|
||||
[
|
||||
{
|
||||
"key": C.iNew,
|
||||
"name": "New",
|
||||
"cols": (100, 100, 100)
|
||||
}, {
|
||||
"key": C.iMajor,
|
||||
"name": "Major",
|
||||
"cols": (200, 150, 0)
|
||||
}, {
|
||||
"key": C.iMain,
|
||||
"name": "Main",
|
||||
"cols": (50, 200, 0)
|
||||
}, {
|
||||
"key": None,
|
||||
"name": "Final",
|
||||
"cols": (20, 30, 40)
|
||||
}
|
||||
], [
|
||||
C.iMinor # Deleted item
|
||||
]
|
||||
)
|
||||
|
||||
# Check Project
|
||||
projSettings._doSave()
|
||||
|
||||
statusItems = dict(theProject.statusItems.items())
|
||||
assert statusItems[C.sNew]["name"] == "New"
|
||||
assert statusItems[C.sDraft]["name"] == "Draft"
|
||||
assert statusItems[C.sFinished]["name"] == "Finished"
|
||||
assert statusItems["s000013"]["name"] == "Final"
|
||||
|
||||
importItems = dict(theProject.importItems.items())
|
||||
assert importItems[C.iNew]["name"] == "New"
|
||||
assert importItems[C.iMajor]["name"] == "Major"
|
||||
assert importItems[C.iMain]["name"] == "Main"
|
||||
assert importItems["i000014"]["name"] == "Final"
|
||||
|
||||
# Clean up
|
||||
# qtbot.stop()
|
||||
projSettings._doClose()
|
||||
|
||||
# END Test testDlgProjSettings_StatusImport
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
"""Test the auto-replace tab of the project settings dialog.
|
||||
"""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
# Mock components
|
||||
monkeypatch.setattr(nwGUI.docEditor.spEnchant, "listDictionaries", lambda: [("en", "none")])
|
||||
|
||||
# Create new project
|
||||
mockRnd.reset()
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
nwGUI.mainConf.backupPath = fncDir
|
||||
|
||||
# Set some values
|
||||
theProject = nwGUI.theProject
|
||||
theProject.autoReplace = {
|
||||
"A": "B", "C": "D"
|
||||
}
|
||||
|
||||
# Create Dialog
|
||||
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_REPLACE)
|
||||
projSettings.show()
|
||||
qtbot.addWidget(projSettings)
|
||||
|
||||
# Auto-Replace Tab
|
||||
# ================
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
projEdit._tabBox.setCurrentWidget(projEdit.tabReplace)
|
||||
tabReplace = projSettings.tabReplace
|
||||
|
||||
assert projEdit.tabReplace.listBox.topLevelItem(0).text(0) == "<A>"
|
||||
assert projEdit.tabReplace.listBox.topLevelItem(0).text(1) == "B"
|
||||
assert projEdit.tabReplace.listBox.topLevelItem(1).text(0) == "<C>"
|
||||
assert projEdit.tabReplace.listBox.topLevelItem(1).text(1) == "D"
|
||||
assert tabReplace.listBox.topLevelItem(0).text(0) == "<A>"
|
||||
assert tabReplace.listBox.topLevelItem(0).text(1) == "B"
|
||||
assert tabReplace.listBox.topLevelItem(1).text(0) == "<C>"
|
||||
assert tabReplace.listBox.topLevelItem(1).text(1) == "D"
|
||||
assert tabReplace.listBox.topLevelItemCount() == 2
|
||||
|
||||
qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton)
|
||||
projEdit.tabReplace.listBox.topLevelItem(2).setSelected(True)
|
||||
projEdit.tabReplace.editKey.setText("")
|
||||
# Nothing to save or delete
|
||||
tabReplace.listBox.clearSelection()
|
||||
assert tabReplace._saveEntry() is False
|
||||
assert tabReplace._delEntry() is False
|
||||
assert tabReplace.listBox.topLevelItemCount() == 2
|
||||
|
||||
# Create a new entry
|
||||
qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton)
|
||||
assert tabReplace.listBox.topLevelItemCount() == 3
|
||||
assert tabReplace.listBox.topLevelItem(2).text(0) == "<keyword3>"
|
||||
assert tabReplace.listBox.topLevelItem(2).text(1) == ""
|
||||
|
||||
# Edit the entry
|
||||
tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(2))
|
||||
tabReplace.editKey.setText("")
|
||||
for c in "Th is ":
|
||||
qtbot.keyClick(projEdit.tabReplace.editKey, c, delay=typeDelay)
|
||||
projEdit.tabReplace.editValue.setText("")
|
||||
qtbot.keyClick(tabReplace.editKey, c, delay=KEY_DELAY)
|
||||
tabReplace.editValue.setText("")
|
||||
for c in "With This Stuff ":
|
||||
qtbot.keyClick(projEdit.tabReplace.editValue, c, delay=typeDelay)
|
||||
qtbot.mouseClick(projEdit.tabReplace.saveButton, Qt.LeftButton)
|
||||
qtbot.keyClick(tabReplace.editValue, c, delay=KEY_DELAY)
|
||||
qtbot.mouseClick(tabReplace.saveButton, Qt.LeftButton)
|
||||
assert tabReplace.listBox.topLevelItem(2).text(0) == "<This>"
|
||||
assert tabReplace.listBox.topLevelItem(2).text(1) == "With This Stuff "
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
projEdit.tabReplace.listBox.clearSelection()
|
||||
assert not projEdit.tabReplace._saveEntry()
|
||||
assert not projEdit.tabReplace._delEntry()
|
||||
qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton)
|
||||
# Create a new entry again
|
||||
tabReplace.listBox.clearSelection()
|
||||
qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton)
|
||||
assert tabReplace.listBox.topLevelItemCount() == 4
|
||||
|
||||
# The list is sorted, so we must find it
|
||||
newIdx = -1
|
||||
for i in range(projEdit.tabReplace.listBox.topLevelItemCount()):
|
||||
if projEdit.tabReplace.listBox.topLevelItem(i).text(0) == "<keyword4>":
|
||||
for i in range(tabReplace.listBox.topLevelItemCount()):
|
||||
if tabReplace.listBox.topLevelItem(i).text(0) == "<keyword4>":
|
||||
newIdx = i
|
||||
break
|
||||
|
||||
assert newIdx >= 0
|
||||
newItem = projEdit.tabReplace.listBox.topLevelItem(newIdx)
|
||||
projEdit.tabReplace.listBox.setCurrentItem(newItem)
|
||||
qtbot.mouseClick(projEdit.tabReplace.delButton, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Save & Check
|
||||
# ============
|
||||
# Then delete the new item
|
||||
tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(newIdx))
|
||||
qtbot.mouseClick(tabReplace.delButton, Qt.LeftButton)
|
||||
assert tabReplace.listBox.topLevelItemCount() == 3
|
||||
|
||||
projEdit._doSave()
|
||||
# Check Project
|
||||
projSettings._doSave()
|
||||
assert theProject.autoReplace == {
|
||||
"A": "B", "C": "D", "This": "With This Stuff"
|
||||
}
|
||||
|
||||
# Open again, and check project settings
|
||||
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
||||
qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
|
||||
# Clean up
|
||||
# qtbot.stop()
|
||||
projSettings._doClose()
|
||||
|
||||
projEdit = getGuiItem("GuiProjectSettings")
|
||||
assert isinstance(projEdit, GuiProjectSettings)
|
||||
|
||||
qtbot.addWidget(projEdit)
|
||||
assert projEdit.tabMain.editName.text() == "Project Name"
|
||||
assert projEdit.tabMain.editTitle.text() == "Project Title"
|
||||
theAuth = projEdit.tabMain.editAuthors.toPlainText().strip().splitlines()
|
||||
assert len(theAuth) == 2
|
||||
assert theAuth[0] == "Jane Doe"
|
||||
assert theAuth[1] == "John Doh"
|
||||
|
||||
projEdit._doClose()
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
assert nwGUI.saveProject()
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Check the files
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, [2, 8, 9, 10])
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
|
||||
# END Test testDlgProjSettings_Dialog
|
||||
# END Test testDlgProjSettings_Replace
|
||||
|
||||
@@ -23,7 +23,7 @@ import os
|
||||
import pytest
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QDialog, QMessageBox, QAction
|
||||
from PyQt5.QtWidgets import QDialog, QAction
|
||||
|
||||
from tools import writeFile, readFile, getGuiItem
|
||||
from mock import causeOSError
|
||||
@@ -31,25 +31,17 @@ from mock import causeOSError
|
||||
from novelwriter.constants import nwFiles
|
||||
from novelwriter.dialogs.wordlist import GuiWordList
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
"""test the word list editor.
|
||||
"""
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiWordList, "exec_", lambda *a: None)
|
||||
monkeypatch.setattr(GuiWordList, "result", lambda *a: QDialog.Accepted)
|
||||
monkeypatch.setattr(GuiWordList, "accept", lambda *a: None)
|
||||
|
||||
# Open project
|
||||
nwGUI.openProject(nwMinimal)
|
||||
qtbot.wait(stepDelay)
|
||||
dictFile = os.path.join(nwMinimal, "meta", nwFiles.PROJ_DICT)
|
||||
|
||||
# Load the dialog
|
||||
@@ -59,7 +51,6 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
wList = getGuiItem("GuiWordList")
|
||||
assert isinstance(wList, GuiWordList)
|
||||
wList.show()
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# List should be blank
|
||||
assert wList.listBox.count() == 0
|
||||
@@ -73,7 +64,6 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
"word_f\n"
|
||||
"word_b\n"
|
||||
))
|
||||
qtbot.wait(stepDelay)
|
||||
assert wList._loadWordList()
|
||||
|
||||
# Check that the content was loaded
|
||||
@@ -130,7 +120,7 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
monkeypatch.setattr("builtins.open", causeOSError)
|
||||
assert not wList._doSave()
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
wList._doClose()
|
||||
|
||||
# END Test testDlgWordList_Dialog
|
||||
|
||||
@@ -25,33 +25,26 @@ from mock import causeOSError
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QTextBlock, QTextCursor, QTextOption
|
||||
from PyQt5.QtWidgets import QAction, QMessageBox, qApp
|
||||
from PyQt5.QtWidgets import QAction, qApp
|
||||
|
||||
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemLayout
|
||||
from novelwriter.constants import nwKeyWords, nwUnicode
|
||||
from novelwriter.core.index import countWords
|
||||
from novelwriter.gui.doceditor import GuiDocEditor
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
KEY_DELAY = 1
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_Init(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
def testGuiEditor_Init(qtbot, nwGUI, nwMinimal, ipsumText):
|
||||
"""Test initialising the editor.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Open project
|
||||
assert nwGUI.openProject(nwMinimal)
|
||||
assert nwGUI.openDocument("8c659a11cd429")
|
||||
|
||||
nwGUI.docEditor.setText("### Lorem Ipsum\n\n%s" % ipsumText[0])
|
||||
assert nwGUI.saveDocument()
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Check Defaults
|
||||
qDoc = nwGUI.docEditor.document()
|
||||
@@ -80,7 +73,7 @@ def testGuiEditor_Init(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
assert nwGUI.docEditor.horizontalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
|
||||
assert nwGUI.docEditor._typPadChar == nwUnicode.U_THNBSP
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiEditor_Init
|
||||
|
||||
@@ -89,10 +82,6 @@ def testGuiEditor_Init(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumText):
|
||||
"""Test loading text into the editor.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Open project
|
||||
sHandle = "8c659a11cd429"
|
||||
assert nwGUI.openProject(nwMinimal) is True
|
||||
@@ -102,7 +91,6 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
|
||||
nwGUI.docEditor.replaceText(longText)
|
||||
assert nwGUI.saveDocument() is True
|
||||
assert nwGUI.closeDocument() is True
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Load Text
|
||||
# =========
|
||||
@@ -142,7 +130,7 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
|
||||
assert nwGUI.docEditor.loadText(sHandle) is True
|
||||
assert nwGUI.docEditor.toPlainText() == ""
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiEditor_LoadText
|
||||
|
||||
@@ -151,15 +139,10 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
|
||||
def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumText):
|
||||
"""Test saving text from the editor.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Open project
|
||||
sHandle = "8c659a11cd429"
|
||||
assert nwGUI.openProject(nwMinimal) is True
|
||||
assert nwGUI.openDocument(sHandle) is True
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Save Text
|
||||
# =========
|
||||
@@ -193,24 +176,19 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
|
||||
# Regular save
|
||||
assert nwGUI.docEditor.saveText() is True
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiEditor_SaveText
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_MetaData(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
def testGuiEditor_MetaData(qtbot, nwGUI, nwMinimal):
|
||||
"""Test extracting various meta data and other values.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Open project
|
||||
sHandle = "8c659a11cd429"
|
||||
assert nwGUI.openProject(nwMinimal) is True
|
||||
assert nwGUI.openDocument(sHandle) is True
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Get Text
|
||||
# This should replace line and paragraph separators, but preserve
|
||||
@@ -253,21 +231,16 @@ def testGuiEditor_MetaData(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_Actions(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
def testGuiEditor_Actions(qtbot, nwGUI, nwMinimal, ipsumText):
|
||||
"""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.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Open project
|
||||
sHandle = "8c659a11cd429"
|
||||
assert nwGUI.openProject(nwMinimal) is True
|
||||
assert nwGUI.openDocument(sHandle) is True
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
|
||||
assert nwGUI.docEditor.replaceText(theText) is True
|
||||
@@ -487,7 +460,7 @@ def testGuiEditor_Actions(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
# Unknown Action
|
||||
assert nwGUI.docEditor.docAction(nwDocAction.NO_ACTION) is False
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiEditor_Actions
|
||||
|
||||
@@ -496,15 +469,10 @@ def testGuiEditor_Actions(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
"""Test the document insert functions.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Open project
|
||||
sHandle = "8c659a11cd429"
|
||||
assert nwGUI.openProject(nwMinimal) is True
|
||||
assert nwGUI.openDocument(sHandle) is True
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
|
||||
assert nwGUI.docEditor.replaceText(theText) is True
|
||||
@@ -577,7 +545,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
"\n\n\n", "\n\n@pov: Jane\n@char: John\n\n", 1
|
||||
)
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiEditor_Insert
|
||||
|
||||
@@ -586,15 +554,10 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
"""Test the text manipulation functions.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Open project
|
||||
sHandle = "8c659a11cd429"
|
||||
assert nwGUI.openProject(nwMinimal) is True
|
||||
assert nwGUI.openDocument(sHandle) is True
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
|
||||
assert nwGUI.docEditor.replaceText(theText) is True
|
||||
@@ -791,7 +754,7 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumTe
|
||||
assert newPara[6] == twoBits[4]
|
||||
assert newPara[7] == " ".join(twoBits[5:])
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiEditor_TextManipulation
|
||||
|
||||
@@ -800,15 +763,10 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumTe
|
||||
def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
"""Test the block formatting function.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Open project
|
||||
sHandle = "8c659a11cd429"
|
||||
assert nwGUI.openProject(nwMinimal) is True
|
||||
assert nwGUI.openDocument(sHandle) is True
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
|
||||
assert nwGUI.docEditor.replaceText(theText) is True
|
||||
@@ -825,10 +783,6 @@ def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumTex
|
||||
mp.setattr(QTextBlock, "isValid", lambda *a, **k: False)
|
||||
assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is False
|
||||
|
||||
# Empty Block
|
||||
assert nwGUI.docEditor.setCursorLine(1) is True
|
||||
assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is False
|
||||
|
||||
# Keyword
|
||||
assert nwGUI.docEditor.replaceText("@pov: Jane\n\n") is True
|
||||
assert nwGUI.docEditor.setCursorPosition(5) is True
|
||||
@@ -1115,24 +1069,19 @@ def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumTex
|
||||
assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_COM) is True
|
||||
assert nwGUI.docEditor.getText() == "#### Title\n\n% The Text\n\n"
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiEditor_BlockFormatting
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
def testGuiEditor_Tags(qtbot, nwGUI, nwMinimal, ipsumText):
|
||||
"""Test the document editor tags functionality.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Open project
|
||||
sHandle = "8c659a11cd429"
|
||||
assert nwGUI.openProject(nwMinimal) is True
|
||||
assert nwGUI.openDocument(sHandle) is True
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Create Scene
|
||||
theText = "### A Scene\n\n@char: Jane, John\n\n" + ipsumText[0] + "\n\n"
|
||||
@@ -1144,7 +1093,7 @@ def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
assert nwGUI.openDocument(cHandle) is True
|
||||
assert nwGUI.docEditor.replaceText(theText) is True
|
||||
assert nwGUI.saveDocument() is True
|
||||
assert nwGUI.projView.revealNewTreeItem(cHandle)
|
||||
assert nwGUI.projView.projTree.revealNewTreeItem(cHandle)
|
||||
nwGUI.docEditor.updateTagHighLighting()
|
||||
|
||||
# Follow Tag
|
||||
@@ -1181,7 +1130,7 @@ def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
assert nwGUI.closeDocViewer() is True
|
||||
assert nwGUI.docViewer._docHandle is None
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiEditor_Tags
|
||||
|
||||
@@ -1190,10 +1139,6 @@ def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
"""Test saving text from the editor.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
class MockThreadPool:
|
||||
|
||||
def __init__(self):
|
||||
@@ -1226,7 +1171,6 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
nwGUI.theProject.tree[sHandle]._initCount = 0 # Clear item's count
|
||||
nwGUI.theProject.tree[sHandle]._wordCount = 0 # Clear item's count
|
||||
assert nwGUI.openDocument(sHandle) is True
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
theText = "\n\n".join(ipsumText)
|
||||
cC, wC, pC = countWords(theText)
|
||||
@@ -1249,7 +1193,6 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
|
||||
nwGUI.docEditor.wCounterDoc.run()
|
||||
# nwGUI.docEditor._updateDocCounts(cC, wC, pC)
|
||||
qtbot.wait(stepDelay)
|
||||
assert nwGUI.theProject.tree[sHandle]._charCount == cC
|
||||
assert nwGUI.theProject.tree[sHandle]._wordCount == wC
|
||||
assert nwGUI.theProject.tree[sHandle]._paraCount == pC
|
||||
@@ -1258,7 +1201,6 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
# Select all text
|
||||
assert nwGUI.docEditor.docFooter._docSelection is False
|
||||
nwGUI.docEditor.docAction(nwDocAction.SEL_ALL)
|
||||
qtbot.wait(stepDelay)
|
||||
assert nwGUI.docEditor.docFooter._docSelection is True
|
||||
|
||||
# Run the selection word counter
|
||||
@@ -1267,10 +1209,9 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
|
||||
nwGUI.docEditor.wCounterSel.run()
|
||||
# nwGUI.docEditor._updateSelCounts(cC, wC, pC)
|
||||
qtbot.wait(stepDelay)
|
||||
assert nwGUI.docEditor.docFooter.wordsText.text() == f"Words: {wC} selected"
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiEditor_WordCounters
|
||||
|
||||
@@ -1279,14 +1220,11 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
"""Test the document editor search functionality.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
|
||||
|
||||
assert nwGUI.openProject(nwLipsum) is True
|
||||
assert nwGUI.openDocument("4c4f28287af27") is True
|
||||
origText = nwGUI.docEditor.getText()
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Select the Word "est"
|
||||
nwGUI.docEditor.setCursorPosition(630)
|
||||
@@ -1301,11 +1239,11 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
|
||||
# Find next by enter key
|
||||
monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: True)
|
||||
qtbot.keyClick(nwGUI.docEditor.docSearch.searchBox, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor.docSearch.searchBox, Qt.Key_Return, delay=KEY_DELAY)
|
||||
assert abs(nwGUI.docEditor.getCursorPosition() - 1284) < 3
|
||||
|
||||
# Find next by button
|
||||
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay)
|
||||
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY)
|
||||
assert abs(nwGUI.docEditor.getCursorPosition() - 1498) < 3
|
||||
|
||||
# Activate loop search
|
||||
@@ -1323,14 +1261,14 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
assert nwGUI.docEditor.setCursorPosition(15)
|
||||
|
||||
# Toggle search again with header button
|
||||
qtbot.mouseClick(nwGUI.docEditor.docHeader.searchButton, Qt.LeftButton, delay=keyDelay)
|
||||
qtbot.mouseClick(nwGUI.docEditor.docHeader.searchButton, Qt.LeftButton, delay=KEY_DELAY)
|
||||
assert nwGUI.docEditor.docSearch.setSearchText("")
|
||||
assert nwGUI.docEditor.docSearch.isVisible() is True
|
||||
|
||||
# Search for non-existing
|
||||
nwGUI.docEditor.setCursorPosition(0)
|
||||
assert nwGUI.docEditor.docSearch.setSearchText("abcdef")
|
||||
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay)
|
||||
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY)
|
||||
assert nwGUI.docEditor.getCursorPosition() < 3 # No result
|
||||
|
||||
# Enable RegEx search
|
||||
@@ -1341,19 +1279,19 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
# Set invalid RegEx
|
||||
nwGUI.docEditor.setCursorPosition(0)
|
||||
assert nwGUI.docEditor.docSearch.setSearchText(r"\bSus[")
|
||||
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay)
|
||||
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY)
|
||||
assert nwGUI.docEditor.getCursorPosition() < 3 # No result
|
||||
|
||||
# Set dangerous RegEx (issue #1015)
|
||||
# If this doesn't get caught, the app will hang
|
||||
nwGUI.docEditor.setCursorPosition(0)
|
||||
assert nwGUI.docEditor.docSearch.setSearchText(r".*")
|
||||
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay)
|
||||
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY)
|
||||
assert abs(nwGUI.docEditor.getCursorPosition() - 14) < 3
|
||||
|
||||
# Set valid RegEx
|
||||
assert nwGUI.docEditor.docSearch.setSearchText(r"\bSus")
|
||||
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay)
|
||||
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY)
|
||||
assert abs(nwGUI.docEditor.getCursorPosition() - 208) < 3
|
||||
|
||||
# Find next and then prev
|
||||
@@ -1404,7 +1342,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
assert abs(nwGUI.docEditor.getCursorPosition() - 208) < 3
|
||||
|
||||
# Replace "sus" with "foo" via replace button
|
||||
qtbot.mouseClick(nwGUI.docEditor.docSearch.replaceButton, Qt.LeftButton, delay=keyDelay)
|
||||
qtbot.mouseClick(nwGUI.docEditor.docSearch.replaceButton, Qt.LeftButton, delay=KEY_DELAY)
|
||||
assert nwGUI.docEditor.getText()[205:213] == "foocipit"
|
||||
|
||||
# Revert last two replaces
|
||||
@@ -1482,7 +1420,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
monkeypatch.setattr(nwGUI.docEditor.docSearch.replaceBox, "hasFocus", lambda: True)
|
||||
assert nwGUI.docEditor.focusNextPrevChild(True) is True
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiEditor_Search
|
||||
|
||||
|
||||
@@ -23,25 +23,17 @@ import pytest
|
||||
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from PyQt5.QtGui import QTextCursor
|
||||
from PyQt5.QtWidgets import qApp, QAction, QMessageBox
|
||||
from PyQt5.QtWidgets import qApp, QAction
|
||||
from mock import causeException
|
||||
|
||||
from novelwriter.enum import nwDocAction
|
||||
from novelwriter.core.tohtml import ToHtml
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
"""Test the document viewer.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Open project
|
||||
assert nwGUI.openProject(nwLipsum)
|
||||
|
||||
@@ -184,6 +176,6 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
assert nwGUI.docViewer.loadText("846352075de7d") is False
|
||||
assert nwGUI.docViewer.toPlainText() == "An error occurred while generating the preview."
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiViewer_Main
|
||||
|
||||
@@ -37,17 +37,13 @@ from novelwriter.gui.projtree import GuiProjectTree
|
||||
from novelwriter.gui.doceditor import GuiDocEditor
|
||||
from novelwriter.gui.noveltree import GuiNovelView
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
KEY_DELAY = 1
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiMain_ProjectBlocker(monkeypatch, nwGUI):
|
||||
def testGuiMain_ProjectBlocker(nwGUI):
|
||||
"""Test the blocking of features when there's no project open.
|
||||
"""
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Test no-project blocking
|
||||
assert nwGUI.closeProject() is True
|
||||
assert nwGUI.saveProject() is False
|
||||
@@ -74,9 +70,6 @@ def testGuiMain_ProjectBlocker(monkeypatch, nwGUI):
|
||||
def testGuiMain_NewProject(monkeypatch, nwGUI, fncProj):
|
||||
"""Test creating a new project.
|
||||
"""
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# No data
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(GuiProjectWizard, "exec_", lambda *a: None)
|
||||
@@ -113,8 +106,6 @@ def testGuiMain_NewProject(monkeypatch, nwGUI, fncProj):
|
||||
def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
"""Test handling of project tree items based on GUI focus states.
|
||||
"""
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
|
||||
sHandle = "000000000000f"
|
||||
@@ -165,9 +156,6 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mockRnd):
|
||||
"""Test the document editor.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
|
||||
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
|
||||
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
|
||||
@@ -195,11 +183,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
compFile = os.path.join(refDir, "guiEditor_Main_Initial_nwProject.nwx")
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Re-open project
|
||||
assert nwGUI.openProject(fncProj)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Check that we loaded the data
|
||||
assert len(nwGUI.theProject.tree) == 8
|
||||
@@ -241,18 +227,18 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
|
||||
# Type something into the document
|
||||
nwGUI.switchFocus(nwWidget.EDITOR)
|
||||
qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY)
|
||||
for c in "# Jane Doe":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
for c in "@tag: Jane":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
for c in "This is a file about Jane.":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
# Add a Plot File
|
||||
nwGUI.switchFocus(nwWidget.TREE)
|
||||
@@ -263,18 +249,18 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
|
||||
# Type something into the document
|
||||
nwGUI.switchFocus(nwWidget.EDITOR)
|
||||
qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY)
|
||||
for c in "# Main Plot":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
for c in "@tag: MainPlot":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
for c in "This is a file detailing the main plot.":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
# Add a World File
|
||||
nwGUI.switchFocus(nwWidget.TREE)
|
||||
@@ -290,18 +276,18 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
|
||||
# Type something into the document
|
||||
nwGUI.switchFocus(nwWidget.EDITOR)
|
||||
qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY)
|
||||
for c in "# Main Location":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
for c in "@tag: Home":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
for c in "This is a file describing Jane's home.":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
# Trigger autosaves before making more changes
|
||||
nwGUI._autoSaveDocument()
|
||||
@@ -317,67 +303,67 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
|
||||
# Type something into the document
|
||||
nwGUI.switchFocus(nwWidget.EDITOR)
|
||||
qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY)
|
||||
for c in "# Novel":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "## Chapter":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "@pov: Jane":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
for c in "@plot: MainPlot":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "### Scene":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "% How about a comment?":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
for c in "@pov: Jane":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
for c in "@plot: MainPlot":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
for c in "@location: Home":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "#### Some Section":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "@char: Jane":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "This is a paragraph of nonsense text.":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
# Don't allow Shift+Enter to insert a line separator (issue #1150)
|
||||
for c in "This is another paragraph":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Enter, modifier=Qt.ShiftModifier, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Enter, modifier=Qt.ShiftModifier, delay=KEY_DELAY)
|
||||
for c in "with a line separator in it.":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
# Auto-Replace
|
||||
# ============
|
||||
@@ -386,43 +372,43 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
"This is another paragraph of much longer nonsense text. "
|
||||
"It is in fact 1 very very NONSENSICAL nonsense text! "
|
||||
):
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
for c in "We can also try replacing \"quotes\", even single 'quotes' are replaced. ":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
for c in "Isn't that nice? ":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
for c in "We can hyphen-ate, make dashes -- and even longer dashes --- if we want. ":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
for c in "Ellipsis? Not a problem either ... ":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
for c in "How about three hyphens - -":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Left, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Backspace, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Right, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Left, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Backspace, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Right, delay=KEY_DELAY)
|
||||
for c in "- for long dash? It works too.":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "\"Full line double quoted text.\"":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "'Full line single quoted text.'":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
# Insert spaces before and after quotes
|
||||
nwGUI.mainConf.fmtPadBefore = "\u201d"
|
||||
nwGUI.mainConf.fmtPadAfter = "\u201c"
|
||||
|
||||
for c in "Some \"double quoted text with spaces padded\".":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
nwGUI.mainConf.fmtPadBefore = ""
|
||||
nwGUI.mainConf.fmtPadAfter = ""
|
||||
@@ -431,24 +417,24 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
nwGUI.mainConf.fmtPadBefore = ":"
|
||||
|
||||
for c in "@object: NoSpaceAdded":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "% synopsis: No space before this colon.":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "Add space before this colon: See?":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "But don't add a double space : See?":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
nwGUI.mainConf.fmtPadBefore = ""
|
||||
|
||||
@@ -456,55 +442,49 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
# ================
|
||||
|
||||
for c in "\t\"Tab-indented text\"":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in ">\"Paragraph-indented text\"":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in ">>\"Right-aligned text\"":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in "\t'Tab-indented text'":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in ">'Paragraph-indented text'":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
for c in ">>'Right-aligned text'":
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY)
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
nwGUI.docEditor.wCounterDoc.run()
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Save the document
|
||||
assert nwGUI.docEditor.docChanged()
|
||||
assert nwGUI.saveDocument()
|
||||
assert not nwGUI.docEditor.docChanged()
|
||||
qtbot.wait(stepDelay)
|
||||
nwGUI.rebuildIndex()
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Open and view the edited document
|
||||
nwGUI.switchFocus(nwWidget.VIEWER)
|
||||
assert nwGUI.openDocument(C.hSceneDoc)
|
||||
assert nwGUI.viewDocument(C.hSceneDoc)
|
||||
qtbot.wait(stepDelay)
|
||||
assert nwGUI.saveProject()
|
||||
assert nwGUI.closeDocViewer()
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Check a Quick Create and Delete
|
||||
assert nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None)
|
||||
@@ -553,11 +533,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiMain_FocusFullMode(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
def testGuiMain_FocusFullMode(qtbot, nwGUI, fncProj, mockRnd):
|
||||
"""Test toggling focus mode in main window.
|
||||
"""
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
assert nwGUI.isFocusMode is False
|
||||
|
||||
@@ -596,6 +574,6 @@ def testGuiMain_FocusFullMode(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
nwGUI.toggleFullScreenMode()
|
||||
assert nwGUI.mainConf.isFullScreen is False
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiMain_FocusFullMode
|
||||
|
||||
@@ -32,24 +32,17 @@ from novelwriter.enum import nwDocAction, nwDocInsert
|
||||
from novelwriter.constants import nwKeyWords, nwUnicode
|
||||
from novelwriter.gui.doceditor import GuiDocEditor
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
"""Test the main menu Edit and Format entries.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
|
||||
|
||||
# Test Document Action with No Project
|
||||
assert nwGUI.docEditor.docAction(nwDocAction.COPY) is False
|
||||
|
||||
assert nwGUI.openProject(nwLipsum) is True
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Split By Chapter
|
||||
assert nwGUI.openDocument("4c4f28287af27") is True
|
||||
@@ -61,57 +54,43 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger)
|
||||
fmtStr = "**Pellentesque** nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:90] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText()[39:86] == cleanText
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Italic
|
||||
nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger)
|
||||
fmtStr = "_Pellentesque_ nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:88] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText()[39:86] == cleanText
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Strikethrough
|
||||
nwGUI.mainMenu.aFmtStrike.activate(QAction.Trigger)
|
||||
fmtStr = "~~Pellentesque~~ nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:90] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
nwGUI.mainMenu.aFmtStrike.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText()[39:86] == cleanText
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Should get us back to plain
|
||||
nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger)
|
||||
qtbot.wait(stepDelay)
|
||||
nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger)
|
||||
qtbot.wait(stepDelay)
|
||||
nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger)
|
||||
qtbot.wait(stepDelay)
|
||||
nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText()[39:86] == cleanText
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Double Quotes
|
||||
nwGUI.mainMenu.aFmtDQuote.activate(QAction.Trigger)
|
||||
fmtStr = "“Pellentesque” nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:88] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText()[39:86] == cleanText
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Single Quotes
|
||||
nwGUI.mainMenu.aFmtSQuote.activate(QAction.Trigger)
|
||||
fmtStr = "‘Pellentesque’ nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:88] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText()[39:86] == cleanText
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Block Formats
|
||||
# =============
|
||||
@@ -121,61 +100,60 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
nwGUI.mainMenu.aFmtHead1.activate(QAction.Trigger)
|
||||
fmtStr = "# Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:88] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Header 2
|
||||
nwGUI.mainMenu.aFmtHead2.activate(QAction.Trigger)
|
||||
fmtStr = "## Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:89] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Header 3
|
||||
nwGUI.mainMenu.aFmtHead3.activate(QAction.Trigger)
|
||||
fmtStr = "### Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:90] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Header 4
|
||||
nwGUI.mainMenu.aFmtHead4.activate(QAction.Trigger)
|
||||
fmtStr = "#### Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:91] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Title Format
|
||||
nwGUI.mainMenu.aFmtTitle.activate(QAction.Trigger)
|
||||
fmtStr = "#! Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:89] == fmtStr
|
||||
|
||||
# Unnumbered Chapter
|
||||
nwGUI.mainMenu.aFmtUnNum.activate(QAction.Trigger)
|
||||
fmtStr = "##! Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:90] == fmtStr
|
||||
|
||||
# Clear Format
|
||||
nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText()[39:86] == cleanText
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Comment On
|
||||
nwGUI.mainMenu.aFmtComment.activate(QAction.Trigger)
|
||||
fmtStr = "% Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:88] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Comment Off
|
||||
nwGUI.mainMenu.aFmtComment.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText()[39:86] == cleanText
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Check comment with no space before text
|
||||
assert nwGUI.docEditor.setCursorPosition(39)
|
||||
assert nwGUI.docEditor.insertText("%")
|
||||
fmtStr = "%Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:87] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText()[39:86] == cleanText
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Undo/Redo
|
||||
nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
|
||||
fmtStr = "%Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert nwGUI.docEditor.getText()[39:87] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
nwGUI.mainMenu.aEditRedo.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText()[39:86] == cleanText
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Cut, Copy and Paste
|
||||
assert nwGUI.docEditor.setCursorPosition(39)
|
||||
@@ -240,36 +218,30 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
nwGUI.mainMenu.aFmtAlignLeft.activate(QAction.Trigger)
|
||||
fmtStr = "A single, short paragraph. <<"
|
||||
assert nwGUI.docEditor.getText()[:29] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Right Align
|
||||
nwGUI.mainMenu.aFmtAlignRight.activate(QAction.Trigger)
|
||||
fmtStr = ">> A single, short paragraph."
|
||||
assert nwGUI.docEditor.getText()[:29] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Centre Align
|
||||
nwGUI.mainMenu.aFmtAlignCentre.activate(QAction.Trigger)
|
||||
fmtStr = ">> A single, short paragraph. <<"
|
||||
assert nwGUI.docEditor.getText()[:32] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Left Indent
|
||||
nwGUI.mainMenu.aFmtIndentLeft.activate(QAction.Trigger)
|
||||
fmtStr = "> A single, short paragraph."
|
||||
assert nwGUI.docEditor.getText()[:28] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Right Indent
|
||||
nwGUI.mainMenu.aFmtIndentRight.activate(QAction.Trigger)
|
||||
fmtStr = "> A single, short paragraph. <"
|
||||
assert nwGUI.docEditor.getText()[:30] == fmtStr
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# No Format
|
||||
nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText()[:30] == cleanText
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Other Checks
|
||||
|
||||
@@ -352,10 +324,6 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
assert nwGUI.docEditor.setCursorPosition(17)
|
||||
assert not nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT)
|
||||
|
||||
# Cannot Format Empty Line
|
||||
assert nwGUI.docEditor.setCursorPosition(13)
|
||||
assert not nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT)
|
||||
|
||||
# Invalid Action
|
||||
assert nwGUI.docEditor.setCursorPosition(30)
|
||||
assert not nwGUI.docEditor._formatBlock(nwDocAction.NO_ACTION)
|
||||
@@ -368,21 +336,17 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
"Also text with \"double\" quotes which are \"less tricky\".\n\n"
|
||||
)
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiMenu_EditFormat
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiMenu_ContextMenus(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
def testGuiMenu_ContextMenus(qtbot, nwGUI, nwLipsum):
|
||||
"""Test the context menus.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
|
||||
assert nwGUI.openProject(nwLipsum)
|
||||
assert nwGUI.openDocument("4c4f28287af27")
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Editor Context Menu
|
||||
theCursor = nwGUI.docEditor.textCursor()
|
||||
@@ -452,7 +416,7 @@ def testGuiMenu_ContextMenus(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
assert nwGUI.docViewer.docHeader.backButton.isEnabled()
|
||||
assert not nwGUI.docViewer.docHeader.forwardButton.isEnabled()
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiMenu_ContextMenus
|
||||
|
||||
@@ -461,10 +425,6 @@ def testGuiMenu_ContextMenus(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
"""Test the Insert menu.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
|
||||
assert nwGUI.projView.projTree._getTreeItem(C.hSceneDoc) is not None
|
||||
@@ -482,7 +442,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
assert nwGUI.docEditor.insertText(None) is False
|
||||
assert nwGUI.docEditor.isEmpty()
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# Check Menu Entries
|
||||
nwGUI.mainMenu.aInsENDash.activate(QAction.Trigger)
|
||||
@@ -577,9 +537,8 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
assert nwGUI.docEditor.getText() == nwUnicode.U_THNBSP
|
||||
nwGUI.docEditor.clear()
|
||||
|
||||
##
|
||||
# Insert Keywords
|
||||
##
|
||||
# Insert Keywords
|
||||
# ===============
|
||||
|
||||
nwGUI.docEditor.setText("Stuff")
|
||||
nwGUI.mainMenu.mInsKWItems[nwKeyWords.TAG_KEY][0].activate(QAction.Trigger)
|
||||
@@ -629,9 +588,15 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
|
||||
nwGUI.docEditor.clear()
|
||||
|
||||
##
|
||||
# Insert Break or Space
|
||||
##
|
||||
# Insert Special Comments
|
||||
# =======================
|
||||
|
||||
nwGUI.docEditor.setText("Stuff\n")
|
||||
nwGUI.mainMenu.aInsSynopsis.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "Stuff\n% Synopsis: \n"
|
||||
|
||||
# Insert Break or Space
|
||||
# =====================
|
||||
|
||||
nwGUI.docEditor.setText("### Stuff\n")
|
||||
nwGUI.mainMenu.aInsNewPage.activate(QAction.Trigger)
|
||||
@@ -647,9 +612,8 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
|
||||
nwGUI.docEditor.clear()
|
||||
|
||||
##
|
||||
# Insert text from file
|
||||
##
|
||||
# Insert Text from File
|
||||
# =====================
|
||||
|
||||
nwGUI.closeDocument()
|
||||
|
||||
@@ -676,18 +640,17 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
assert nwGUI.docEditor.getText() == "Bar"
|
||||
|
||||
# The document isn't empty, so the message box should pop
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a, **k: QMessageBox.No)
|
||||
assert not nwGUI.importDocument()
|
||||
assert nwGUI.docEditor.getText() == "Bar"
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(QMessageBox, "question", lambda *a, **k: QMessageBox.No)
|
||||
assert not nwGUI.importDocument()
|
||||
assert nwGUI.docEditor.getText() == "Bar"
|
||||
|
||||
# Finally, accept the replaced text, this time we use the menu entry to trigger it
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a, **k: QMessageBox.Yes)
|
||||
nwGUI.mainMenu.aImportFile.activate(QAction.Trigger)
|
||||
assert nwGUI.docEditor.getText() == "Foo"
|
||||
|
||||
##
|
||||
# Reveal file location
|
||||
##
|
||||
# Reveal File Location
|
||||
# ====================
|
||||
|
||||
theMessage = ""
|
||||
|
||||
@@ -705,6 +668,6 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
assert theBits[0] == "The currently open file is saved in:"
|
||||
assert theBits[1] == os.path.join(fncProj, "content", "000000000000f.nwd")
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiMenu_Insert
|
||||
|
||||
@@ -26,7 +26,7 @@ from tools import C, buildTestProject, writeFile
|
||||
|
||||
from PyQt5.QtGui import QFocusEvent
|
||||
from PyQt5.QtCore import Qt, QEvent
|
||||
from PyQt5.QtWidgets import QMessageBox, QToolTip
|
||||
from PyQt5.QtWidgets import QToolTip
|
||||
|
||||
from novelwriter.enum import nwWidget, nwItemType
|
||||
from novelwriter.gui.noveltree import NovelTreeColumn
|
||||
@@ -37,9 +37,6 @@ from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
"""Test navigating the novel tree.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
|
||||
@@ -26,7 +26,7 @@ import pytest
|
||||
from tools import buildTestProject, writeFile
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QWidget, QMessageBox, QAction
|
||||
from PyQt5.QtWidgets import QWidget, QAction
|
||||
|
||||
from novelwriter.enum import nwItemClass, nwOutline, nwView
|
||||
|
||||
@@ -35,10 +35,6 @@ from novelwriter.enum import nwItemClass, nwOutline, nwView
|
||||
def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, fncDir):
|
||||
"""Test the outline view.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
@@ -156,13 +152,9 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, fncDir):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
def testGuiOutline_Content(qtbot, nwGUI, nwLipsum):
|
||||
"""Test the outline view.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
|
||||
assert nwGUI.openProject(nwLipsum)
|
||||
nwGUI.mainConf.lastPath = nwLipsum
|
||||
|
||||
@@ -183,7 +175,7 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
|
||||
# Add a second novel folder
|
||||
newHandle = nwGUI.theProject.newRoot(nwItemClass.NOVEL)
|
||||
nwGUI.projView.revealNewTreeItem(newHandle)
|
||||
nwGUI.projView.projTree.revealNewTreeItem(newHandle)
|
||||
|
||||
# Check new values in dropdown list
|
||||
assert outlineBar.novelValue.itemData(0) == lipHandle
|
||||
@@ -202,7 +194,7 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
aHandle = nwGUI.theProject.newFile(dTitle, newHandle)
|
||||
hHash = "#"*hLevel
|
||||
writeFile(os.path.join(nwLipsum, "content", f"{aHandle}.nwd"), f"{hHash} {dTitle}\n\n")
|
||||
nwGUI.projView.revealNewTreeItem(aHandle)
|
||||
nwGUI.projView.projTree.revealNewTreeItem(aHandle)
|
||||
|
||||
nwGUI.rebuildIndex()
|
||||
|
||||
|
||||
@@ -40,11 +40,6 @@ from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
"""Test adding and removing items from the project tree.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
projView = nwGUI.projView
|
||||
@@ -160,11 +155,11 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd)
|
||||
# ============
|
||||
|
||||
# Also check error handling in reveal function
|
||||
assert projView.revealNewTreeItem("abc") is False
|
||||
assert projView.projTree.revealNewTreeItem("abc") is False
|
||||
|
||||
# Add an item that cannot be displayed in the tree
|
||||
nHandle = theProject.newFile("Test", None)
|
||||
assert projView.revealNewTreeItem(nHandle) is False
|
||||
assert projView.projTree.revealNewTreeItem(nHandle) is False
|
||||
|
||||
# Clean up
|
||||
# qtbot.stop()
|
||||
@@ -177,17 +172,13 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd)
|
||||
def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
"""Test adding and removing items from the project tree.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
nwTree = nwGUI.projView
|
||||
projView = nwGUI.projView
|
||||
projTree = nwGUI.projView.projTree
|
||||
|
||||
# Try to move item with no project
|
||||
assert nwTree.projTree.moveTreeItem(1) is False
|
||||
assert projView.projTree.moveTreeItem(1) is False
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
@@ -197,68 +188,68 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
# ==============
|
||||
|
||||
# Add some files
|
||||
nwTree.setSelectedHandle(C.hChapterDir)
|
||||
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
projView.setSelectedHandle(C.hChapterDir)
|
||||
assert projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
|
||||
# Move with no selections
|
||||
nwTree.projTree.clearSelection()
|
||||
assert nwTree.projTree.moveTreeItem(1) is False
|
||||
projTree.clearSelection()
|
||||
assert projTree.moveTreeItem(1) is False
|
||||
|
||||
# Move second item up twice (should give same result)
|
||||
nwTree.setSelectedHandle(C.hSceneDoc)
|
||||
assert nwTree.projTree.moveTreeItem(-1) is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
projView.setSelectedHandle(C.hSceneDoc)
|
||||
assert projTree.moveTreeItem(-1) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hSceneDoc, C.hChapterDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
assert nwTree.projTree.moveTreeItem(-1) is False
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
assert projTree.moveTreeItem(-1) is False
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hSceneDoc, C.hChapterDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
|
||||
# Restore
|
||||
assert nwTree.projTree.moveTreeItem(1) is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
assert projTree.moveTreeItem(1) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
|
||||
# Move fifth item down twice (should give same result)
|
||||
nwTree.setSelectedHandle("0000000000011")
|
||||
assert nwTree.projTree.moveTreeItem(1) is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
projView.setSelectedHandle("0000000000011")
|
||||
assert projTree.moveTreeItem(1) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000012", "0000000000011",
|
||||
]
|
||||
assert nwTree.projTree.moveTreeItem(1) is False
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
assert projTree.moveTreeItem(1) is False
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000012", "0000000000011",
|
||||
]
|
||||
|
||||
# Restore
|
||||
assert nwTree.projTree.moveTreeItem(-1) is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
assert projTree.moveTreeItem(-1) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
|
||||
# Move down again, and restore via undo
|
||||
nwTree.setSelectedHandle("0000000000011")
|
||||
assert nwTree.projTree.moveTreeItem(1) is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
projView.setSelectedHandle("0000000000011")
|
||||
assert projTree.moveTreeItem(1) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000012", "0000000000011",
|
||||
]
|
||||
assert nwTree.projTree.undoLastMove() is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
assert projTree.undoLastMove() is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
@@ -266,23 +257,23 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
# Root Folder
|
||||
# ===========
|
||||
|
||||
nwTree.setSelectedHandle(C.hNovelRoot)
|
||||
projView.setSelectedHandle(C.hNovelRoot)
|
||||
assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 0
|
||||
|
||||
# Move novel folder up
|
||||
assert nwTree.projTree.moveTreeItem(-1) is False
|
||||
assert projTree.moveTreeItem(-1) is False
|
||||
assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 0
|
||||
|
||||
# Move novel folder down
|
||||
assert nwTree.projTree.moveTreeItem(1) is True
|
||||
assert projTree.moveTreeItem(1) is True
|
||||
assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 1
|
||||
|
||||
# Move novel folder up again
|
||||
assert nwTree.projTree.moveTreeItem(-1) is True
|
||||
assert projTree.moveTreeItem(-1) is True
|
||||
assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 0
|
||||
|
||||
# Clean up
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
nwGUI.closeProject()
|
||||
|
||||
# END Test testGuiProjTree_MoveItems
|
||||
@@ -292,83 +283,79 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
def testGuiProjTree_RequestDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
"""Test external requests for removing items from project tree.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
nwView = nwGUI.projView
|
||||
projView = nwGUI.projView
|
||||
projTree = nwGUI.projView.projTree
|
||||
|
||||
# Try to run with no project
|
||||
assert nwView.requestDeleteItem() is False
|
||||
assert projView.requestDeleteItem() is False
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
|
||||
# Try emptying the trash already now, when there is no trash folder
|
||||
assert nwView.emptyTrash() is False
|
||||
assert projView.emptyTrash() is False
|
||||
|
||||
# Add some files
|
||||
nwView.setSelectedHandle(C.hChapterDir)
|
||||
assert nwView.projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert nwView.projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert nwView.projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert nwView.getTreeFromHandle(C.hChapterDir) == [
|
||||
projView.setSelectedHandle(C.hChapterDir)
|
||||
assert projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
|
||||
# Delete item without focus -> blocked
|
||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
|
||||
nwView.setSelectedHandle("0000000000012")
|
||||
assert nwView.requestDeleteItem() is False
|
||||
projView.setSelectedHandle("0000000000012")
|
||||
assert projView.requestDeleteItem() is False
|
||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
|
||||
|
||||
# No selection made
|
||||
nwView.projTree.clearSelection()
|
||||
projTree.clearSelection()
|
||||
caplog.clear()
|
||||
assert nwView.requestDeleteItem() is False
|
||||
assert projView.requestDeleteItem() is False
|
||||
assert "no item to delete" in caplog.text
|
||||
|
||||
# Not a valid handle
|
||||
nwView.projTree.clearSelection()
|
||||
projTree.clearSelection()
|
||||
caplog.clear()
|
||||
assert nwView.requestDeleteItem("0000000000000") is False
|
||||
assert projView.requestDeleteItem("0000000000000") is False
|
||||
assert "No tree item with handle '0000000000000'" in caplog.text
|
||||
|
||||
# Delete Root Folders
|
||||
# ===================
|
||||
|
||||
assert nwView.requestDeleteItem(C.hNovelRoot) is False # Novel Root is blocked
|
||||
assert nwView.requestDeleteItem(C.hCharRoot) is True # Character Root
|
||||
assert projView.requestDeleteItem(C.hNovelRoot) is False # Novel Root is blocked
|
||||
assert projView.requestDeleteItem(C.hCharRoot) is True # Character Root
|
||||
|
||||
# Delete File
|
||||
# ===========
|
||||
|
||||
# Block adding trash folder
|
||||
funcPointer = nwView.projTree._addTrashRoot
|
||||
nwView.projTree._addTrashRoot = lambda *a: None
|
||||
assert nwView.requestDeleteItem("0000000000012") is False
|
||||
nwView.projTree._addTrashRoot = funcPointer
|
||||
funcPointer = projTree._addTrashRoot
|
||||
projTree._addTrashRoot = lambda *a: None
|
||||
assert projView.requestDeleteItem("0000000000012") is False
|
||||
projTree._addTrashRoot = funcPointer
|
||||
|
||||
# Delete last two documents, which also adds the trash folder
|
||||
assert nwView.requestDeleteItem("0000000000012") is True
|
||||
assert nwView.requestDeleteItem("0000000000011") is True
|
||||
assert nwView.getTreeFromHandle(C.hChapterDir) == [
|
||||
assert projView.requestDeleteItem("0000000000012") is True
|
||||
assert projView.requestDeleteItem("0000000000011") is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010"
|
||||
]
|
||||
trashHandle = nwGUI.theProject.tree.trashRoot()
|
||||
assert nwView.getTreeFromHandle(trashHandle) == [
|
||||
assert projTree.getTreeFromHandle(trashHandle) == [
|
||||
trashHandle, "0000000000012", "0000000000011"
|
||||
]
|
||||
|
||||
# Try to delete the trash folder
|
||||
caplog.clear()
|
||||
assert nwView.requestDeleteItem("0000000000013") is False
|
||||
assert projView.requestDeleteItem("0000000000013") is False
|
||||
assert "Cannot delete the Trash folder" in caplog.text
|
||||
|
||||
nwGUI.closeProject()
|
||||
@@ -380,11 +367,6 @@ def testGuiProjTree_RequestDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fncDir,
|
||||
def testGuiProjTree_MoveItemToTrash(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
"""Test moving items to Trash.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
theProject = nwGUI.theProject
|
||||
@@ -439,11 +421,6 @@ def testGuiProjTree_MoveItemToTrash(qtbot, caplog, monkeypatch, nwGUI, fncDir, m
|
||||
def testGuiProjTree_PermanentlyDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
"""Test permanently deleting items.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
theProject = nwGUI.theProject
|
||||
@@ -497,11 +474,6 @@ def testGuiProjTree_PermanentlyDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fnc
|
||||
def testGuiProjTree_EmptyTrash(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
"""Test emptying Trash.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
theProject = nwGUI.theProject
|
||||
@@ -557,11 +529,6 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
"""Test the building of the project tree context menu. All this does
|
||||
is test that the menu builds. It doesn't open the actual menu,
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
monkeypatch.setattr(QMenu, "exec_", lambda *a: None)
|
||||
|
||||
@@ -682,12 +649,6 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, i
|
||||
"""
|
||||
mergeData = {}
|
||||
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
|
||||
monkeypatch.setattr(GuiDocMerge, "__init__", lambda *a: None)
|
||||
monkeypatch.setattr(GuiDocMerge, "exec_", lambda *a: None)
|
||||
monkeypatch.setattr(GuiDocMerge, "result", lambda *a: QDialog.Accepted)
|
||||
@@ -792,12 +753,6 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, ip
|
||||
splitData = {}
|
||||
splitText = []
|
||||
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
|
||||
monkeypatch.setattr(GuiDocSplit, "__init__", lambda *a: None)
|
||||
monkeypatch.setattr(GuiDocSplit, "exec_", lambda *a: None)
|
||||
monkeypatch.setattr(GuiDocSplit, "result", lambda *a: QDialog.Accepted)
|
||||
@@ -882,14 +837,17 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, ip
|
||||
assert tHandle in theProject.tree
|
||||
assert os.path.isfile(os.path.join(prjDir, "content", f"{tHandle}.nwd"))
|
||||
|
||||
# Add to a folder
|
||||
# Add to a folder and move source to trash
|
||||
splitData["intoFolder"] = True
|
||||
splitData["moveToTrash"] = True
|
||||
assert projTree._splitDocument(hSplitDoc) is True
|
||||
assert "0000000000029" in theProject.tree # The folder
|
||||
for tHandle in trdSet:
|
||||
assert tHandle in theProject.tree
|
||||
assert os.path.isfile(os.path.join(prjDir, "content", f"{tHandle}.nwd"))
|
||||
|
||||
assert theProject.tree.isTrash(hSplitDoc) is True
|
||||
|
||||
# Cancelled by user
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(GuiDocSplit, "result", lambda *a: QDialog.Rejected)
|
||||
@@ -905,12 +863,6 @@ def testGuiProjTree_Other(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
"""Test various parts of the project tree class not covered by
|
||||
other tests.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
|
||||
@@ -24,23 +24,19 @@ import pytest
|
||||
|
||||
from tools import C, buildTestProject
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from novelwriter.enum import nwState
|
||||
from novelwriter.core.document import NWDoc
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
def testGuiStatusBar_Main(qtbot, nwGUI, fncProj, mockRnd):
|
||||
"""Test the the various features of the status bar.
|
||||
"""
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
cHandle = nwGUI.theProject.newFile("A Note", C.hCharRoot)
|
||||
newDoc = NWDoc(nwGUI.theProject, cHandle)
|
||||
newDoc.writeDocument("# A Note\n\n")
|
||||
nwGUI.projView.revealNewTreeItem(cHandle)
|
||||
nwGUI.projView.projTree.revealNewTreeItem(cHandle)
|
||||
nwGUI.rebuildIndex(beQuiet=True)
|
||||
|
||||
# Reference Time
|
||||
|
||||
@@ -19,161 +19,428 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import pytest
|
||||
import novelwriter
|
||||
|
||||
from PyQt5.QtGui import QColor, QPixmap, QIcon
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
from configparser import ConfigParser
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
from mock import causeOSError
|
||||
from novelwriter.constants import nwLabels
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
||||
from tools import writeFile
|
||||
|
||||
from PyQt5.QtGui import QIcon, QPalette, QPixmap
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from novelwriter.config import Config
|
||||
from novelwriter.gui.theme import GuiIcons, GuiTheme
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
|
||||
"""Test the theme and icon classes.
|
||||
def testGuiTheme_Main(qtbot, nwGUI, fncDir):
|
||||
"""Test the theme class init.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
mainTheme: GuiTheme = nwGUI.mainTheme
|
||||
mainConf: Config = nwGUI.mainConf
|
||||
|
||||
nwGUI = novelwriter.main(
|
||||
["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal]
|
||||
)
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
qtbot.wait(stepDelay)
|
||||
# Methods
|
||||
# =======
|
||||
|
||||
# Change Settings
|
||||
assert novelwriter.CONFIG.confPath == nwMinimal
|
||||
novelwriter.CONFIG.guiTheme = "default_dark"
|
||||
novelwriter.CONFIG.guiSyntax = "tomorrow_night_eighties"
|
||||
novelwriter.CONFIG.guiIcons = "typicons_colour_dark"
|
||||
novelwriter.CONFIG.guiFont = "Cantarell"
|
||||
novelwriter.CONFIG.guiFontSize = 11
|
||||
novelwriter.CONFIG.confChanged = True
|
||||
assert novelwriter.CONFIG.saveConfig()
|
||||
mSize = mainTheme.getTextWidth("m")
|
||||
assert mSize > 0
|
||||
assert mainTheme.getTextWidth("m", mainTheme.guiFont) == mSize
|
||||
|
||||
nwGUI.closeMain()
|
||||
nwGUI.close()
|
||||
del nwGUI
|
||||
# Init Fonts
|
||||
# ==========
|
||||
|
||||
# Re-open
|
||||
assert novelwriter.CONFIG.confPath == nwMinimal
|
||||
nwGUI = novelwriter.main(
|
||||
["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal]
|
||||
)
|
||||
assert nwGUI.mainConf.confPath == nwMinimal
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
qtbot.wait(stepDelay)
|
||||
# The defaults should be set
|
||||
defaultFont = mainConf.guiFont
|
||||
defaultSize = mainConf.guiFontSize
|
||||
|
||||
assert novelwriter.CONFIG.guiTheme == "default_dark"
|
||||
assert novelwriter.CONFIG.guiSyntax == "tomorrow_night_eighties"
|
||||
assert novelwriter.CONFIG.guiIcons == "typicons_dark"
|
||||
assert novelwriter.CONFIG.guiFont != ""
|
||||
assert novelwriter.CONFIG.guiFontSize > 0
|
||||
# CHange them to nonsense values
|
||||
mainConf.guiFont = "notafont"
|
||||
mainConf.guiFontSize = 99
|
||||
|
||||
# Check GUI Colours
|
||||
thePalette = nwGUI.palette()
|
||||
assert thePalette.window().color() == QColor(54, 54, 54)
|
||||
assert thePalette.windowText().color() == QColor(174, 174, 174)
|
||||
assert thePalette.base().color() == QColor(62, 62, 62)
|
||||
assert thePalette.alternateBase().color() == QColor(78, 78, 78)
|
||||
assert thePalette.text().color() == QColor(174, 174, 174)
|
||||
assert thePalette.toolTipBase().color() == QColor(255, 255, 192)
|
||||
assert thePalette.toolTipText().color() == QColor(21, 21, 13)
|
||||
assert thePalette.button().color() == QColor(62, 62, 62)
|
||||
assert thePalette.buttonText().color() == QColor(174, 174, 174)
|
||||
assert thePalette.brightText().color() == QColor(174, 174, 174)
|
||||
assert thePalette.highlight().color() == QColor(44, 152, 247)
|
||||
assert thePalette.highlightedText().color() == QColor(255, 255, 255)
|
||||
assert thePalette.link().color() == QColor(44, 152, 247)
|
||||
assert thePalette.linkVisited().color() == QColor(44, 152, 247)
|
||||
# Let the theme class set them back to default
|
||||
mainTheme._setGuiFont()
|
||||
assert mainConf.guiFont == defaultFont
|
||||
assert mainConf.guiFontSize == defaultSize
|
||||
|
||||
assert nwGUI.mainTheme.statNone == [150, 152, 150]
|
||||
assert nwGUI.mainTheme.statSaved == [39, 135, 78]
|
||||
assert nwGUI.mainTheme.statUnsaved == [138, 32, 32]
|
||||
# A second call should just restore the defaults again
|
||||
mainTheme._setGuiFont()
|
||||
assert mainConf.guiFont == defaultFont
|
||||
assert mainConf.guiFontSize == defaultSize
|
||||
|
||||
# Check Syntax Colours
|
||||
assert nwGUI.mainTheme.colBack == [45, 45, 45]
|
||||
assert nwGUI.mainTheme.colText == [204, 204, 204]
|
||||
assert nwGUI.mainTheme.colLink == [102, 153, 204]
|
||||
assert nwGUI.mainTheme.colHead == [102, 153, 204]
|
||||
assert nwGUI.mainTheme.colHeadH == [102, 153, 204]
|
||||
assert nwGUI.mainTheme.colEmph == [249, 145, 57]
|
||||
assert nwGUI.mainTheme.colDialN == [242, 119, 122]
|
||||
assert nwGUI.mainTheme.colDialD == [153, 204, 153]
|
||||
assert nwGUI.mainTheme.colDialS == [255, 204, 102]
|
||||
assert nwGUI.mainTheme.colHidden == [153, 153, 153]
|
||||
assert nwGUI.mainTheme.colKey == [242, 119, 122]
|
||||
assert nwGUI.mainTheme.colVal == [204, 153, 204]
|
||||
assert nwGUI.mainTheme.colSpell == [242, 119, 122]
|
||||
assert nwGUI.mainTheme.colError == [153, 204, 153]
|
||||
assert nwGUI.mainTheme.colRepTag == [102, 204, 204]
|
||||
assert nwGUI.mainTheme.colMod == [249, 145, 57]
|
||||
# Scan for Themes
|
||||
# ===============
|
||||
|
||||
# Test Icon class
|
||||
iconCache = nwGUI.mainTheme.iconCache
|
||||
novelwriter.CONFIG.guiIcons = "invalid"
|
||||
assert iconCache.updateTheme() is True
|
||||
assert novelwriter.CONFIG.guiIcons == "typicons_light"
|
||||
assert mainTheme._listConf({}, "not_a_path") is False
|
||||
|
||||
# Ask for a non-existent key
|
||||
anImg = iconCache.loadDecoration("nonsense", 20, 20)
|
||||
assert isinstance(anImg, QPixmap)
|
||||
assert anImg.isNull()
|
||||
themeOne = os.path.join(fncDir, "themes", "themeone.conf")
|
||||
themeTwo = os.path.join(fncDir, "themes", "themetwo.conf")
|
||||
writeFile(themeOne, "# Stuff")
|
||||
writeFile(themeTwo, "# Stuff")
|
||||
|
||||
# Add a non-existent file and request it
|
||||
iconCache.IMAGE_MAP["nonsense"] = "nofile.jpg"
|
||||
anImg = iconCache.loadDecoration("nonsense", 20, 20)
|
||||
assert isinstance(anImg, QPixmap)
|
||||
assert anImg.isNull()
|
||||
result = {}
|
||||
assert mainTheme._listConf(result, os.path.join(fncDir, "themes")) is True
|
||||
assert result["themeone"] == themeOne
|
||||
assert result["themetwo"] == themeTwo
|
||||
|
||||
# Get a real image, with different size parameters
|
||||
anImg = iconCache.loadDecoration("wiz-back", 20, None)
|
||||
assert isinstance(anImg, QPixmap)
|
||||
assert not anImg.isNull()
|
||||
assert anImg.width() == 20
|
||||
assert anImg.height() >= 56
|
||||
# Parse Colours
|
||||
# =============
|
||||
|
||||
anImg = iconCache.loadDecoration("wiz-back", None, 70)
|
||||
assert isinstance(anImg, QPixmap)
|
||||
assert not anImg.isNull()
|
||||
assert anImg.height() == 70
|
||||
assert anImg.width() >= 24
|
||||
parser = ConfigParser()
|
||||
parser["Palette"] = {
|
||||
"colour1": "100, 150, 200",
|
||||
"colour2": "100, 150, 200, 250",
|
||||
"colour3": "250, 250",
|
||||
"colour4": "-10, 127, 300",
|
||||
}
|
||||
|
||||
anImg = iconCache.loadDecoration("wiz-back", 30, 70)
|
||||
assert isinstance(anImg, QPixmap)
|
||||
assert not anImg.isNull()
|
||||
assert anImg.height() == 70
|
||||
assert anImg.width() == 30
|
||||
# Test the parser for several valid and invalid values
|
||||
assert mainTheme._parseColour(parser, "Palette", "colour1") == [100, 150, 200]
|
||||
assert mainTheme._parseColour(parser, "Palette", "colour2") == [100, 150, 200]
|
||||
assert mainTheme._parseColour(parser, "Palette", "colour3") == [0, 0, 0]
|
||||
assert mainTheme._parseColour(parser, "Palette", "colour4") == [0, 127, 255]
|
||||
assert mainTheme._parseColour(parser, "Palette", "colour5") == [0, 0, 0]
|
||||
|
||||
anImg = iconCache.loadDecoration("wiz-back", None, None)
|
||||
assert isinstance(anImg, QPixmap)
|
||||
assert not anImg.isNull()
|
||||
assert anImg.height() >= 1500
|
||||
assert anImg.width() >= 500
|
||||
# The palette should load with the parsed values
|
||||
mainTheme._setPalette(parser, "Palette", "colour1", QPalette.Window)
|
||||
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (100, 150, 200, 255)
|
||||
mainTheme._setPalette(parser, "Palette", "colour2", QPalette.Window)
|
||||
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (100, 150, 200, 255)
|
||||
mainTheme._setPalette(parser, "Palette", "colour3", QPalette.Window)
|
||||
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (0, 0, 0, 255)
|
||||
mainTheme._setPalette(parser, "Palette", "colour4", QPalette.Window)
|
||||
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (0, 127, 255, 255)
|
||||
mainTheme._setPalette(parser, "Palette", "colour5", QPalette.Window)
|
||||
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (0, 0, 0, 255)
|
||||
|
||||
# Load icons
|
||||
anIcon = iconCache.getIcon("nonsense")
|
||||
assert isinstance(anIcon, QIcon)
|
||||
assert anIcon.isNull()
|
||||
|
||||
anIcon = iconCache.getIcon("novelwriter")
|
||||
assert isinstance(anIcon, QIcon)
|
||||
assert not anIcon.isNull()
|
||||
|
||||
# Check return empty icon if file not found
|
||||
iconCache.ICON_KEYS.add("testicon3")
|
||||
anIcon = iconCache.getIcon("testicon3")
|
||||
assert isinstance(anIcon, QIcon)
|
||||
assert anIcon.isNull()
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
nwGUI.closeMain()
|
||||
nwGUI.close()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiTheme_Main
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncDir):
|
||||
"""Test the theme part of the class.
|
||||
"""
|
||||
mainTheme: GuiTheme = nwGUI.mainTheme
|
||||
mainConf: Config = nwGUI.mainConf
|
||||
|
||||
# List Themes
|
||||
# ===========
|
||||
|
||||
shutil.copy(
|
||||
os.path.join(mainConf.assetPath, "themes", "default_dark.conf"),
|
||||
os.path.join(fncDir, "themes")
|
||||
)
|
||||
shutil.copy(
|
||||
os.path.join(mainConf.assetPath, "themes", "default.conf"),
|
||||
os.path.join(fncDir, "themes")
|
||||
)
|
||||
writeFile(os.path.join(fncDir, "themes", "default.qss"), "/* Stuff */")
|
||||
|
||||
# Block the reading of the files
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert mainTheme.listThemes() == []
|
||||
|
||||
# Load the theme info
|
||||
themesList = mainTheme.listThemes()
|
||||
assert themesList[0] == ("default_dark", "Default Dark Theme")
|
||||
assert themesList[1] == ("default", "Default Theme")
|
||||
|
||||
# A second call should returned the cached list
|
||||
assert mainTheme.listThemes() == mainTheme._themeList
|
||||
|
||||
# Check handling of broken theme settings
|
||||
mainConf.guiTheme = "not_a_theme"
|
||||
assert mainTheme.loadTheme() is False
|
||||
|
||||
# Check handling of unreadable file
|
||||
mainConf.guiTheme = "default"
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert mainTheme.loadTheme() is False
|
||||
|
||||
# Load Default Theme
|
||||
# ==================
|
||||
|
||||
# Set a mock colour for the window background
|
||||
mainTheme._guiPalette.color(QPalette.Window).setRgb(0, 0, 0, 0)
|
||||
|
||||
# Load the default theme
|
||||
mainConf.guiTheme = "default"
|
||||
assert mainTheme.loadTheme() is True
|
||||
|
||||
# This should load a standard palette
|
||||
wCol = QApplication.style().standardPalette().color(QPalette.Window).getRgb()
|
||||
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == wCol
|
||||
|
||||
# Load Default Dark Theme
|
||||
# =======================
|
||||
|
||||
mainConf.guiTheme = "default_dark"
|
||||
assert mainTheme.loadTheme() is True
|
||||
|
||||
# Check a few values
|
||||
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (54, 54, 54, 255)
|
||||
assert mainTheme._guiPalette.color(QPalette.WindowText).getRgb() == (174, 174, 174, 255)
|
||||
assert mainTheme._guiPalette.color(QPalette.Base).getRgb() == (62, 62, 62, 255)
|
||||
assert mainTheme._guiPalette.color(QPalette.AlternateBase).getRgb() == (78, 78, 78, 255)
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiTheme_Theme
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncDir):
|
||||
"""Test the syntax part of the class.
|
||||
"""
|
||||
mainTheme: GuiTheme = nwGUI.mainTheme
|
||||
mainConf: Config = nwGUI.mainConf
|
||||
|
||||
# List Themes
|
||||
# ===========
|
||||
|
||||
shutil.copy(
|
||||
os.path.join(mainConf.assetPath, "syntax", "default_dark.conf"),
|
||||
os.path.join(fncDir, "syntax")
|
||||
)
|
||||
shutil.copy(
|
||||
os.path.join(mainConf.assetPath, "syntax", "default_light.conf"),
|
||||
os.path.join(fncDir, "syntax")
|
||||
)
|
||||
|
||||
# Block the reading of the files
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert mainTheme.listThemes() == []
|
||||
|
||||
# Load the syntax info
|
||||
syntaxList = mainTheme.listSyntax()
|
||||
assert syntaxList[0] == ("default_dark", "Default Dark")
|
||||
assert syntaxList[1] == ("default_light", "Default Light")
|
||||
|
||||
# A second call should returned the cached list
|
||||
assert mainTheme.listSyntax() == mainTheme._syntaxList
|
||||
|
||||
# Check handling of broken theme settings
|
||||
mainConf.guiSyntax = "not_a_syntax"
|
||||
assert mainTheme.loadSyntax() is False
|
||||
|
||||
# Check handling of unreadable file
|
||||
mainConf.guiSyntax = "default_light"
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert mainTheme.loadSyntax() is False
|
||||
|
||||
# Load Default Light Syntax
|
||||
# =========================
|
||||
|
||||
# Load the default syntax
|
||||
mainConf.guiSyntax = "default_light"
|
||||
assert mainTheme.loadSyntax() is True
|
||||
|
||||
# Check some values
|
||||
assert mainTheme.syntaxName == "Default Light"
|
||||
assert mainTheme.colBack == [255, 255, 255]
|
||||
assert mainTheme.colText == [0, 0, 0]
|
||||
assert mainTheme.colLink == [0, 0, 200]
|
||||
|
||||
# Load Default Dark Theme
|
||||
# =======================
|
||||
|
||||
# Load the default syntax
|
||||
mainConf.guiSyntax = "default_dark"
|
||||
assert mainTheme.loadSyntax() is True
|
||||
|
||||
# Check some values
|
||||
assert mainTheme.syntaxName == "Default Dark"
|
||||
assert mainTheme.colBack == [54, 54, 54]
|
||||
assert mainTheme.colText == [199, 207, 208]
|
||||
assert mainTheme.colLink == [184, 200, 0]
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiTheme_Syntax
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncDir):
|
||||
"""Test the icon cache class.
|
||||
"""
|
||||
iconCache: GuiIcons = nwGUI.mainTheme.iconCache
|
||||
mainConf: Config = nwGUI.mainConf
|
||||
|
||||
# Load Theme
|
||||
# ==========
|
||||
|
||||
# Invalid theme name
|
||||
assert iconCache.loadTheme("not_a_theme") is False
|
||||
|
||||
# Check handling of unreadable file
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert iconCache.loadTheme("typicons_dark") is False
|
||||
|
||||
# Load a broken theme file
|
||||
iconsDir = os.path.join(fncDir, "icons")
|
||||
os.mkdir(iconsDir)
|
||||
os.mkdir(os.path.join(iconsDir, "testicons"))
|
||||
writeFile(os.path.join(iconsDir, "testicons", "icons.conf"), (
|
||||
"[Main]\n"
|
||||
"name = Test Icons\n"
|
||||
"\n"
|
||||
"[Map]\n"
|
||||
"add = add.svg\n"
|
||||
"stuff = stuff.svg\n"
|
||||
))
|
||||
|
||||
assetPath = mainConf.assetPath
|
||||
mainConf.assetPath = fncDir
|
||||
|
||||
caplog.clear()
|
||||
assert iconCache.loadTheme("testicons") is True
|
||||
assert "Unknown icon name 'stuff' in config file" in caplog.text
|
||||
assert "Icon file 'add.svg' not in theme folder" in caplog.text
|
||||
|
||||
mainConf.assetPath = assetPath
|
||||
|
||||
# Load working theme file
|
||||
assert iconCache.loadTheme("typicons_dark") is True
|
||||
assert "add" in iconCache._themeMap
|
||||
|
||||
# Load Decorations
|
||||
# ================
|
||||
|
||||
# Invalid name should return empty pixmap
|
||||
qPix = iconCache.loadDecoration("stuff")
|
||||
assert qPix.isNull() is True
|
||||
|
||||
# Load an image
|
||||
qPix = iconCache.loadDecoration("wiz-back")
|
||||
assert qPix.isNull() is False
|
||||
|
||||
# Fail finding the file
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("os.path.isfile", lambda *a: False)
|
||||
qPix = iconCache.loadDecoration("wiz-back")
|
||||
assert qPix.isNull() is True
|
||||
|
||||
# Test image sizes
|
||||
qPix = iconCache.loadDecoration("wiz-back", pxW=100, pxH=None)
|
||||
assert qPix.isNull() is False
|
||||
assert qPix.width() == 100
|
||||
assert qPix.height() > 100
|
||||
|
||||
qPix = iconCache.loadDecoration("wiz-back", pxW=None, pxH=100)
|
||||
assert qPix.isNull() is False
|
||||
assert qPix.width() < 100
|
||||
assert qPix.height() == 100
|
||||
|
||||
qPix = iconCache.loadDecoration("wiz-back", pxW=100, pxH=100)
|
||||
assert qPix.isNull() is False
|
||||
assert qPix.width() == 100
|
||||
assert qPix.height() == 100
|
||||
|
||||
# Load Icons
|
||||
# ==========
|
||||
|
||||
# Load an unknown icon
|
||||
qIcon = iconCache.getIcon("stuff")
|
||||
assert isinstance(qIcon, QIcon)
|
||||
assert qIcon.isNull() is True
|
||||
|
||||
# Load an icon, it is likelyu already cached
|
||||
qIcon = iconCache.getIcon("add")
|
||||
assert isinstance(qIcon, QIcon)
|
||||
assert qIcon.isNull() is False
|
||||
|
||||
# Load it as a pixmap with a size
|
||||
qPix = iconCache.getPixmap("add", (50, 50))
|
||||
assert isinstance(qPix, QPixmap)
|
||||
assert qPix.isNull() is False
|
||||
assert qPix.width() == 50
|
||||
assert qPix.height() == 50
|
||||
|
||||
# Load app icon
|
||||
qIcon = iconCache.getIcon("novelwriter")
|
||||
assert isinstance(qIcon, QIcon)
|
||||
assert qIcon.isNull() is False
|
||||
|
||||
# Load mime icon
|
||||
qIcon = iconCache.getIcon("proj_nwx")
|
||||
assert isinstance(qIcon, QIcon)
|
||||
assert qIcon.isNull() is False
|
||||
|
||||
# Load Item Icons
|
||||
# ===============
|
||||
|
||||
# Root -> Not Null
|
||||
assert iconCache.getItemIcon(
|
||||
nwItemType.ROOT, nwItemClass.NOVEL, nwItemLayout.NO_LAYOUT, hLevel="H0"
|
||||
) == iconCache.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])
|
||||
|
||||
# Folder -> Not Null
|
||||
assert iconCache.getItemIcon(
|
||||
nwItemType.FOLDER, nwItemClass.NOVEL, nwItemLayout.NO_LAYOUT, hLevel="H0"
|
||||
) == iconCache.getIcon("proj_folder")
|
||||
|
||||
# Document H0 -> Not Null
|
||||
assert iconCache.getItemIcon(
|
||||
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.NO_LAYOUT, hLevel="H0"
|
||||
) == iconCache.getIcon("proj_document")
|
||||
|
||||
# Document H1 -> Not Null
|
||||
assert iconCache.getItemIcon(
|
||||
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H1"
|
||||
) == iconCache.getIcon("proj_title")
|
||||
|
||||
# Document H2 -> Not Null
|
||||
assert iconCache.getItemIcon(
|
||||
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H2"
|
||||
) == iconCache.getIcon("proj_chapter")
|
||||
|
||||
# Document H3 -> Not Null
|
||||
assert iconCache.getItemIcon(
|
||||
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H3"
|
||||
) == iconCache.getIcon("proj_scene")
|
||||
|
||||
# Document H4 -> Not Null
|
||||
assert iconCache.getItemIcon(
|
||||
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H4"
|
||||
) == iconCache.getIcon("proj_section")
|
||||
|
||||
# Document H5 -> Not Null
|
||||
assert iconCache.getItemIcon(
|
||||
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.NO_LAYOUT, hLevel="H4"
|
||||
) == iconCache.getIcon("proj_document")
|
||||
|
||||
# Note -> Not Null
|
||||
assert iconCache.getItemIcon(
|
||||
nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.NOTE, hLevel="H5"
|
||||
) == iconCache.getIcon("proj_note")
|
||||
|
||||
# No Type -> Null
|
||||
assert iconCache.getItemIcon(
|
||||
nwItemType.NO_TYPE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H0"
|
||||
).isNull() is True
|
||||
|
||||
# Header Decorations
|
||||
# ==================
|
||||
|
||||
assert iconCache.getHeaderDecoration(-1) == iconCache._headerDec[0]
|
||||
assert iconCache.getHeaderDecoration(0) == iconCache._headerDec[0]
|
||||
assert iconCache.getHeaderDecoration(1) == iconCache._headerDec[1]
|
||||
assert iconCache.getHeaderDecoration(2) == iconCache._headerDec[2]
|
||||
assert iconCache.getHeaderDecoration(3) == iconCache._headerDec[3]
|
||||
assert iconCache.getHeaderDecoration(4) == iconCache._headerDec[4]
|
||||
assert iconCache.getHeaderDecoration(5) == iconCache._headerDec[4]
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiTheme_Icons
|
||||
|
||||
@@ -26,22 +26,16 @@ from shutil import copyfile
|
||||
from tools import cmpFiles, getGuiItem
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QAction, QMessageBox, QFileDialog
|
||||
from PyQt5.QtWidgets import QAction, QFileDialog
|
||||
|
||||
from novelwriter.tools import GuiBuildNovel
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
"""Test the build tool.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda a, b, c, **k: (c, None))
|
||||
|
||||
# Check that we cannot open when there is no project
|
||||
@@ -121,27 +115,17 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
|
||||
# Change Title Formats and Flip Switches
|
||||
nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%")
|
||||
qtbot.wait(stepDelay)
|
||||
nwBuild.fmtScene.setText(r"Scene %ch%.%sc%: %title%")
|
||||
qtbot.wait(stepDelay)
|
||||
nwBuild.fmtSection.setText(r"%ch%.%sc%.1: %title%")
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
qtbot.mouseClick(nwBuild.justifyText, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwBuild.includeSynopsis, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwBuild.includeComments, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwBuild.includeKeywords, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwBuild.replaceUCode, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
qtbot.mouseClick(nwBuild.noteFiles, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwBuild.ignoreFlag, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
|
||||
|
||||
@@ -175,7 +159,6 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
|
||||
# Replace Tabs with Spaces
|
||||
qtbot.mouseClick(nwBuild.replaceTabs, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
|
||||
|
||||
@@ -210,20 +193,13 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
|
||||
# Putline Mode
|
||||
nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%")
|
||||
qtbot.wait(stepDelay)
|
||||
nwBuild.fmtScene.setText(r"Scene %sca%: %title%")
|
||||
qtbot.wait(stepDelay)
|
||||
nwBuild.fmtSection.setText(r"Section: %title%")
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
qtbot.mouseClick(nwBuild.includeComments, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwBuild.noteFiles, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwBuild.ignoreFlag, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwBuild.includeBody, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
|
||||
|
||||
@@ -287,6 +263,6 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
|
||||
nwBuild._doClose()
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testToolBuild_Main
|
||||
|
||||
@@ -23,18 +23,15 @@ import pytest
|
||||
|
||||
from tools import C, getGuiItem, buildTestProject
|
||||
|
||||
from PyQt5.QtWidgets import QAction, QMessageBox
|
||||
from PyQt5.QtWidgets import QAction
|
||||
|
||||
from novelwriter.tools import GuiLipsum
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testToolLipsum_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
def testToolLipsum_Main(qtbot, nwGUI, fncProj, mockRnd):
|
||||
"""Test the Lorem Ipsum tool.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Check that we cannot open when there is no project
|
||||
nwGUI.mainMenu.aLipsumText.activate(QAction.Trigger)
|
||||
assert getGuiItem("GuiLipsum") is None
|
||||
@@ -70,6 +67,6 @@ def testToolLipsum_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
# Close
|
||||
nwLipsum._doClose()
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testToolLipsum_Main
|
||||
|
||||
@@ -26,7 +26,7 @@ import pytest
|
||||
from tools import getGuiItem
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QFileDialog, QWizard, QMessageBox, QDialog
|
||||
from PyQt5.QtWidgets import QFileDialog, QWizard, QDialog
|
||||
|
||||
from novelwriter.enum import nwItemClass
|
||||
from novelwriter.tools.projwizard import (
|
||||
@@ -34,10 +34,6 @@ from novelwriter.tools.projwizard import (
|
||||
ProjWizardPopulatePage, ProjWizardCustomPage, ProjWizardFinalPage
|
||||
)
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Not running on Darwin")
|
||||
@@ -45,13 +41,8 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
"""Test the launch of the project wizard.
|
||||
Disabled for macOS because the test segfaults on QWizard.show()
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
##
|
||||
# Test New Project Function
|
||||
##
|
||||
# Test New Project Function
|
||||
# ========================
|
||||
|
||||
# New with a project open should cause an error
|
||||
assert nwGUI.openProject(nwMinimal)
|
||||
@@ -73,9 +64,8 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: {"projPath": nwMinimal})
|
||||
assert nwGUI.newProject() is False
|
||||
|
||||
##
|
||||
# Test the Wizard Launching
|
||||
##
|
||||
# Test the Wizard Launching
|
||||
# =========================
|
||||
|
||||
nwGUI.mainConf.lastPath = " "
|
||||
monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *a: None)
|
||||
@@ -87,7 +77,6 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
assert isinstance(nwWiz, GuiProjectWizard)
|
||||
nwWiz.show()
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwWiz.button(QWizard.CancelButton), Qt.LeftButton)
|
||||
assert result is None
|
||||
|
||||
@@ -100,7 +89,7 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
nwWiz.reject()
|
||||
nwWiz.close()
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testToolProjectWizard_Handling
|
||||
|
||||
@@ -111,14 +100,12 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType):
|
||||
"""Test the new project wizard with a set of selection scenarios.
|
||||
"""
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *a: None)
|
||||
|
||||
nwGUI.mainConf.lastPath = " "
|
||||
nwWiz = GuiProjectWizard(nwGUI)
|
||||
nwWiz.show()
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.addWidget(nwWiz)
|
||||
|
||||
# Intro Page
|
||||
# ==========
|
||||
@@ -134,7 +121,6 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType):
|
||||
# Setting projName should activate the button
|
||||
assert nwWiz.button(QWizard.NextButton).isEnabled()
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
|
||||
|
||||
# Folder Page
|
||||
@@ -173,7 +159,6 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType):
|
||||
# Setting projPath should activate the button
|
||||
assert nwWiz.button(QWizard.NextButton).isEnabled()
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
|
||||
|
||||
# Populate Page
|
||||
@@ -183,7 +168,6 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType):
|
||||
assert isinstance(popPage, ProjWizardPopulatePage)
|
||||
assert nwWiz.button(QWizard.NextButton).isEnabled()
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
if prjType.startswith("minimal"):
|
||||
popPage.popMinimal.setChecked(True)
|
||||
elif prjType.startswith("custom"):
|
||||
@@ -191,7 +175,6 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType):
|
||||
elif prjType.startswith("sample"):
|
||||
popPage.popSample.setChecked(True)
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
|
||||
|
||||
# Custom Page
|
||||
@@ -219,7 +202,6 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType):
|
||||
customPage.numChapters.setValue(0)
|
||||
customPage.numScenes.setValue(10)
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
|
||||
|
||||
# Final Page
|
||||
@@ -264,6 +246,6 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType):
|
||||
nwWiz.reject()
|
||||
nwWiz.close()
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testToolProjectWizard_Run
|
||||
|
||||
@@ -27,26 +27,16 @@ from mock import causeOSError
|
||||
from tools import getGuiItem, writeFile, buildTestProject
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
|
||||
from PyQt5.QtWidgets import QAction, QFileDialog
|
||||
|
||||
from novelwriter.tools import GuiWritingStats
|
||||
from novelwriter.constants import nwFiles
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
"""Test the full writing stats tool.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Create a project to work on
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
qtbot.wait(100)
|
||||
@@ -60,7 +50,6 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
|
||||
sessLog = getGuiItem("GuiWritingStats")
|
||||
assert isinstance(sessLog, GuiWritingStats)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# Test Loading
|
||||
# ============
|
||||
@@ -186,9 +175,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
|
||||
# No Novel Files
|
||||
qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
jsonStats = os.path.join(fncDir, "sessionStats.json")
|
||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||
@@ -234,9 +221,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
# No Note Files
|
||||
qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
|
||||
qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
jsonStats = os.path.join(fncDir, "sessionStats.json")
|
||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||
@@ -282,11 +267,9 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
# No Negative Entries
|
||||
qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton)
|
||||
qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
jsonStats = os.path.join(fncDir, "sessionStats.json")
|
||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||
@@ -316,9 +299,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
# Un-hide Zero Entries
|
||||
qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton)
|
||||
qtbot.mouseClick(sessLog.hideZeros, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
jsonStats = os.path.join(fncDir, "sessionStats.json")
|
||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||
@@ -371,9 +352,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
|
||||
# Group by Day
|
||||
qtbot.mouseClick(sessLog.groupByDay, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
jsonStats = os.path.join(fncDir, "sessionStats.json")
|
||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||
@@ -422,10 +401,9 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
assert not sessLog._loadLogFile()
|
||||
assert not sessLog._saveData(sessLog.FMT_CSV)
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
sessLog._doClose()
|
||||
assert nwGUI.closeProject()
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# END Test testToolWritingStats_Main
|
||||
|
||||
@@ -30,6 +30,17 @@ XML_IGNORE = ("<novelWriterXML", "<saveCount", "<autoCount", "<editTime")
|
||||
|
||||
class C:
|
||||
|
||||
# Import and status items from test project when random generator is mocked
|
||||
sNew = "s000000"
|
||||
sNote = "s000001"
|
||||
sDraft = "s000002"
|
||||
sFinished = "s000003"
|
||||
|
||||
iNew = "i000004"
|
||||
iMinor = "i000005"
|
||||
iMajor = "i000006"
|
||||
iMain = "i000007"
|
||||
|
||||
# Handles from test project when random generator is mocked
|
||||
hInvalid = "0000000000000"
|
||||
hNovelRoot = "0000000000008"
|
||||
|
||||