Merge branch 'main' into xml_rw_class

This commit is contained in:
Veronica Berglyd Olsen
2022-10-30 19:40:12 +01:00
70 changed files with 2167 additions and 1697 deletions
+1 -1
View File
@@ -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
+18 -1
View File
@@ -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
+1
View File
@@ -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

+3 -1
View File
@@ -1,2 +1,4 @@
[Main]
name = Default System Theme
name = Default Theme
description = Qt standard colours
icontheme = typicons_light
+8 -6
View File
@@ -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
-15
View File
@@ -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
##
+5 -1
View File
@@ -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:
+31 -15
View File
@@ -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
##
+6
View File
@@ -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
+8 -1
View File
@@ -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)
+40 -45
View File
@@ -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
+28 -3
View File
@@ -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.
"""
+4 -3
View File
@@ -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
+160 -87
View File
@@ -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.
+77 -41
View File
@@ -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.
"""
+11 -4
View File
@@ -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
##
+9
View File
@@ -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"))
+43 -17
View File
@@ -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
##
+19 -3
View File
@@ -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.
"""
+154 -112
View File
@@ -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:
+16 -6
View File
@@ -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
##
+126 -180
View File
@@ -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.
+17 -8
View File
@@ -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
+36 -11
View File
@@ -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 -1
View File
@@ -1,3 +1,3 @@
pyqt5>=5.3
pyqt5>=5.10
lxml>=4.2.0
pyenchant>=3.0.0
+3 -3
View File
@@ -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>
+2 -1
View File
@@ -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
+8 -5
View File
@@ -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.
+5 -1
View File
@@ -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 -2
View File
@@ -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>
-26
View File
@@ -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
# ============
+7
View File
@@ -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
+1 -6
View File
@@ -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
+4 -10
View File
@@ -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
+1 -6
View File
@@ -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)
-5
View File
@@ -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
+43 -36
View File
@@ -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
+3 -14
View File
@@ -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()
+2 -21
View File
@@ -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
+325 -167
View File
@@ -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
+2 -12
View File
@@ -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 -87
View File
@@ -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
+2 -10
View File
@@ -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
+120 -142
View File
@@ -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
+34 -71
View File
@@ -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
+1 -4
View File
@@ -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)
+4 -12
View File
@@ -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()
+66 -114
View File
@@ -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)
+2 -6
View File
@@ -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
+400 -133
View File
@@ -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
+2 -26
View File
@@ -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
+3 -6
View File
@@ -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
+8 -26
View File
@@ -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
+3 -25
View File
@@ -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
+11
View File
@@ -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"