Merge branch 'master' into release_0.7

This commit is contained in:
Veronica K. B. Olsen
2020-05-31 17:04:07 +02:00
35 changed files with 1088 additions and 159 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+93
View File
@@ -0,0 +1,93 @@
Copyright (c) 2009-2010, Understanding Limited (dave@understandinglimited.com)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+56
View File
@@ -0,0 +1,56 @@
<?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"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="tick.svg"
id="svg896"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<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" />
<sodipodi:namedview
inkscape:current-layer="svg896"
inkscape:window-maximized="1"
inkscape:window-y="1"
inkscape:window-x="2560"
inkscape:cy="13.298248"
inkscape:cx="11.288366"
inkscape:zoom="34.117902"
inkscape:pagecheckerboard="true"
showgrid="false"
id="namedview898"
inkscape:window-height="1370"
inkscape:window-width="2560"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<path
style="stroke-width:1.3846;fill:#aeaeae;fill-opacity:1"
id="path894"
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" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+56
View File
@@ -0,0 +1,56 @@
<?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"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="tick.svg"
id="svg896"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<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" />
<sodipodi:namedview
inkscape:current-layer="svg896"
inkscape:window-maximized="1"
inkscape:window-y="1"
inkscape:window-x="2560"
inkscape:cy="13.298248"
inkscape:cx="11.288366"
inkscape:zoom="34.117902"
inkscape:pagecheckerboard="true"
showgrid="false"
id="namedview898"
inkscape:window-height="1370"
inkscape:window-width="2560"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<path
style="stroke-width:1.3846;fill:#000000;fill-opacity:0.77999997"
id="path894"
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" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+56
View File
@@ -0,0 +1,56 @@
<?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"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.2"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg8936"
sodipodi:docname="times.svg"
inkscape:version="1.0rc1 (09960d6f05, 2020-04-09)">
<metadata
id="metadata8942">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8940" />
<sodipodi:namedview
inkscape:document-rotation="0"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1344"
id="namedview8938"
showgrid="false"
inkscape:zoom="38.125"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8936" />
<path
d="m 19.0412,4.9586375 c -1.014432,-1.0157327 -2.663535,-1.0157327 -3.677967,0 L 12.000001,8.3218703 8.6367669,4.9586375 c -1.0144323,-1.0157327 -2.663535,-1.0157327 -3.6779673,0 -1.0157327,1.0157328 -1.0157327,2.6622343 0,3.6779673 L 8.3207319,11.999838 4.9587996,15.36307 c -1.0157327,1.015733 -1.0157327,2.662235 0,3.677968 0.5072161,0.508516 1.1730998,0.762124 1.8389836,0.762124 0.6658835,0 1.3317669,-0.253608 1.8389837,-0.762124 l 3.3632341,-3.363233 3.363232,3.363233 c 0.507216,0.508516 1.173099,0.762124 1.838984,0.762124 0.665882,0 1.331765,-0.253608 1.838983,-0.762124 1.015733,-1.015733 1.015733,-2.662235 0,-3.677968 L 15.679268,11.999838 19.0412,8.6366048 c 1.015733,-1.015733 1.015733,-2.6622345 0,-3.6779673 z"
id="path8934"
inkscape:connector-curvature="0"
style="fill:#aeaeae;fill-opacity:1;stroke-width:1.30056" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+55
View File
@@ -0,0 +1,55 @@
<?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"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0rc1 (09960d6f05, 2020-04-09)"
sodipodi:docname="times.svg"
id="svg8936"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<metadata
id="metadata8942">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8940" />
<sodipodi:namedview
inkscape:current-layer="svg8936"
inkscape:window-maximized="1"
inkscape:window-y="0"
inkscape:window-x="0"
inkscape:cy="12"
inkscape:cx="12"
inkscape:zoom="38.125"
showgrid="false"
id="namedview8938"
inkscape:window-height="1344"
inkscape:window-width="2560"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<path
style="fill:#000000;fill-opacity:0.78;stroke-width:1.30056"
inkscape:connector-curvature="0"
id="path8934"
d="m 19.0412,4.9586375 c -1.014432,-1.0157327 -2.663535,-1.0157327 -3.677967,0 L 12.000001,8.3218703 8.6367669,4.9586375 c -1.0144323,-1.0157327 -2.663535,-1.0157327 -3.6779673,0 -1.0157327,1.0157328 -1.0157327,2.6622343 0,3.6779673 L 8.3207319,11.999838 4.9587996,15.36307 c -1.0157327,1.015733 -1.0157327,2.662235 0,3.677968 0.5072161,0.508516 1.1730998,0.762124 1.8389836,0.762124 0.6658835,0 1.3317669,-0.253608 1.8389837,-0.762124 l 3.3632341,-3.363233 3.363232,3.363233 c 0.507216,0.508516 1.173099,0.762124 1.838984,0.762124 0.665882,0 1.331765,-0.253608 1.838983,-0.762124 1.015733,-1.015733 1.015733,-2.662235 0,-3.677968 L 15.679268,11.999838 19.0412,8.6366048 c 1.015733,-1.015733 1.015733,-2.6622345 0,-3.6779673 z" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -37,3 +37,5 @@ search-replace = search-replace.svg
clear = backspace.svg clear = backspace.svg
save = download.svg save = download.svg
edit = pencil.svg edit = pencil.svg
check = tick.svg
cross = times.svg
@@ -0,0 +1,56 @@
<?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"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="tick.svg"
id="svg896"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<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" />
<sodipodi:namedview
inkscape:current-layer="svg896"
inkscape:window-maximized="1"
inkscape:window-y="1"
inkscape:window-x="2560"
inkscape:cy="13.298248"
inkscape:cx="11.288366"
inkscape:zoom="34.117902"
inkscape:pagecheckerboard="true"
showgrid="false"
id="namedview898"
inkscape:window-height="1370"
inkscape:window-width="2560"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<path
style="stroke-width:1.3846;fill:#99cc99;fill-opacity:1"
id="path894"
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" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -37,3 +37,5 @@ search-replace = search-replace.svg
clear = backspace.svg clear = backspace.svg
save = download.svg save = download.svg
edit = pencil.svg edit = pencil.svg
check = tick.svg
cross = times.svg
@@ -0,0 +1,56 @@
<?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"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="tick.svg"
id="svg896"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<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" />
<sodipodi:namedview
inkscape:current-layer="svg896"
inkscape:window-maximized="1"
inkscape:window-y="1"
inkscape:window-x="2560"
inkscape:cy="13.298248"
inkscape:cx="11.288366"
inkscape:zoom="34.117902"
inkscape:pagecheckerboard="true"
showgrid="false"
id="namedview898"
inkscape:window-height="1370"
inkscape:window-width="2560"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<path
style="stroke-width:1.3846;fill:#718c00;fill-opacity:1"
id="path894"
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" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -36,3 +36,5 @@ search-replace = search-replace.svg
clear = backspace.svg clear = backspace.svg
save = download.svg save = download.svg
edit = pencil.svg edit = pencil.svg
check = tick.svg
cross = times.svg
@@ -0,0 +1,56 @@
<?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"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="tick.svg"
id="svg896"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<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" />
<sodipodi:namedview
inkscape:current-layer="svg896"
inkscape:window-maximized="1"
inkscape:window-y="1"
inkscape:window-x="2560"
inkscape:cy="13.298248"
inkscape:cx="11.288366"
inkscape:zoom="34.117902"
inkscape:pagecheckerboard="true"
showgrid="false"
id="namedview898"
inkscape:window-height="1370"
inkscape:window-width="2560"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<path
style="stroke-width:1.3846;fill:#aeaeae;fill-opacity:1"
id="path894"
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" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -36,3 +36,5 @@ search-replace = search-replace.svg
clear = backspace.svg clear = backspace.svg
save = download.svg save = download.svg
edit = pencil.svg edit = pencil.svg
check = tick.svg
cross = times.svg
@@ -0,0 +1,56 @@
<?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"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="tick.svg"
id="svg896"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<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" />
<sodipodi:namedview
inkscape:current-layer="svg896"
inkscape:window-maximized="1"
inkscape:window-y="1"
inkscape:window-x="2560"
inkscape:cy="13.298248"
inkscape:cx="11.288366"
inkscape:zoom="34.117902"
inkscape:pagecheckerboard="true"
showgrid="false"
id="namedview898"
inkscape:window-height="1370"
inkscape:window-width="2560"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<path
style="stroke-width:1.3846;fill:#000000;fill-opacity:0.77999997"
id="path894"
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" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+20 -12
View File
@@ -36,7 +36,6 @@ from time import time
from PyQt5.Qt import PYQT_VERSION_STR from PyQt5.Qt import PYQT_VERSION_STR
from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo
from PyQt5.QtWidgets import QErrorMessage
from nw.constants import nwFiles, nwUnicode from nw.constants import nwFiles, nwUnicode
from nw.common import splitVersionNumber, formatTimeStamp from nw.common import splitVersionNumber, formatTimeStamp
@@ -82,11 +81,13 @@ class Config:
self.confChanged = False self.confChanged = False
## General ## General
self.guiTheme = "default" self.guiTheme = "default"
self.guiSyntax = "default_light" self.guiSyntax = "default_light"
self.guiIcons = "typicons_grey_light" self.guiIcons = "typicons_grey_light"
self.guiDark = False self.guiDark = False
self.guiLang = "en" # Hardcoded for now self.guiLang = "en" # Hardcoded for now
self.guiFont = ""
self.guiFontSize = 11
## Sizes ## Sizes
self.winGeometry = [1100, 650] self.winGeometry = [1100, 650]
@@ -202,7 +203,6 @@ class Config:
"""Initialise the config class. The manual setting of confPath """Initialise the config class. The manual setting of confPath
and dataPath is mainly intended for the test suite. and dataPath is mainly intended for the test suite.
""" """
if confPath is None: if confPath is None:
confRoot = QStandardPaths.writableLocation(QStandardPaths.ConfigLocation) confRoot = QStandardPaths.writableLocation(QStandardPaths.ConfigLocation)
self.confPath = path.join(path.abspath(confRoot), self.appHandle) self.confPath = path.join(path.abspath(confRoot), self.appHandle)
@@ -318,6 +318,12 @@ class Config:
self.guiDark = self._parseLine( self.guiDark = self._parseLine(
cnfParse, cnfSec, "guidark", self.CNF_BOOL, self.guiDark cnfParse, cnfSec, "guidark", self.CNF_BOOL, self.guiDark
) )
self.guiFont = self._parseLine(
cnfParse, cnfSec, "guifont", self.CNF_STR, self.guiFont
)
self.guiFontSize = self._parseLine(
cnfParse, cnfSec, "guifontsize", self.CNF_INT, self.guiFontSize
)
## Sizes ## Sizes
cnfSec = "Sizes" cnfSec = "Sizes"
@@ -464,11 +470,13 @@ class Config:
## Main ## Main
cnfSec = "Main" cnfSec = "Main"
cnfParse.add_section(cnfSec) cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec,"timestamp", formatTimeStamp(time())) cnfParse.set(cnfSec,"timestamp", formatTimeStamp(time()))
cnfParse.set(cnfSec,"theme", str(self.guiTheme)) cnfParse.set(cnfSec,"theme", str(self.guiTheme))
cnfParse.set(cnfSec,"syntax", str(self.guiSyntax)) cnfParse.set(cnfSec,"syntax", str(self.guiSyntax))
cnfParse.set(cnfSec,"icons", str(self.guiIcons)) cnfParse.set(cnfSec,"icons", str(self.guiIcons))
cnfParse.set(cnfSec,"guidark", str(self.guiDark)) cnfParse.set(cnfSec,"guidark", str(self.guiDark))
cnfParse.set(cnfSec,"guifont", str(self.guiFont))
cnfParse.set(cnfSec,"guifontsize", str(self.guiFontSize))
## Sizes ## Sizes
cnfSec = "Sizes" cnfSec = "Sizes"
+1
View File
@@ -45,6 +45,7 @@ class nwFiles():
INDEX_FILE = "tagsIndex.json" INDEX_FILE = "tagsIndex.json"
OPTS_FILE = "guiOptions.json" OPTS_FILE = "guiOptions.json"
RECENT_FILE = "recentProjects.json" RECENT_FILE = "recentProjects.json"
BUILD_CACHE = "prevBuild.json"
# END Class nwFiles # END Class nwFiles
+7 -1
View File
@@ -71,6 +71,9 @@ class NWDoc():
"""Open a document from handle, capturing potential file system """Open a document from handle, capturing potential file system
errors and parse meta data. errors and parse meta data.
""" """
if not isHandle(tHandle):
return None
# Always clear first, since the object will often be reused. # Always clear first, since the object will often be reused.
self.clearDocument() self.clearDocument()
@@ -170,7 +173,10 @@ class NWDoc():
"""Permanently delete a document source file and its backups """Permanently delete a document source file and its backups
from the project data folder. from the project data folder.
""" """
docFile = self.docHandle+".nwd" if not isHandle(tHandle):
return False
docFile = tHandle+".nwd"
chkList = [] chkList = []
chkList.append(path.join(self.theProject.projContent, docFile)) chkList.append(path.join(self.theProject.projContent, docFile))
+24 -8
View File
@@ -152,15 +152,19 @@ class NWProject():
self.projTree.append(None, pHandle, newItem) self.projTree.append(None, pHandle, newItem)
return newItem.itemHandle return newItem.itemHandle
def addTrash(self): def trashFolder(self):
"""Add the special trash root folder to the project. """Add the special trash root folder to the project.
""" """
newItem = NWItem(self) trashHandle = self.projTree.trashRoot()
newItem.setName("Trash") if trashHandle is None:
newItem.setType(nwItemType.TRASH) newItem = NWItem(self)
newItem.setClass(nwItemClass.TRASH) newItem.setName("Trash")
self.projTree.append(None, None, newItem) newItem.setType(nwItemType.TRASH)
return newItem.itemHandle newItem.setClass(nwItemClass.TRASH)
self.projTree.append(None, None, newItem)
return newItem.itemHandle
return trashHandle
## ##
# Project Methods # Project Methods
@@ -179,6 +183,7 @@ class NWProject():
hScene = self.newFile("New Scene", nwItemClass.NOVEL, hChapt) hScene = self.newFile("New Scene", nwItemClass.NOVEL, hChapt)
self.projOpened = time() self.projOpened = time()
self.setProjectChanged(True) self.setProjectChanged(True)
self.saveProject(autoSave=True)
return True return True
def clearProject(self): def clearProject(self):
@@ -674,7 +679,7 @@ class NWProject():
# Setters # Setters
## ##
def setProjectPath(self, projPath): def setProjectPath(self, projPath, newProject=False):
"""Set the project storage path, and also expand ~ to the user """Set the project storage path, and also expand ~ to the user
directory using the path library. directory using the path library.
""" """
@@ -684,7 +689,18 @@ class NWProject():
if projPath.startswith("~"): if projPath.startswith("~"):
projPath = path.expanduser(projPath) projPath = path.expanduser(projPath)
self.projPath = path.abspath(projPath) self.projPath = path.abspath(projPath)
if newProject and self.mainConf.showGUI:
if listdir(self.projPath):
self.theParent.makeAlert((
"New project folder is not empty. "
"Each project requires a dedicated project folder."
), nwAlert.ERROR)
return False
self.ensureFolderStructure()
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
def setProjectName(self, projName): def setProjectName(self, projName):
+7 -1
View File
@@ -107,7 +107,7 @@ class QConfigLayout(QGridLayout):
return return
def addRow(self, theLabel, theWidget, helpText=None, theUnit=None): def addRow(self, theLabel, theWidget, helpText=None, theUnit=None, theButton=None):
"""Add a label and a widget as a new row of the grid. """Add a label and a widget as a new row of the grid.
""" """
thisEntry = { thisEntry = {
@@ -152,6 +152,12 @@ class QConfigLayout(QGridLayout):
controlBox.addWidget(QLabel(theUnit), 0, Qt.AlignVCenter) controlBox.addWidget(QLabel(theUnit), 0, Qt.AlignVCenter)
controlBox.setSpacing(8) controlBox.setSpacing(8)
self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter)
elif theButton is not None:
controlBox = QHBoxLayout()
controlBox.addWidget(qWidget, 0, Qt.AlignVCenter)
controlBox.addWidget(theButton, 0, Qt.AlignVCenter)
controlBox.setSpacing(8)
self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter)
else: else:
self.addWidget(qWidget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter) self.addWidget(qWidget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter)
+162 -23
View File
@@ -26,6 +26,7 @@
""" """
import logging import logging
import json
import nw import nw
from os import path from os import path
@@ -39,25 +40,27 @@ from PyQt5.QtGui import (
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel, QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel,
QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction, QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction,
QFileDialog, QFontComboBox, QSpinBox QFileDialog, QFontDialog, QSpinBox
) )
from nw.gui.additions import QSwitch from nw.gui.additions import QSwitch
from nw.core import ToHtml from nw.core import ToHtml
from nw.constants import ( from nw.constants import (
nwAlert, nwItemType, nwItemLayout, nwItemClass nwAlert, nwFiles, nwItemType, nwItemLayout, nwItemClass
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class GuiBuildNovel(QDialog): class GuiBuildNovel(QDialog):
FMT_ODT = 1 FMT_ODT = 1
FMT_PDF = 2 FMT_PDF = 2
FMT_HTM = 3 FMT_HTM = 3
FMT_MD = 4 FMT_MD = 4
FMT_NWD = 5 FMT_NWD = 5
FMT_TXT = 6 FMT_TXT = 6
FMT_JSON_H = 7
FMT_JSON_M = 8
def __init__(self, theParent, theProject): def __init__(self, theParent, theProject):
QDialog.__init__(self, theParent) QDialog.__init__(self, theParent)
@@ -170,19 +173,21 @@ class GuiBuildNovel(QDialog):
self.textForm = QGridLayout(self) self.textForm = QGridLayout(self)
self.textGroup.setLayout(self.textForm) self.textGroup.setLayout(self.textForm)
self.textFont = QFontComboBox() ## Font Family
self.textFont.setFixedWidth(220) self.textFont = QLineEdit()
self.textFont.setToolTip( self.textFont.setReadOnly(True)
"The font is used for PDF and printing. Other formats have no font set." self.textFont.setFixedWidth(182)
) self.textFont.setText(
self.textFont.setCurrentFont( self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont)
QFont(self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont))
) )
self.fontButton = QPushButton("...")
self.fontButton.setMaximumWidth(30)
self.fontButton.clicked.connect(self._selectFont)
self.textSize = QSpinBox(self) self.textSize = QSpinBox(self)
self.textSize.setFixedWidth(60) self.textSize.setFixedWidth(60)
self.textSize.setMinimum(5) self.textSize.setMinimum(6)
self.textSize.setMaximum(48) self.textSize.setMaximum(72)
self.textSize.setSingleStep(1) self.textSize.setSingleStep(1)
self.textSize.setToolTip( self.textSize.setToolTip(
"The size is used for PDF and printing. Other formats have no size set." "The size is used for PDF and printing. Other formats have no size set."
@@ -201,13 +206,15 @@ class GuiBuildNovel(QDialog):
self.textForm.addWidget(QLabel("Font family"), 0, 0, 1, 1, Qt.AlignLeft) self.textForm.addWidget(QLabel("Font family"), 0, 0, 1, 1, Qt.AlignLeft)
self.textForm.addWidget(self.textFont, 0, 1, 1, 1, Qt.AlignRight) self.textForm.addWidget(self.textFont, 0, 1, 1, 1, Qt.AlignRight)
self.textForm.addWidget(self.fontButton, 0, 2, 1, 1, Qt.AlignRight)
self.textForm.addWidget(QLabel("Font size"), 1, 0, 1, 1, Qt.AlignLeft) self.textForm.addWidget(QLabel("Font size"), 1, 0, 1, 1, Qt.AlignLeft)
self.textForm.addWidget(self.textSize, 1, 1, 1, 1, Qt.AlignRight) self.textForm.addWidget(self.textSize, 1, 1, 1, 2, Qt.AlignRight)
self.textForm.addWidget(QLabel("Justify text"), 2, 0, 1, 1, Qt.AlignLeft) self.textForm.addWidget(QLabel("Justify text"), 2, 0, 1, 1, Qt.AlignLeft)
self.textForm.addWidget(self.justifyText, 2, 1, 1, 1, Qt.AlignRight) self.textForm.addWidget(self.justifyText, 2, 1, 1, 2, Qt.AlignRight)
self.textForm.setColumnStretch(0, 1) self.textForm.setColumnStretch(0, 1)
self.textForm.setColumnStretch(1, 0) self.textForm.setColumnStretch(1, 0)
self.textForm.setColumnStretch(2, 0)
# Include Switches # Include Switches
# ================ # ================
@@ -329,7 +336,7 @@ class GuiBuildNovel(QDialog):
self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD)) self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD))
self.saveMenu.addAction(self.saveMD) self.saveMenu.addAction(self.saveMD)
self.saveNWD = QAction("novelWriter Markdown (.nwd)", self) self.saveNWD = QAction("%s Markdown (.nwd)" % nw.__package__, self)
self.saveNWD.triggered.connect(lambda: self._saveDocument(self.FMT_NWD)) self.saveNWD.triggered.connect(lambda: self._saveDocument(self.FMT_NWD))
self.saveMenu.addAction(self.saveNWD) self.saveMenu.addAction(self.saveNWD)
@@ -337,6 +344,14 @@ class GuiBuildNovel(QDialog):
self.saveTXT.triggered.connect(lambda: self._saveDocument(self.FMT_TXT)) self.saveTXT.triggered.connect(lambda: self._saveDocument(self.FMT_TXT))
self.saveMenu.addAction(self.saveTXT) self.saveMenu.addAction(self.saveTXT)
self.saveJsonH = QAction("JSON + %s HTML (.json)" % nw.__package__, self)
self.saveJsonH.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_H))
self.saveMenu.addAction(self.saveJsonH)
self.saveJsonM = QAction("JSON + %s Markdown (.json)" % nw.__package__, self)
self.saveJsonM.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_M))
self.saveMenu.addAction(self.saveJsonM)
self.btnClose = QPushButton("Close") self.btnClose = QPushButton("Close")
self.btnClose.clicked.connect(self._doClose) self.btnClose.clicked.connect(self._doClose)
@@ -368,6 +383,20 @@ class GuiBuildNovel(QDialog):
logger.debug("GuiBuildNovel initialisation complete") logger.debug("GuiBuildNovel initialisation complete")
# Load from Cache
if self._loadCache():
textFont = self.textFont.text()
textSize = self.textSize.value()
justifyText = self.justifyText.isChecked()
self.docView.setTextFont(textFont, textSize)
self.docView.setJustify(justifyText)
self.docView.setStyleSheet(self.htmlStyle)
self.docView.setContent(self.htmlText)
else:
self.htmlText = []
self.htmlStyle = []
self.nwdText = []
return return
## ##
@@ -385,7 +414,7 @@ class GuiBuildNovel(QDialog):
fmtScene = self.fmtScene.text().strip() fmtScene = self.fmtScene.text().strip()
fmtSection = self.fmtSection.text().strip() fmtSection = self.fmtSection.text().strip()
justifyText = self.justifyText.isChecked() justifyText = self.justifyText.isChecked()
textFont = self.textFont.currentFont().family() textFont = self.textFont.text()
textSize = self.textSize.value() textSize = self.textSize.value()
incSynopsis = self.includeSynopsis.isChecked() incSynopsis = self.includeSynopsis.isChecked()
incComments = self.includeComments.isChecked() incComments = self.includeComments.isChecked()
@@ -455,6 +484,8 @@ class GuiBuildNovel(QDialog):
self.docView.setStyleSheet(self.htmlStyle) self.docView.setStyleSheet(self.htmlStyle)
self.docView.setContent(self.htmlText) self.docView.setContent(self.htmlText)
self._saveCache()
return return
def _checkInclude(self, theItem, noteFiles, novelFiles, ignoreFlag): def _checkInclude(self, theItem, noteFiles, novelFiles, ignoreFlag):
@@ -530,7 +561,7 @@ class GuiBuildNovel(QDialog):
elif theFormat == self.FMT_NWD: elif theFormat == self.FMT_NWD:
fileExt = "nwd" fileExt = "nwd"
textFmt = "%s markdown" % nw.__package__ textFmt = "%s Markdown" % nw.__package__
outTool = "NW" outTool = "NW"
elif theFormat == self.FMT_TXT: elif theFormat == self.FMT_TXT:
@@ -539,6 +570,16 @@ class GuiBuildNovel(QDialog):
textFmt = "Plain Text" textFmt = "Plain Text"
outTool = "Qt" outTool = "Qt"
elif theFormat == self.FMT_JSON_H:
fileExt = "json"
textFmt = "JSON + %s HTML" % nw.__package__
outTool = "NW"
elif theFormat == self.FMT_JSON_M:
fileExt = "json"
textFmt = "JSON + %s Markdown" % nw.__package__
outTool = "NW"
else: else:
return False return False
@@ -612,6 +653,33 @@ class GuiBuildNovel(QDialog):
for aLine in self.nwdText: for aLine in self.nwdText:
outFile.write(aLine) outFile.write(aLine)
elif theFormat == self.FMT_JSON_H or theFormat == self.FMT_JSON_M:
jsonData = {
"meta" : {
"workingTitle" : self.theProject.projName,
"novelTitle" : self.theProject.bookTitle,
"authors" : self.theProject.bookAuthors,
}
}
if theFormat == self.FMT_JSON_H:
theBody = []
for htmlPage in self.htmlText:
theBody.append(htmlPage.rstrip("\n").split("\n"))
jsonData["text"] = {
"css" : self.htmlStyle,
"html" : theBody,
}
elif theFormat == self.FMT_JSON_M:
theBody = []
for nwdPage in self.nwdText:
theBody.append(nwdPage.split("\n"))
jsonData["text"] = {
"nwd" : theBody,
}
outFile.write(json.dumps(jsonData, indent=2))
wSuccess = True wSuccess = True
except Exception as e: except Exception as e:
@@ -667,6 +735,72 @@ class GuiBuildNovel(QDialog):
self.docView.qDocument.print(thePrinter) self.docView.qDocument.print(thePrinter)
return return
def _selectFont(self):
"""Open the QFontDialog and set a font for the font style.
"""
currFont = QFont()
currFont.setFamily(self.textFont.text())
currFont.setPointSize(self.textSize.value())
theFont, theStatus = QFontDialog.getFont(currFont, self)
if theStatus:
self.textFont.setText(theFont.family())
self.textSize.setValue(theFont.pointSize())
return
def _loadCache(self):
"""Save the current data to cache.
"""
buildCache = path.join(self.theProject.projCache, nwFiles.BUILD_CACHE)
dataCount = 0
if path.isfile(buildCache):
logger.debug("Loading build cache")
try:
with open(buildCache, mode="r", encoding="utf8") as inFile:
theJson = inFile.read()
theData = json.loads(theJson)
except Exception as e:
logger.error("Failed to load build cache")
logger.error(str(e))
return False
if "htmlText" in theData.keys():
self.htmlText = theData["htmlText"]
dataCount += 1
if "htmlStyle" in theData.keys():
self.htmlStyle = theData["htmlStyle"]
dataCount += 1
if "nwdText" in theData.keys():
self.nwdText = theData["nwdText"]
dataCount += 1
return dataCount == 3
def _saveCache(self):
"""Save the current data to cache.
"""
buildCache = path.join(self.theProject.projCache, nwFiles.BUILD_CACHE)
if self.mainConf.debugInfo:
nIndent = 2
else:
nIndent = None
logger.debug("Saving build cache")
try:
with open(buildCache, mode="w+", encoding="utf8") as outFile:
outFile.write(json.dumps({
"htmlText" : self.htmlText,
"htmlStyle" : self.htmlStyle,
"nwdText" : self.nwdText,
}, indent=nIndent))
except Exception as e:
logger.error("Failed to save build cache")
logger.error(str(e))
return False
return True
def _doClose(self): def _doClose(self):
"""Close button was clicked. """Close button was clicked.
""" """
@@ -709,7 +843,7 @@ class GuiBuildNovel(QDialog):
self.optState.setValue("GuiBuildNovel", "winWidth", self.width()) self.optState.setValue("GuiBuildNovel", "winWidth", self.width())
self.optState.setValue("GuiBuildNovel", "winHeight", self.height()) self.optState.setValue("GuiBuildNovel", "winHeight", self.height())
self.optState.setValue("GuiBuildNovel", "justifyText", self.justifyText.isChecked()) self.optState.setValue("GuiBuildNovel", "justifyText", self.justifyText.isChecked())
self.optState.setValue("GuiBuildNovel", "textFont", self.textFont.currentFont().family()) self.optState.setValue("GuiBuildNovel", "textFont", self.textFont.text())
self.optState.setValue("GuiBuildNovel", "textSize", self.textSize.value()) self.optState.setValue("GuiBuildNovel", "textSize", self.textSize.value())
self.optState.setValue("GuiBuildNovel", "addNovel", self.novelFiles.isChecked()) self.optState.setValue("GuiBuildNovel", "addNovel", self.novelFiles.isChecked())
self.optState.setValue("GuiBuildNovel", "addNotes", self.noteFiles.isChecked()) self.optState.setValue("GuiBuildNovel", "addNotes", self.noteFiles.isChecked())
@@ -746,6 +880,11 @@ class GuiBuildNovelDocView(QTextBrowser):
self.qDocument = self.document() self.qDocument = self.document()
self.qDocument.setDocumentMargin(self.mainConf.textMargin) self.qDocument.setDocumentMargin(self.mainConf.textMargin)
self.setPlaceholderText(
"This area will show the content of the document to be "
"exported or printed. Press the \"Build Novel Project\" "
"button to generate content."
)
theFont = QFont() theFont = QFont()
if self.mainConf.textFont is None: if self.mainConf.textFont is None:
+78 -11
View File
@@ -33,9 +33,8 @@ from os import path
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QWidget, QHBoxLayout, QVBoxLayout, QTabWidget, QComboBox, QSpinBox, QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QLineEdit, QMessageBox,
QPushButton, QFontComboBox, QLineEdit, QDialogButtonBox, QMessageBox, QDialogButtonBox, QFileDialog, QFontDialog
QFileDialog
) )
from nw.gui.additions import QSwitch, QConfigLayout, PagedDialog from nw.gui.additions import QSwitch, QConfigLayout, PagedDialog
@@ -183,6 +182,34 @@ class GuiConfigEditGeneralTab(QWidget):
"This may improve the look of icons on dark themes." "This may improve the look of icons on dark themes."
) )
## Font Family
self.guiFont = QLineEdit()
self.guiFont.setReadOnly(True)
self.guiFont.setFixedWidth(162)
self.guiFont.setText(self.mainConf.guiFont)
self.fontButton = QPushButton("...")
self.fontButton.setMaximumWidth(30)
self.fontButton.clicked.connect(self._selectFont)
self.mainForm.addRow(
"Font family",
self.guiFont,
"Changing this requires restarting %s." % nw.__package__,
theButton = self.fontButton
)
## Font Size
self.guiFontSize = QSpinBox(self)
self.guiFontSize.setMinimum(8)
self.guiFontSize.setMaximum(60)
self.guiFontSize.setSingleStep(1)
self.guiFontSize.setValue(self.mainConf.guiFontSize)
self.mainForm.addRow(
"Font size",
self.guiFontSize,
"Changing this requires restarting %s." % nw.__package__,
theUnit = "pt"
)
# GUI Settings # GUI Settings
# ============ # ============
self.mainForm.addGroupLabel("GUI Settings") self.mainForm.addGroupLabel("GUI Settings")
@@ -266,6 +293,8 @@ class GuiConfigEditGeneralTab(QWidget):
guiTheme = self.selectTheme.currentData() guiTheme = self.selectTheme.currentData()
guiIcons = self.selectIcons.currentData() guiIcons = self.selectIcons.currentData()
guiDark = self.preferDarkIcons.isChecked() guiDark = self.preferDarkIcons.isChecked()
guiFont = self.guiFont.text()
guiFontSize = self.guiFontSize.value()
showFullPath = self.showFullPath.isChecked() showFullPath = self.showFullPath.isChecked()
autoSaveDoc = self.autoSaveDoc.value() autoSaveDoc = self.autoSaveDoc.value()
autoSaveProj = self.autoSaveProj.value() autoSaveProj = self.autoSaveProj.value()
@@ -276,10 +305,14 @@ class GuiConfigEditGeneralTab(QWidget):
# Check if restart is needed # Check if restart is needed
needsRestart |= self.mainConf.guiTheme != guiTheme needsRestart |= self.mainConf.guiTheme != guiTheme
needsRestart |= self.mainConf.guiIcons != guiIcons needsRestart |= self.mainConf.guiIcons != guiIcons
needsRestart |= self.mainConf.guiFont != guiFont
needsRestart |= self.mainConf.guiFontSize != guiFontSize
self.mainConf.guiTheme = guiTheme self.mainConf.guiTheme = guiTheme
self.mainConf.guiIcons = guiIcons self.mainConf.guiIcons = guiIcons
self.mainConf.guiDark = guiDark self.mainConf.guiDark = guiDark
self.mainConf.guiFont = guiFont
self.mainConf.guiFontSize = guiFontSize
self.mainConf.showFullPath = showFullPath self.mainConf.showFullPath = showFullPath
self.mainConf.autoSaveDoc = autoSaveDoc self.mainConf.autoSaveDoc = autoSaveDoc
self.mainConf.autoSaveProj = autoSaveProj self.mainConf.autoSaveProj = autoSaveProj
@@ -321,6 +354,18 @@ class GuiConfigEditGeneralTab(QWidget):
self.askBeforeBackup.setEnabled(theState) self.askBeforeBackup.setEnabled(theState)
return return
def _selectFont(self):
"""Open the QFontDialog and set a font for the font style.
"""
currFont = QFont()
currFont.setFamily(self.mainConf.guiFont)
currFont.setPointSize(self.mainConf.guiFontSize)
theFont, theStatus = QFontDialog.getFont(currFont, self)
if theStatus:
self.guiFont.setText(theFont.family())
self.guiFontSize.setValue(theFont.pointSize())
return
# END Class GuiConfigEditGeneralTab # END Class GuiConfigEditGeneralTab
class GuiConfigEditLayoutTab(QWidget): class GuiConfigEditLayoutTab(QWidget):
@@ -341,26 +386,32 @@ class GuiConfigEditLayoutTab(QWidget):
# ========== # ==========
self.mainForm.addGroupLabel("Text Style") self.mainForm.addGroupLabel("Text Style")
## Font Family ## Font Family
self.textStyleFont = QFontComboBox() self.textStyleFont = QLineEdit()
self.textStyleFont.setMaximumWidth(200) self.textStyleFont.setReadOnly(True)
self.textStyleFont.setCurrentFont(QFont(self.mainConf.textFont)) self.textStyleFont.setFixedWidth(162)
self.textStyleFont.setText(self.mainConf.textFont)
self.fontButton = QPushButton("...")
self.fontButton.setMaximumWidth(30)
self.fontButton.clicked.connect(self._selectFont)
self.mainForm.addRow( self.mainForm.addRow(
"Font family", "Font family",
self.textStyleFont, self.textStyleFont,
"Font for the document editor and viewer." "Font for the document editor and viewer.",
theButton = self.fontButton
) )
## Font Size ## Font Size
self.textStyleSize = QSpinBox(self) self.textStyleSize = QSpinBox(self)
self.textStyleSize.setMinimum(5) self.textStyleSize.setMinimum(8)
self.textStyleSize.setMaximum(120) self.textStyleSize.setMaximum(60)
self.textStyleSize.setSingleStep(1) self.textStyleSize.setSingleStep(1)
self.textStyleSize.setValue(self.mainConf.textSize) self.textStyleSize.setValue(self.mainConf.textSize)
self.mainForm.addRow( self.mainForm.addRow(
"Font size", "Font size",
self.textStyleSize, self.textStyleSize,
theUnit="pt" theUnit = "pt"
) )
# Text Flow # Text Flow
@@ -461,7 +512,7 @@ class GuiConfigEditLayoutTab(QWidget):
validEntries = True validEntries = True
needsRestart = False needsRestart = False
textFont = self.textStyleFont.currentFont().family() textFont = self.textStyleFont.text()
textSize = self.textStyleSize.value() textSize = self.textStyleSize.value()
textWidth = self.textFlowMax.value() textWidth = self.textFlowMax.value()
zenWidth = self.zenDocWidth.value() zenWidth = self.zenDocWidth.value()
@@ -487,6 +538,22 @@ class GuiConfigEditLayoutTab(QWidget):
return validEntries, needsRestart return validEntries, needsRestart
##
# Slots
##
def _selectFont(self):
"""Open the QFontDialog and set a font for the font style.
"""
currFont = QFont()
currFont.setFamily(self.mainConf.textFont)
currFont.setPointSize(self.mainConf.textSize)
theFont, theStatus = QFontDialog.getFont(currFont, self)
if theStatus:
self.textStyleFont.setText(theFont.family())
self.textStyleSize.setValue(theFont.pointSize())
return
# END Class GuiConfigEditLayoutTab # END Class GuiConfigEditLayoutTab
class GuiConfigEditEditingTab(QWidget): class GuiConfigEditEditingTab(QWidget):
+40 -22
View File
@@ -29,7 +29,7 @@ import logging
import nw import nw
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont from PyQt5.QtGui import QFont, QIcon, QPixmap
from PyQt5.QtWidgets import QFrame, QGridLayout, QLabel from PyQt5.QtWidgets import QFrame, QGridLayout, QLabel
from nw.constants import ( from nw.constants import (
@@ -47,30 +47,34 @@ class GuiDocDetails(QFrame):
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
self.theParent = theParent self.theParent = theParent
self.theProject = theProject self.theProject = theProject
self.theTheme = theParent.theTheme
self.theHandle = None
self.mainBox = QGridLayout(self) self.mainBox = QGridLayout(self)
self.mainBox.setVerticalSpacing(1) self.mainBox.setVerticalSpacing(1)
self.mainBox.setHorizontalSpacing(6) self.mainBox.setHorizontalSpacing(6)
self.setLayout(self.mainBox) self.setLayout(self.mainBox)
self.fntLabel = QFont() self.pS = 0.9*self.theTheme.fontPointSize
self.fntLabel.setPointSize(10) self.iPx = self.theTheme.textIconSize
self.fntLabel.setBold(True) self.sPx = int(round(0.8*self.theTheme.textIconSize))
self.fntFixed = QFont() self.expCheck = self.theTheme.getPixmap("check", (self.iPx, self.iPx))
self.fntFixed.setFamily("Monospace") self.expCross = self.theTheme.getPixmap("cross", (self.iPx, self.iPx))
self.fntFixed.setPointSize(10)
self.fntLabel = QFont()
self.fntLabel.setBold(True)
self.fntLabel.setPointSizeF(self.pS)
self.fntValue = QFont() self.fntValue = QFont()
self.fntValue.setPointSize(10) self.fntValue.setPointSizeF(self.pS)
# Label # Label
self.labelName = QLabel("Label ") self.labelName = QLabel("Label")
self.labelName.setFont(self.fntLabel) self.labelName.setFont(self.fntLabel)
self.labelName.setAlignment(Qt.AlignLeft | Qt.AlignBaseline) self.labelName.setAlignment(Qt.AlignLeft | Qt.AlignBaseline)
self.labelFlag = QLabel("") self.labelFlag = QLabel("")
self.labelFlag.setFont(self.fntFixed)
self.labelFlag.setAlignment(Qt.AlignRight | Qt.AlignBaseline) self.labelFlag.setAlignment(Qt.AlignRight | Qt.AlignBaseline)
self.labelData = QLabel("") self.labelData = QLabel("")
@@ -79,12 +83,11 @@ class GuiDocDetails(QFrame):
self.labelData.setWordWrap(True) self.labelData.setWordWrap(True)
# Status # Status
self.statusName = QLabel("Status ") self.statusName = QLabel("Status")
self.statusName.setFont(self.fntLabel) self.statusName.setFont(self.fntLabel)
self.statusName.setAlignment(Qt.AlignLeft) self.statusName.setAlignment(Qt.AlignLeft)
self.statusFlag = QLabel("") self.statusFlag = QLabel("")
self.statusFlag.setFont(self.fntFixed)
self.statusFlag.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.statusFlag.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.statusData = QLabel("") self.statusData = QLabel("")
@@ -92,12 +95,12 @@ class GuiDocDetails(QFrame):
self.statusData.setAlignment(Qt.AlignLeft) self.statusData.setAlignment(Qt.AlignLeft)
# Class # Class
self.className = QLabel("Class ") self.className = QLabel("Class")
self.className.setFont(self.fntLabel) self.className.setFont(self.fntLabel)
self.className.setAlignment(Qt.AlignLeft) self.className.setAlignment(Qt.AlignLeft)
self.classFlag = QLabel("") self.classFlag = QLabel("")
self.classFlag.setFont(self.fntFixed) self.classFlag.setFont(self.fntValue)
self.classFlag.setAlignment(Qt.AlignRight) self.classFlag.setAlignment(Qt.AlignRight)
self.classData = QLabel("") self.classData = QLabel("")
@@ -105,12 +108,12 @@ class GuiDocDetails(QFrame):
self.classData.setAlignment(Qt.AlignLeft) self.classData.setAlignment(Qt.AlignLeft)
# Layout # Layout
self.layoutName = QLabel("Layout ") self.layoutName = QLabel("Layout")
self.layoutName.setFont(self.fntLabel) self.layoutName.setFont(self.fntLabel)
self.layoutName.setAlignment(Qt.AlignLeft) self.layoutName.setAlignment(Qt.AlignLeft)
self.layoutFlag = QLabel("") self.layoutFlag = QLabel("")
self.layoutFlag.setFont(self.fntFixed) self.layoutFlag.setFont(self.fntValue)
self.layoutFlag.setAlignment(Qt.AlignRight) self.layoutFlag.setAlignment(Qt.AlignRight)
self.layoutData = QLabel("") self.layoutData = QLabel("")
@@ -173,6 +176,12 @@ class GuiDocDetails(QFrame):
self.mainBox.setColumnStretch(3,0) self.mainBox.setColumnStretch(3,0)
self.mainBox.setColumnStretch(4,0) self.mainBox.setColumnStretch(4,0)
# Make sure the columns for flags and counts don't resize too often
flagWidth = self.theTheme.getTextWidth("Mm", self.fntValue)
countWidth = self.theTheme.getTextWidth("99,999", self.fntValue)
self.mainBox.setColumnMinimumWidth(1, flagWidth)
self.mainBox.setColumnMinimumWidth(4, countWidth)
logger.debug("DocDetails initialisation complete") logger.debug("DocDetails initialisation complete")
return return
@@ -181,9 +190,20 @@ class GuiDocDetails(QFrame):
# Class Methods # Class Methods
## ##
def updateCounts(self, tHandle, cC, wC, pC):
"""Just update the counts if the handle is the same as the one
we're already showing.
"""
if tHandle == self.theHandle:
self.cCountData.setText("{:n}".format(cC))
self.wCountData.setText("{:n}".format(wC))
self.pCountData.setText("{:n}".format(pC))
return
def updateViewBox(self, tHandle): def updateViewBox(self, tHandle):
"""Populate the details box from a given handle. """Populate the details box from a given handle.
""" """
self.theHandle = tHandle
nwItem = self.theProject.projTree[tHandle] nwItem = self.theProject.projTree[tHandle]
if nwItem is None: if nwItem is None:
@@ -214,14 +234,12 @@ class GuiDocDetails(QFrame):
if nwItem.itemType == nwItemType.FILE: if nwItem.itemType == nwItemType.FILE:
if nwItem.isExported: if nwItem.isExported:
exportFlag = nwUnicode.U_CHECK self.labelFlag.setPixmap(self.expCheck)
else: else:
exportFlag = " " self.labelFlag.setPixmap(self.expCross)
else: else:
exportFlag = "-" self.labelFlag.setPixmap(QPixmap(1,1))
self.statusFlag.setPixmap(flagIcon.pixmap(self.sPx, self.sPx))
self.labelFlag.setText(exportFlag)
self.statusFlag.setPixmap(flagIcon.pixmap(10, 10))
self.classFlag.setText(nwLabels.CLASS_FLAG[nwItem.itemClass]) self.classFlag.setText(nwLabels.CLASS_FLAG[nwItem.itemClass])
if nwItem.itemLayout == nwItemLayout.NO_LAYOUT: if nwItem.itemLayout == nwItemLayout.NO_LAYOUT:
self.layoutFlag.setText("-") self.layoutFlag.setText("-")
+19 -9
View File
@@ -300,15 +300,16 @@ class GuiDocEditor(QTextEdit):
docText = self.getText() docText = self.getText()
cursPos = self.getCursorPosition() cursPos = self.getCursorPosition()
theItem = self.nwDocument.theItem self.nwDocument.theItem.setCharCount(self.charCount)
theItem.setCharCount(self.charCount) self.nwDocument.theItem.setWordCount(self.wordCount)
theItem.setWordCount(self.wordCount) self.nwDocument.theItem.setParaCount(self.paraCount)
theItem.setParaCount(self.paraCount) self.nwDocument.theItem.setCursorPos(cursPos)
theItem.setCursorPos(cursPos)
self.nwDocument.saveDocument(docText) self.nwDocument.saveDocument(docText)
self.setDocumentChanged(False) self.setDocumentChanged(False)
self.theParent.theIndex.scanText(theItem.itemHandle, docText) self.theParent.theIndex.scanText(
self.nwDocument.theItem.itemHandle, docText
)
return True return True
@@ -737,15 +738,24 @@ class GuiDocEditor(QTextEdit):
def _updateCounts(self): def _updateCounts(self):
"""Slot for the word counter's finished signal """Slot for the word counter's finished signal
""" """
if self.theHandle is None or self.nwDocument.theItem is None:
return
logger.verbose("Updating word count") logger.verbose("Updating word count")
tHandle = self.nwDocument.docHandle
self.charCount = self.wCounter.charCount self.charCount = self.wCounter.charCount
self.wordCount = self.wCounter.wordCount self.wordCount = self.wCounter.wordCount
self.paraCount = self.wCounter.paraCount self.paraCount = self.wCounter.paraCount
self.theParent.statusBar.setCounts(self.charCount,self.wordCount,self.paraCount) self.nwDocument.theItem.setCharCount(self.charCount)
self.theParent.treeView.propagateCount(tHandle, self.wordCount) self.nwDocument.theItem.setWordCount(self.wordCount)
self.nwDocument.theItem.setParaCount(self.paraCount)
self.theParent.statusBar.setCounts(self.charCount, self.wordCount, self.paraCount)
self.theParent.treeView.propagateCount(self.theHandle, self.wordCount)
self.theParent.treeView.projectWordCount() self.theParent.treeView.projectWordCount()
self.theParent.treeMeta.updateCounts(
self.theHandle, self.charCount, self.wordCount, self.paraCount
)
self._checkDocSize(self.charCount) self._checkDocSize(self.charCount)
return return
+3 -2
View File
@@ -52,19 +52,20 @@ class GuiDocTitleBar(QLabel):
self.setText("") self.setText("")
self.setIndent(0) self.setIndent(0)
self.setMargin(0) self.setMargin(0)
self.setContentsMargins(0,0,0,0) self.setContentsMargins(0, 0, 0, 0)
self.setAutoFillBackground(True) self.setAutoFillBackground(True)
self.setAlignment(Qt.AlignCenter) self.setAlignment(Qt.AlignCenter)
self.setWordWrap(True) self.setWordWrap(True)
self.setFrameShape(QFrame.NoFrame) self.setFrameShape(QFrame.NoFrame)
self.setLineWidth(0) self.setLineWidth(0)
lblPalette = self.palette() lblPalette = self.palette()
lblPalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack)) lblPalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack))
lblPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText)) lblPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
self.setPalette(lblPalette) self.setPalette(lblPalette)
lblFont = self.font() lblFont = self.font()
lblFont.setPointSizeF(0.9*self.theTheme.defFontSize) lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize)
self.setFont(lblFont) self.setFont(lblFont)
logger.debug("DocTitleBar initialisation complete") logger.debug("DocTitleBar initialisation complete")
+38 -33
View File
@@ -29,7 +29,7 @@ import logging
import nw import nw
from PyQt5.QtCore import Qt, QSize from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QFont, QColor from PyQt5.QtGui import QFont, QColor, QIcon
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
qApp, QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMessageBox qApp, QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMessageBox
) )
@@ -45,8 +45,9 @@ class GuiDocTree(QTreeWidget):
C_NAME = 0 C_NAME = 0
C_COUNT = 1 C_COUNT = 1
C_FLAGS = 2 C_EXPORT = 2
C_HANDLE = 3 C_FLAGS = 3
C_HANDLE = 4
def __init__(self, theParent, theProject): def __init__(self, theParent, theProject):
QTreeWidget.__init__(self, theParent) QTreeWidget.__init__(self, theParent)
@@ -64,15 +65,20 @@ class GuiDocTree(QTreeWidget):
self.clearTree() self.clearTree()
# Build GUI # Build GUI
self.setIconSize(QSize(13,13)) iPx = self.theTheme.textIconSize
self.setIconSize(QSize(iPx, iPx))
self.setExpandsOnDoubleClick(True) self.setExpandsOnDoubleClick(True)
self.setIndentation(13) self.setIndentation(iPx)
self.setColumnCount(4) self.setColumnCount(5)
self.setHeaderLabels(["Label","Words","Flags","Handle"]) self.setHeaderLabels(["Label", "Words", "Inc", "Flags", "Handle"])
self.hideColumn(self.C_HANDLE) self.hideColumn(self.C_HANDLE)
treeHead = self.headerItem() treeHead = self.headerItem()
treeHead.setTextAlignment(self.C_COUNT,Qt.AlignRight) treeHead.setTextAlignment(self.C_COUNT, Qt.AlignRight)
treeHead.setToolTip(self.C_NAME, "Item label")
treeHead.setToolTip(self.C_COUNT, "Word count")
treeHead.setToolTip(self.C_EXPORT, "Include in build")
treeHead.setToolTip(self.C_FLAGS, "Status, class, and layout flags")
# Allow Move by Drag & Drop # Allow Move by Drag & Drop
self.setDragEnabled(True) self.setDragEnabled(True)
@@ -87,11 +93,11 @@ class GuiDocTree(QTreeWidget):
# self.setSelectionMode(QAbstractItemView.ExtendedSelection) # self.setSelectionMode(QAbstractItemView.ExtendedSelection)
# self.setSelectionBehavior(QAbstractItemView.SelectRows) # self.setSelectionBehavior(QAbstractItemView.SelectRows)
for colN in range(len(self.mainConf.treeColWidth)): for colN, colW in enumerate(self.mainConf.treeColWidth):
self.setColumnWidth(colN,self.mainConf.treeColWidth[colN]) self.setColumnWidth(colN, colW)
self.fontFlags = QFont("Monospace",10) self.resizeColumnToContents(self.C_EXPORT)
self.fontCount = QFont("Monospace",10) self.resizeColumnToContents(self.C_FLAGS)
logger.debug("DocTree initialisation complete") logger.debug("DocTree initialisation complete")
@@ -200,8 +206,8 @@ class GuiDocTree(QTreeWidget):
def revealTreeItem(self, tHandle): def revealTreeItem(self, tHandle):
"""Reveal a newly added project item in the project tree. """Reveal a newly added project item in the project tree.
""" """
nwItem = self.theProject.projTree[tHandle] nwItem = self.theProject.projTree[tHandle]
trItem = self._addTreeItem(nwItem) trItem = self._addTreeItem(nwItem)
pHandle = nwItem.parHandle pHandle = nwItem.parHandle
if pHandle is not None and pHandle in self.theMap.keys(): if pHandle is not None and pHandle in self.theMap.keys():
self.theMap[pHandle].setExpanded(True) self.theMap[pHandle].setExpanded(True)
@@ -271,7 +277,6 @@ class GuiDocTree(QTreeWidget):
retVals = [ retVals = [
self.columnWidth(0), self.columnWidth(0),
self.columnWidth(1), self.columnWidth(1),
self.columnWidth(2),
] ]
return retVals return retVals
@@ -422,30 +427,30 @@ class GuiDocTree(QTreeWidget):
tHandle = nwItem.itemHandle tHandle = nwItem.itemHandle
pHandle = nwItem.parHandle pHandle = nwItem.parHandle
stExport = " " expIcon = QIcon()
stClass = nwLabels.CLASS_FLAG[nwItem.itemClass]
stLayout = ""
stClass = nwLabels.CLASS_FLAG[nwItem.itemClass]
stLayout = ""
if nwItem.itemType == nwItemType.FILE: if nwItem.itemType == nwItemType.FILE:
stLayout = "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout] stLayout = "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout]
if nwItem.isExported: if nwItem.isExported:
stExport = nwUnicode.U_CHECK expIcon = self.theTheme.getIcon("check")
else: else:
stExport = "-" expIcon = self.theTheme.getIcon("cross")
tStatus = stClass+stLayout
tStatus = stExport+" "+stClass+stLayout
iStatus = nwItem.itemStatus iStatus = nwItem.itemStatus
if tClass == nwItemClass.NOVEL: if tClass == nwItemClass.NOVEL:
iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's valid iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's valid
flagIcon = self.theParent.statusIcons[iStatus] flagIcon = self.theParent.statusIcons[iStatus]
else: else:
iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's valid iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's valid
flagIcon = self.theParent.importIcons[iStatus] flagIcon = self.theParent.importIcons[iStatus]
trItem.setText(self.C_NAME, tName) trItem.setText(self.C_NAME, tName)
trItem.setText(self.C_FLAGS,tStatus) trItem.setIcon(self.C_EXPORT, expIcon)
trItem.setIcon(self.C_FLAGS,flagIcon) trItem.setText(self.C_FLAGS, tStatus)
trItem.setIcon(self.C_FLAGS, flagIcon)
return return
@@ -569,12 +574,11 @@ class GuiDocTree(QTreeWidget):
newItem.setText(self.C_NAME, "") newItem.setText(self.C_NAME, "")
newItem.setText(self.C_COUNT, "0") newItem.setText(self.C_COUNT, "0")
newItem.setText(self.C_EXPORT, "")
newItem.setText(self.C_FLAGS, "") newItem.setText(self.C_FLAGS, "")
newItem.setText(self.C_HANDLE, tHandle) newItem.setText(self.C_HANDLE, tHandle)
# newItem.setForeground(self.C_COUNT,QColor(*self.theParent.theTheme.treeWCount))
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight) newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
newItem.setFont(self.C_FLAGS, self.fontFlags)
self.theMap[tHandle] = newItem self.theMap[tHandle] = newItem
if pHandle is None: if pHandle is None:
@@ -608,15 +612,15 @@ class GuiDocTree(QTreeWidget):
"""Adds the trash root folder if it doesn't already exist in the """Adds the trash root folder if it doesn't already exist in the
project tree. project tree.
""" """
trashHandle = self.theProject.projTree.trashRoot() trashHandle = self.theProject.trashFolder()
if trashHandle is None: if trashHandle is None:
self.theProject.addTrash() return None
trItem = self._getTreeItem(trashHandle)
if trItem is None:
trItem = self._addTreeItem( trItem = self._addTreeItem(
self.theProject.projTree[trashHandle] self.theProject.projTree[trashHandle]
) )
trItem.setExpanded(True) trItem.setExpanded(True)
else:
trItem = self._getTreeItem(trashHandle)
return trItem return trItem
def _addOrphanedRoot(self): def _addOrphanedRoot(self):
@@ -627,6 +631,7 @@ class GuiDocTree(QTreeWidget):
newItem = QTreeWidgetItem([""]*4) newItem = QTreeWidgetItem([""]*4)
newItem.setText(self.C_NAME, "Orphaned Files") newItem.setText(self.C_NAME, "Orphaned Files")
newItem.setText(self.C_COUNT, "") newItem.setText(self.C_COUNT, "")
newItem.setText(self.C_EXPORT, "")
newItem.setText(self.C_FLAGS, "") newItem.setText(self.C_FLAGS, "")
newItem.setText(self.C_HANDLE, "") newItem.setText(self.C_HANDLE, "")
self.addTopLevelItem(newItem) self.addTopLevelItem(newItem)
+2
View File
@@ -93,6 +93,8 @@ class GuiIcons:
"clear" : (QStyle.SP_LineEditClearButton, "clear_left"), "clear" : (QStyle.SP_LineEditClearButton, "clear_left"),
"save" : (QStyle.SP_DialogSaveButton, "document-save"), "save" : (QStyle.SP_DialogSaveButton, "document-save"),
"edit" : (None, None), "edit" : (None, None),
"check" : (None, None),
"cross" : (None, None),
## Other Icons ## Other Icons
"warning" : (QStyle.SP_MessageBoxWarning, "dialog-warning"), "warning" : (QStyle.SP_MessageBoxWarning, "dialog-warning"),
+13 -12
View File
@@ -48,6 +48,7 @@ class GuiMainStatus(QStatusBar):
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
self.theParent = theParent self.theParent = theParent
self.theTheme = theParent.theTheme
self.refTime = None self.refTime = None
self.charCount = 0 self.charCount = 0
@@ -56,11 +57,11 @@ class GuiMainStatus(QStatusBar):
self.projWords = 0 self.projWords = 0
self.sessWords = 0 self.sessWords = 0
self.monoFont = QFont("Monospace",10) colNone = QColor(*self.theTheme.statNone)
colTrue = QColor(*self.theTheme.statUnsaved)
colFalse = QColor(*self.theTheme.statSaved)
colNone = QColor(*self.theParent.theTheme.statNone) iPx = self.theTheme.textIconSize
colTrue = QColor(*self.theParent.theTheme.statUnsaved)
colFalse = QColor(*self.theParent.theTheme.statSaved)
# Permanent Widgets # Permanent Widgets
# ================= # =================
@@ -68,14 +69,14 @@ class GuiMainStatus(QStatusBar):
## The Spell Checker Language ## The Spell Checker Language
self.langIcon = QLabel("") self.langIcon = QLabel("")
self.langText = QLabel("None") self.langText = QLabel("None")
self.langIcon.setPixmap(self.theParent.theTheme.getPixmap("status_lang",(14,14))) self.langIcon.setPixmap(self.theTheme.getPixmap("status_lang", (iPx, iPx)))
self.langIcon.setContentsMargins(0, 0, 0, 0) self.langIcon.setContentsMargins(0, 0, 0, 0)
self.langText.setContentsMargins(0, 0, 8, 0) self.langText.setContentsMargins(0, 0, 8, 0)
self.addPermanentWidget(self.langIcon) self.addPermanentWidget(self.langIcon)
self.addPermanentWidget(self.langText) self.addPermanentWidget(self.langText)
## The Editor Status ## The Editor Status
self.docIcon = StatusLED(colNone, colTrue, colFalse, 14, 14, self) self.docIcon = StatusLED(colNone, colTrue, colFalse, iPx, iPx, self)
self.docText = QLabel("Editor") self.docText = QLabel("Editor")
self.docIcon.setContentsMargins(0, 0, 0, 0) self.docIcon.setContentsMargins(0, 0, 0, 0)
self.docText.setContentsMargins(0, 0, 8, 0) self.docText.setContentsMargins(0, 0, 8, 0)
@@ -83,7 +84,7 @@ class GuiMainStatus(QStatusBar):
self.addPermanentWidget(self.docText) self.addPermanentWidget(self.docText)
## The Project Status ## The Project Status
self.projIcon = StatusLED(colNone, colTrue, colFalse, 14, 14, self) self.projIcon = StatusLED(colNone, colTrue, colFalse, iPx, iPx, self)
self.projText = QLabel("Project") self.projText = QLabel("Project")
self.projIcon.setContentsMargins(0, 0, 0, 0) self.projIcon.setContentsMargins(0, 0, 0, 0)
self.projText.setContentsMargins(0, 0, 8, 0) self.projText.setContentsMargins(0, 0, 8, 0)
@@ -93,19 +94,19 @@ class GuiMainStatus(QStatusBar):
## The Project and Session Stats ## The Project and Session Stats
self.statsIcon = QLabel() self.statsIcon = QLabel()
self.statsText = QLabel("") self.statsText = QLabel("")
self.statsIcon.setPixmap(self.theParent.theTheme.getPixmap("status_stats",(14,14))) self.statsIcon.setPixmap(self.theTheme.getPixmap("status_stats", (iPx, iPx)))
self.statsIcon.setContentsMargins(0, 0, 0, 0) self.statsIcon.setContentsMargins(0, 0, 0, 0)
self.statsText.setContentsMargins(0, 0, 8, 0) self.statsText.setContentsMargins(0, 0, 8, 0)
self.addPermanentWidget(self.statsIcon) self.addPermanentWidget(self.statsIcon)
self.addPermanentWidget(self.statsText) self.addPermanentWidget(self.statsText)
## The Session Clock ## The Session Clock
### Set the mimimum width so the label doesn't rescale every second
self.timeIcon = QLabel() self.timeIcon = QLabel()
self.timeText = QLabel("") self.timeText = QLabel("")
self.timeIcon.setPixmap(self.theParent.theTheme.getPixmap("status_time",(14,14))) self.timeIcon.setPixmap(self.theTheme.getPixmap("status_time", (iPx, iPx)))
self.timeText.setToolTip("Session Time") self.timeText.setToolTip("Session Time")
self.timeText.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.timeText.setMinimumWidth(self.theTheme.getTextWidth("00:00:00:"))
self.timeText.setFont(self.monoFont)
self.timeIcon.setContentsMargins(0, 0, 0, 0) self.timeIcon.setContentsMargins(0, 0, 0, 0)
self.timeText.setContentsMargins(0, 0, 0, 0) self.timeText.setContentsMargins(0, 0, 0, 0)
self.addPermanentWidget(self.timeIcon) self.addPermanentWidget(self.timeIcon)
@@ -200,7 +201,7 @@ class GuiMainStatus(QStatusBar):
"""Update statistics. """Update statistics.
""" """
self.statsText.setToolTip( self.statsText.setToolTip(
"D: Document word count<br>P: Project word count" "D: Document word count<br>P: Project word count (session change)"
) )
self.statsText.setText(( self.statsText.setText((
"D:{wC:n} P:{pWC:n} ({sWC:+n})" "D:{wC:n} P:{pWC:n} ({sWC:+n})"
+78 -5
View File
@@ -30,9 +30,12 @@ import configparser
import nw import nw
from os import path, listdir from os import path, listdir
from math import ceil
from PyQt5.QtWidgets import qApp from PyQt5.QtWidgets import qApp
from PyQt5.QtGui import QPalette, QColor, QIcon from PyQt5.QtGui import (
QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase
)
from nw.constants import nwAlert from nw.constants import nwAlert
from nw.gui.icons import GuiIcons from nw.gui.icons import GuiIcons
@@ -48,7 +51,7 @@ class GuiTheme:
self.theIcons = GuiIcons(self.theParent) self.theIcons = GuiIcons(self.theParent)
self.guiPalette = QPalette() self.guiPalette = QPalette()
self.guiPath = "gui" self.guiPath = "gui"
self.iconPath = "icons" self.fontPath = "fonts"
self.syntaxPath = "syntax" self.syntaxPath = "syntax"
self.cssName = "style.qss" self.cssName = "style.qss"
self.confName = "theme.conf" self.confName = "theme.conf"
@@ -110,7 +113,10 @@ class GuiTheme:
self.syntaxFile = None self.syntaxFile = None
self.confFile = None self.confFile = None
self.cssFile = None self.cssFile = None
self.guiFontDB = QFontDatabase()
self.loadFonts()
self.updateFont()
self.updateTheme() self.updateTheme()
self.theIcons.updateTheme() self.theIcons.updateTheme()
@@ -119,19 +125,86 @@ class GuiTheme:
self.loadDecoration = self.theIcons.loadDecoration self.loadDecoration = self.theIcons.loadDecoration
# Extract Other Info # Extract Other Info
self.defFont = qApp.font() self.guiDPI = qApp.primaryScreen().physicalDotsPerInch()
self.defFontSize = self.defFont.pointSizeF() self.guiFont = qApp.font()
qMetric = QFontMetrics(self.guiFont)
self.fontPointSize = self.guiFont.pointSizeF()
self.fontPixelSize = int(round(qMetric.height()))
self.baseIconSize = int(round(qMetric.ascent()))
self.textIconSize = int(round(qMetric.ascent() + qMetric.leading()))
logger.verbose("GUI Font Family: %s" % self.guiFont.family())
logger.verbose("GUI Font Point Size: %.2f" % self.fontPointSize)
logger.verbose("GUI Font Pixel Size: %d" % self.fontPixelSize)
logger.verbose("GUI Base Icon Size: %d" % self.baseIconSize)
logger.verbose("GUI Text Icon Size: %d" % self.textIconSize)
return return
##
# Methods
##
def getTextWidth(self, theText, theFont=None):
"""Returns the width needed to contain a given piece of text.
"""
if isinstance(theFont, QFont):
qMetrics = QFontMetrics(theFont)
else:
qMetrics = QFontMetrics(self.guiFont)
return int(ceil(qMetrics.boundingRect(theText).width()))
## ##
# Actions # Actions
## ##
def loadFonts(self):
"""Add the fonts in the assets fonts folder to the app.
"""
ttfList = []
fontAssets = path.join(self.mainConf.assetPath, self.fontPath)
for fontFam in listdir(fontAssets):
fontDir = path.join(fontAssets, fontFam)
if path.isdir(fontDir):
if not fontFam in self.guiFontDB.families():
for fontFile in listdir(fontDir):
ttfFile = path.join(fontDir, fontFile)
if path.isfile(ttfFile) and fontFile.endswith(".ttf"):
ttfList.append(ttfFile)
for ttfFile in ttfList:
logger.verbose("Font asset: %s" % path.relpath(ttfFile))
fontID = self.guiFontDB.addApplicationFont(ttfFile)
if fontID < 0:
logger.error("Failed to add font: %s" % path.relpath(ttfFile))
return
def updateFont(self):
"""Updated the GUI's font style from settings,
"""
theFont = QFont()
if self.mainConf.guiFont not in self.guiFontDB.families():
if self.mainConf.osWindows:
# On Windows, default to Cantarell provided by novelWriter
theFont.setFamily("Cantarell")
theFont.setPointSize(11)
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): def updateTheme(self):
"""Update the GUI theme from theme files. """Update the GUI theme from theme files.
""" """
self.guiTheme = self.mainConf.guiTheme self.guiTheme = self.mainConf.guiTheme
self.guiSyntax = self.mainConf.guiSyntax self.guiSyntax = self.mainConf.guiSyntax
self.themeRoot = self.mainConf.themeRoot self.themeRoot = self.mainConf.themeRoot
+27 -17
View File
@@ -294,12 +294,14 @@ class GuiMain(QMainWindow):
return False return False
logger.info("Creating new project") logger.info("Creating new project")
self.theProject.newProject() if self.theProject.setProjectPath(projPath, newProject=True):
self.theProject.setProjectPath(projPath) self.theProject.newProject()
self.rebuildTree() self.rebuildTree()
self.saveProject() self.saveProject()
self.hasProject = True self.hasProject = True
self.statusBar.setRefTime(self.theProject.projOpened) self.statusBar.setRefTime(self.theProject.projOpened)
else:
return False
return True return True
@@ -1017,25 +1019,33 @@ class GuiMain(QMainWindow):
return return
def _treeDoubleClick(self, tItem, colNo): def _treeDoubleClick(self, tItem, colNo):
tHandle = tItem.text(3) """The user double-clicked an item in the tree. If it is a file,
we open it. Otherwise, we do nothing.
"""
tHandle = tItem.text(self.treeView.C_HANDLE)
logger.verbose("User double clicked tree item with handle %s" % tHandle) logger.verbose("User double clicked tree item with handle %s" % tHandle)
nwItem = self.theProject.projTree[tHandle] nwItem = self.theProject.projTree[tHandle]
if nwItem.itemType == nwItemType.FILE: if nwItem is not None:
logger.verbose("Requested item %s is a file" % tHandle) if nwItem.itemType == nwItemType.FILE:
self.openDocument(tHandle) logger.verbose("Requested item %s is a file" % tHandle)
else: self.openDocument(tHandle)
logger.verbose("Requested item %s is a folder" % tHandle) else:
logger.verbose("Requested item %s is a folder" % tHandle)
return return
def _treeKeyPressReturn(self): def _treeKeyPressReturn(self):
"""The user pressed return an item in the tree. If it is a file,
we open it. Otherwise, we do nothing.
"""
tHandle = self.treeView.getSelectedHandle() tHandle = self.treeView.getSelectedHandle()
logger.verbose("User pressed return on tree item with handle %s" % tHandle) logger.verbose("User pressed return on tree item with handle %s" % tHandle)
nwItem = self.theProject.projTree[tHandle] nwItem = self.theProject.projTree[tHandle]
if nwItem.itemType == nwItemType.FILE: if nwItem is not None:
logger.verbose("Requested item %s is a file" % tHandle) if nwItem.itemType == nwItemType.FILE:
self.openDocument(tHandle) logger.verbose("Requested item %s is a file" % tHandle)
else: self.openDocument(tHandle)
logger.verbose("Requested item %s is a folder" % tHandle) else:
logger.verbose("Requested item %s is a folder" % tHandle)
return return
def _keyPressEscape(self): def _keyPressEscape(self):
+10 -3
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="4" autoCount="0" timeStamp="2020-05-29 23:06:02"> <novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="4" autoCount="1" timeStamp="2020-05-30 15:31:30" editTime="12">
<project> <project>
<name>New Project</name> <name>New Project</name>
<title></title> <title></title>
@@ -9,7 +9,7 @@
<spellCheck>True</spellCheck> <spellCheck>True</spellCheck>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>31489056e0916</lastEdited> <lastEdited>31489056e0916</lastEdited>
<lastViewed>31489056e0916</lastViewed> <lastViewed>None</lastViewed>
<lastWordCount>86</lastWordCount> <lastWordCount>86</lastWordCount>
<autoReplace/> <autoReplace/>
<titleFormat> <titleFormat>
@@ -35,7 +35,7 @@
<entry blue="0" green="200" red="50">Main</entry> <entry blue="0" green="200" red="50">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="9"> <content count="10">
<item handle="73475cb40a568" order="0" parent="None"> <item handle="73475cb40a568" order="0" parent="None">
<name>Novel</name> <name>Novel</name>
<type>ROOT</type> <type>ROOT</type>
@@ -119,5 +119,12 @@
<paraCount>1</paraCount> <paraCount>1</paraCount>
<cursorPos>68</cursorPos> <cursorPos>68</cursorPos>
</item> </item>
<item handle="41cfc0d1f2d12" order="4" parent="None">
<name>Trash</name>
<type>TRASH</type>
<class>TRASH</class>
<status>None</status>
<expanded>True</expanded>
</item>
</content> </content>
</novelWriterXML> </novelWriterXML>
+2
View File
@@ -4,6 +4,8 @@ theme = default
syntax = default_light syntax = default_light
icons = typicons_grey_light icons = typicons_grey_light
guidark = False guidark = False
guifont =
guifontsize = 11
[Sizes] [Sizes]
geometry = 1100, 650 geometry = 1100, 650
+9
View File
@@ -235,6 +235,15 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
assert nwGUI.closeDocViewer() assert nwGUI.closeDocViewer()
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
# Check a Quick Create and Delete
assert nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
newHandle = nwGUI.treeView.getSelectedHandle()
assert nwGUI.theProject.projTree["031b4af5197ec"] is not None
assert nwGUI.treeView.deleteItem()
assert nwGUI.treeView.setSelectedHandle(newHandle)
assert nwGUI.treeView.deleteItem()
assert nwGUI.saveProject()
# Check the files # Check the files
refFile = path.join(nwTempGUI, "nwProject.nwx") refFile = path.join(nwTempGUI, "nwProject.nwx")
assert cmpFiles(refFile, path.join(nwRef, "gui", "1_nwProject.nwx"), [2]) assert cmpFiles(refFile, path.join(nwRef, "gui", "1_nwProject.nwx"), [2])