Merge branch 'dev' into editor_footer

This commit is contained in:
Veronica K. B. Olsen
2020-06-17 22:20:36 +02:00
26 changed files with 1188 additions and 320 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ install:
- pip install --upgrade pip - pip install --upgrade pip
- pip install -r requirements.txt - pip install -r requirements.txt
# - pip install pytest-faulthandler # - pip install pytest-faulthandler
- pip install PyVirtualDisplay==0.2.5 - pip install PyVirtualDisplay
- pip install pytest-xvfb - pip install pytest-xvfb
- pip install pytest-cov - pip install pytest-cov
- pip install pytest-qt - pip install pytest-qt
@@ -0,0 +1,31 @@
<?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="svg7731">
<metadata
id="metadata7737">
<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="defs7735" />
<path
d="m 20.121169,3.8786433 c -1.170024,-1.1715244 -3.072064,-1.1715244 -4.242088,0 L 12,7.7577241 8.1209195,3.8786433 c -1.1700247,-1.1715244 -3.0720643,-1.1715244 -4.2420887,0 -1.1715244,1.1715244 -1.1715244,3.070564 0,4.2420884 l 3.8775807,3.8790803 -3.8775807,3.879081 c -1.1715244,1.171525 -1.1715244,3.070564 0,4.242089 C 4.463843,20.707494 5.231859,21 5.999875,21 6.767891,21 7.5359065,20.707494 8.1209195,20.120982 L 12,16.241901 15.879081,20.120982 C 16.464093,20.707494 17.232109,21 18.000125,21 c 0.768015,0 1.536031,-0.292506 2.121044,-0.879018 1.171525,-1.171525 1.171525,-3.070564 0,-4.242089 l -3.87758,-3.879081 3.87758,-3.8790803 c 1.171525,-1.1715244 1.171525,-3.070564 0,-4.2420884 z"
id="path7729"
style="stroke-width:1.50003123;fill:#aeaeae;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,31 @@
<?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"
id="svg7731"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<metadata
id="metadata7737">
<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="defs7735" />
<path
style="stroke-width:1.50003123;fill:#000000;fill-opacity:0.78039217"
id="path7729"
d="m 20.121169,3.8786433 c -1.170024,-1.1715244 -3.072064,-1.1715244 -4.242088,0 L 12,7.7577241 8.1209195,3.8786433 c -1.1700247,-1.1715244 -3.0720643,-1.1715244 -4.2420887,0 -1.1715244,1.1715244 -1.1715244,3.070564 0,4.2420884 l 3.8775807,3.8790803 -3.8775807,3.879081 c -1.1715244,1.171525 -1.1715244,3.070564 0,4.242089 C 4.463843,20.707494 5.231859,21 5.999875,21 6.767891,21 7.5359065,20.707494 8.1209195,20.120982 L 12,16.241901 15.879081,20.120982 C 16.464093,20.707494 17.232109,21 18.000125,21 c 0.768015,0 1.536031,-0.292506 2.121044,-0.879018 1.171525,-1.171525 1.171525,-3.070564 0,-4.242089 l -3.87758,-3.879081 3.87758,-3.8790803 c 1.171525,-1.1715244 1.171525,-3.070564 0,-4.2420884 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,49 @@
<?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"
viewBox="0 0 6.3499999 6.3500002"
version="1.1"
id="svg8">
<defs
id="defs2" />
<metadata
id="metadata5">
<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>
<g
style="fill:#aeaeae;fill-opacity:1"
id="layer1"
transform="translate(0,-290.64999)">
<g
aria-label="Aa"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:Roboto;letter-spacing:0px;word-spacing:0px;fill:#aeaeae;fill-opacity:1;stroke:none;stroke-width:0.264583"
id="text817">
<g
id="g968"
style="fill:#aeaeae;fill-opacity:1">
<path
id="path961"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93889px;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#aeaeae;fill-opacity:1;stroke-width:0.264583"
d="M 0.35983601,295.49539 H 1.0364638 l 0.2815167,-0.85936 H 2.631725 l 0.2815166,0.85936 H 3.6195028 L 2.4292305,292.0678 H 1.6143138 Z m 1.14582229,-1.42734 0.4741333,-1.45203 0.4691945,1.45203 z" />
<path
id="path963"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93889px;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#aeaeae;fill-opacity:1;stroke-width:0.264583"
d="m 4.701114,295.54478 c 0.3062111,0 0.5679722,-0.10372 0.7507111,-0.29139 l 0.0889,0.242 H 5.990164 v -1.44215 c 0,-0.65194 -0.3704167,-1.00754 -1.0766778,-1.00754 -0.3457222,0 -0.7112,0.084 -0.9927167,0.23213 l 0.1481667,0.43956 c 0.2469444,-0.0988 0.48895,-0.14816 0.7112,-0.14816 0.3852334,0 0.5926667,0.14816 0.5926667,0.43462 v 0.0642 c -1.0421056,0.0148 -1.5014223,0.25189 -1.5014223,0.76059 0,0.43462 0.3259667,0.71614 0.8297334,0.71614 z m -0.1975556,-0.8001 c 0,-0.21731 0.2518834,-0.31609 0.8692445,-0.32597 v 0.45438 c -0.1185333,0.1136 -0.2815167,0.1778 -0.4642556,0.1778 -0.2420055,0 -0.4049889,-0.12347 -0.4049889,-0.30621 z" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

+48
View File
@@ -0,0 +1,48 @@
<?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"
id="svg8"
version="1.1"
viewBox="0 0 6.3499999 6.3500002"
height="24"
width="24">
<defs
id="defs2" />
<metadata
id="metadata5">
<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>
<g
transform="translate(0,-290.64999)"
id="layer1">
<g
id="text817"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444494px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:Roboto;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458335"
aria-label="Aa">
<g
style="fill:#000000;fill-opacity:0.78039217"
id="g968">
<path
d="M 0.35983601,295.49539 H 1.0364638 l 0.2815167,-0.85936 H 2.631725 l 0.2815166,0.85936 H 3.6195028 L 2.4292305,292.0678 H 1.6143138 Z m 1.14582229,-1.42734 0.4741333,-1.45203 0.4691945,1.45203 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93888903px;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458335;fill:#000000;fill-opacity:0.78039217"
id="path961" />
<path
d="m 4.701114,295.54478 c 0.3062111,0 0.5679722,-0.10372 0.7507111,-0.29139 l 0.0889,0.242 H 5.990164 v -1.44215 c 0,-0.65194 -0.3704167,-1.00754 -1.0766778,-1.00754 -0.3457222,0 -0.7112,0.084 -0.9927167,0.23213 l 0.1481667,0.43956 c 0.2469444,-0.0988 0.48895,-0.14816 0.7112,-0.14816 0.3852334,0 0.5926667,0.14816 0.5926667,0.43462 v 0.0642 c -1.0421056,0.0148 -1.5014223,0.25189 -1.5014223,0.76059 0,0.43462 0.3259667,0.71614 0.8297334,0.71614 z m -0.1975556,-0.8001 c 0,-0.21731 0.2518834,-0.31609 0.8692445,-0.32597 v 0.45438 c -0.1185333,0.1136 -0.2815167,0.1778 -0.4642556,0.1778 -0.2420055,0 -0.4049889,-0.12347 -0.4049889,-0.30621 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93888903px;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458335;fill:#000000;fill-opacity:0.78039217"
id="path963" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1,31 @@
<?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="svg2150">
<metadata
id="metadata2156">
<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="defs2154" />
<path
d="m 17,6.4445834 h -2.317778 l 1.436667,-1.4366667 c 0.434444,-0.4344445 0.434444,-1.1366667 0,-1.5711111 -0.434445,-0.4344445 -1.136667,-0.4344445 -1.571111,0 l -4.118889,4.1188889 4.118889,4.1188895 c 0.216666,0.216666 0.501111,0.325555 0.785555,0.325555 0.284445,0 0.568889,-0.108889 0.785556,-0.325555 0.434444,-0.434445 0.434444,-1.136667 0,-1.571112 L 14.682222,8.6668056 H 17 c 1.532222,0 2.777778,1.4955554 2.777778,3.3333334 0,1.837778 -1.495556,3.333333 -3.333334,3.333333 -0.614444,0 -1.111111,0.497778 -1.111111,1.111112 0,0.613333 0.496667,1.111111 1.111111,1.111111 C 19.507778,17.555695 22,15.063472 22,12.000139 22,8.9368056 19.756667,6.4445834 17,6.4445834 Z M 7.8811111,12.325695 c -0.4344444,0.434444 -0.4344444,1.136666 0,1.571111 L 9.317778,15.333472 H 7 c -1.5322222,0 -2.7777778,-1.495555 -2.7777778,-3.333333 0,-1.837778 1.4955556,-3.3333334 3.3333334,-3.3333334 0.6144444,0 1.1111111,-0.4977778 1.1111111,-1.1111111 C 8.6666667,6.9423611 8.17,6.4445834 7.5555556,6.4445834 4.4922222,6.4445834 2,8.9368056 2,12.000139 c 0,3.063333 2.2433333,5.555556 5,5.555556 h 2.317778 l -1.4366669,1.436666 c -0.4344444,0.434445 -0.4344444,1.136667 0,1.571111 0.2166667,0.216667 0.5011111,0.325556 0.7855556,0.325556 0.2844444,0 0.5688893,-0.108889 0.7855553,-0.325556 l 4.118889,-4.118888 -4.118889,-4.118889 c -0.434444,-0.434445 -1.1366664,-0.434445 -1.5711109,0 z"
id="path2148"
style="stroke-width:1.11111116;fill:#aeaeae;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+31
View File
@@ -0,0 +1,31 @@
<?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"
id="svg2150"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<metadata
id="metadata2156">
<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="defs2154" />
<path
style="stroke-width:1.11111116;fill:#000000;fill-opacity:0.78039217"
id="path2148"
d="m 17,6.4445834 h -2.317778 l 1.436667,-1.4366667 c 0.434444,-0.4344445 0.434444,-1.1366667 0,-1.5711111 -0.434445,-0.4344445 -1.136667,-0.4344445 -1.571111,0 l -4.118889,4.1188889 4.118889,4.1188895 c 0.216666,0.216666 0.501111,0.325555 0.785555,0.325555 0.284445,0 0.568889,-0.108889 0.785556,-0.325555 0.434444,-0.434445 0.434444,-1.136667 0,-1.571112 L 14.682222,8.6668056 H 17 c 1.532222,0 2.777778,1.4955554 2.777778,3.3333334 0,1.837778 -1.495556,3.333333 -3.333334,3.333333 -0.614444,0 -1.111111,0.497778 -1.111111,1.111112 0,0.613333 0.496667,1.111111 1.111111,1.111111 C 19.507778,17.555695 22,15.063472 22,12.000139 22,8.9368056 19.756667,6.4445834 17,6.4445834 Z M 7.8811111,12.325695 c -0.4344444,0.434444 -0.4344444,1.136666 0,1.571111 L 9.317778,15.333472 H 7 c -1.5322222,0 -2.7777778,-1.495555 -2.7777778,-3.333333 0,-1.837778 1.4955556,-3.3333334 3.3333334,-3.3333334 0.6144444,0 1.1111111,-0.4977778 1.1111111,-1.1111111 C 8.6666667,6.9423611 8.17,6.4445834 7.5555556,6.4445834 4.4922222,6.4445834 2,8.9368056 2,12.000139 c 0,3.063333 2.2433333,5.555556 5,5.555556 h 2.317778 l -1.4366669,1.436666 c -0.4344444,0.434445 -0.4344444,1.136667 0,1.571111 0.2166667,0.216667 0.5011111,0.325556 0.7855556,0.325556 0.2844444,0 0.5688893,-0.108889 0.7855553,-0.325556 l 4.118889,-4.118888 -4.118889,-4.118889 c -0.434444,-0.434445 -1.1366664,-0.434445 -1.5711109,0 z" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,40 @@
<?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"
id="svg8"
version="1.1"
viewBox="0 0 6.3499999 6.3500002"
height="24"
width="24">
<g
style="font-size:5.64444px;line-height:1.25;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans, Normal';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
id="text1549"
aria-label="AB">
<path
id="path1551"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93889px;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.251875;fill:#aeaeae;fill-opacity:1"
d="M 0.52916667,4.8827979 H 1.1423606 L 1.397485,4.023431 H 2.588066 L 2.8431905,4.8827979 H 3.4832396 L 2.4045554,1.4552083 H 1.6660372 Z M 1.5675681,3.4554587 1.9972514,2.003425 2.4224589,3.4554587 Z" />
<path
id="path1553"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93889px;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.25188;fill:#aeaeae;fill-opacity:1"
d="m 3.4395834,4.8827979 h 1.1592928 c 0.7519737,0 1.2219572,-0.4099279 1.2219572,-1.0618613 0,-0.4296835 -0.2058975,-0.7408335 -0.5639802,-0.8840614 0.1969454,-0.1432278 0.3177984,-0.3951112 0.3177984,-0.6667501 0,-0.5284612 -0.389415,-0.8149168 -1.1055804,-0.8149168 H 3.4395834 Z M 4.0125157,1.9688528 h 0.4117952 c 0.3625587,0 0.5639802,0.1679223 0.5639802,0.4642557 0,0.1778 -0.076092,0.3160889 -0.2506578,0.4247445 H 4.0125157 Z m 0,2.4003005 V 3.3566809 h 0.6714051 c 0.3357025,0 0.5416002,0.2173112 0.5416002,0.5482168 0,0.3160889 -0.1745654,0.4642556 -0.5505523,0.4642556 z" />
</g>
<defs
id="defs2" />
<metadata
id="metadata5">
<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>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,40 @@
<?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"
id="svg8"
version="1.1"
viewBox="0 0 6.3499999 6.3500002"
height="24"
width="24">
<g
style="font-size:5.64444px;line-height:1.25;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans, Normal';letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
id="text1549"
aria-label="AB">
<path
id="path1551"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93889px;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.251875;fill:#000000;fill-opacity:0.78039217"
d="M 0.52916667,4.8827979 H 1.1423606 L 1.397485,4.023431 H 2.588066 L 2.8431905,4.8827979 H 3.4832396 L 2.4045554,1.4552083 H 1.6660372 Z M 1.5675681,3.4554587 1.9972514,2.003425 2.4224589,3.4554587 Z" />
<path
id="path1553"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93889px;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.25188;fill:#000000;fill-opacity:0.78039217"
d="m 3.4395834,4.8827979 h 1.1592928 c 0.7519737,0 1.2219572,-0.4099279 1.2219572,-1.0618613 0,-0.4296835 -0.2058975,-0.7408335 -0.5639802,-0.8840614 0.1969454,-0.1432278 0.3177984,-0.3951112 0.3177984,-0.6667501 0,-0.5284612 -0.389415,-0.8149168 -1.1055804,-0.8149168 H 3.4395834 Z M 4.0125157,1.9688528 h 0.4117952 c 0.3625587,0 0.5639802,0.1679223 0.5639802,0.4642557 0,0.1778 -0.076092,0.3160889 -0.2506578,0.4247445 H 4.0125157 Z m 0,2.4003005 V 3.3566809 h 0.6714051 c 0.3357025,0 0.5416002,0.2173112 0.5416002,0.5482168 0,0.3160889 -0.1745654,0.4642556 -0.5505523,0.4642556 z" />
</g>
<defs
id="defs2" />
<metadata
id="metadata5">
<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>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@@ -0,0 +1,31 @@
<?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"
id="svg8301"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<metadata
id="metadata8307">
<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="defs8305" />
<path
style="stroke-width:1.18357;fill:#aeaeae;fill-opacity:1"
id="path8299"
d="m 19.591431,11.061428 c -0.92437,-0.92437 -2.422771,-0.92437 -3.347141,0 l -1.877146,1.877144 V 4.367144 C 14.367144,3.059297 13.306664,2 12,2 10.692153,2 9.6328557,3.059297 9.6328557,4.367144 v 8.571428 l -1.877145,-1.877144 c -0.9243698,-0.92437 -2.4227719,-0.92437 -3.3471417,0 -0.9243697,0.924369 -0.9243697,2.422771 0,3.347141 L 12,22 19.591431,14.408569 c 0.92437,-0.92437 0.92437,-2.421588 0,-3.347141 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,31 @@
<?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"
id="svg8301"
viewBox="0 0 24 24"
height="24"
width="24"
version="1.2">
<metadata
id="metadata8307">
<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="defs8305" />
<path
style="stroke-width:1.18357;fill:#000000;fill-opacity:0.77999997"
id="path8299"
d="m 19.591431,11.061428 c -0.92437,-0.92437 -2.422771,-0.92437 -3.347141,0 l -1.877146,1.877144 V 4.367144 C 14.367144,3.059297 13.306664,2 12,2 10.692153,2 9.6328557,3.059297 9.6328557,4.367144 v 8.571428 l -1.877145,-1.877144 c -0.9243698,-0.92437 -2.4227719,-0.92437 -3.3471417,0 -0.9243697,0.924369 -0.9243697,2.422771 0,3.347141 L 12,22 19.591431,14.408569 c 0.92437,-0.92437 0.92437,-2.421588 0,-3.347141 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,49 @@
<?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"
viewBox="0 0 6.3499999 6.3500002"
version="1.1"
id="svg8">
<defs
id="defs2" />
<metadata
id="metadata5">
<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>
<g
style="fill:#aeaeae;fill-opacity:1"
id="layer1"
transform="translate(0,-290.64999)">
<g
aria-label=".*"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:Roboto;letter-spacing:0px;word-spacing:0px;fill:#aeaeae;fill-opacity:1;stroke:none;stroke-width:0.264583"
id="text817">
<g
id="g1606"
style="fill:#aeaeae;fill-opacity:1">
<path
id="path1599"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93889px;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#aeaeae;fill-opacity:1;stroke-width:0.264583"
d="m 1.9550981,295.54478 c 0.2518833,0 0.4198055,-0.18274 0.4198055,-0.4198 0,-0.25189 -0.1679222,-0.43463 -0.4198055,-0.43463 -0.2568223,0 -0.4247445,0.18274 -0.4247445,0.43463 0,0.23706 0.1679222,0.4198 0.4247445,0.4198 z" />
<path
id="path1601"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93889px;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#aeaeae;fill-opacity:1;stroke-width:0.264583"
d="m 3.5108408,294.12732 h 0.5482167 l -0.1234722,-0.77541 0.6124222,0.49883 0.2568223,-0.43956 -0.7655278,-0.32103 0.7803444,-0.29139 -0.2518833,-0.43956 -0.6371167,0.48401 0.1284111,-0.8001 H 3.5108408 l 0.1382889,0.79516 -0.6223,-0.50377 -0.2518833,0.43956 0.7704667,0.31609 -0.7754056,0.30621 0.2469444,0.4445 0.6321778,-0.50376 z" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

+48
View File
@@ -0,0 +1,48 @@
<?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"
id="svg8"
version="1.1"
viewBox="0 0 6.3499999 6.3500002"
height="24"
width="24">
<defs
id="defs2" />
<metadata
id="metadata5">
<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>
<g
transform="translate(0,-290.64999)"
id="layer1">
<g
id="text817"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444494px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:Roboto;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458335"
aria-label=".*">
<g
style="fill:#000000;fill-opacity:0.78039217"
id="g1606">
<path
d="m 1.9550981,295.54478 c 0.2518833,0 0.4198055,-0.18274 0.4198055,-0.4198 0,-0.25189 -0.1679222,-0.43463 -0.4198055,-0.43463 -0.2568223,0 -0.4247445,0.18274 -0.4247445,0.43463 0,0.23706 0.1679222,0.4198 0.4247445,0.4198 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93888903px;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458335;fill:#000000;fill-opacity:0.78039217"
id="path1599" />
<path
d="m 3.5108408,294.12732 h 0.5482167 l -0.1234722,-0.77541 0.6124222,0.49883 0.2568223,-0.43956 -0.7655278,-0.32103 0.7803444,-0.29139 -0.2518833,-0.43956 -0.6371167,0.48401 0.1284111,-0.8001 H 3.5108408 l 0.1382889,0.79516 -0.6223,-0.50377 -0.2518833,0.43956 0.7704667,0.31609 -0.7754056,0.30621 0.2469444,0.4445 0.6321778,-0.50376 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93888903px;font-family:Cantarell;-inkscape-font-specification:'Cantarell, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458335;fill:#000000;fill-opacity:0.78039217"
id="path1601" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -0,0 +1,45 @@
<?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"
id="svg8"
version="1.1"
viewBox="0 0 6.3499999 6.3500002"
height="24"
width="24">
<defs
id="defs2" />
<metadata
id="metadata5">
<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>
<g
transform="translate(0,-290.64999)"
id="layer1">
<g
id="text817"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:Roboto;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
aria-label=".*">
<g
style="fill:#000000;fill-opacity:0.780392"
id="g1606">
<path
id="rect1629"
transform="matrix(0.26458334,0,0,0.26458334,0,290.64999)"
d="m 2,11.5 v 4 1 2 h 20 v -3 -4 h -3 v 4 H 5 v -4 z"
style="fill:#aeaeae;fill-opacity:1;stroke:none;stroke-width:1.76363;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+45
View File
@@ -0,0 +1,45 @@
<?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"
id="svg8"
version="1.1"
viewBox="0 0 6.3499999 6.3500002"
height="24"
width="24">
<defs
id="defs2" />
<metadata
id="metadata5">
<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>
<g
transform="translate(0,-290.64999)"
id="layer1">
<g
id="text817"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:Roboto;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
aria-label=".*">
<g
style="fill:#000000;fill-opacity:0.780392"
id="g1606">
<path
id="rect1629"
transform="matrix(0.26458334,0,0,0.26458334,0,290.64999)"
d="m 2,11.5 v 4 1 2 h 20 v -3 -4 h -3 v 4 H 5 v -4 z"
style="fill:#000000;fill-opacity:0.780392;stroke:none;stroke-width:1.76363;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+21
View File
@@ -172,3 +172,24 @@ def splitVersionNumber(vString):
vInt = vMajor*10000 + vMinor*100 + vPatch vInt = vMajor*10000 + vMinor*100 + vPatch
return [vMajor, vMinor, vPatch, vInt] return [vMajor, vMinor, vPatch, vInt]
def transferCase(theSource, theTarget):
"""Transfers the case of the source word to the target word. This
will consider all upper or lower, and first char capitalisation.
"""
theResult = theTarget
if not isinstance(theSource, str) or not isinstance(theTarget, str):
return theResult
if len(theTarget) < 1 or len(theSource) < 1:
return theResult
if theSource[0] == theSource[0].upper():
theResult = theTarget[0].upper() + theTarget[1:]
if theSource == theSource.upper():
theResult = theTarget.upper()
elif theSource == theSource.lower():
theResult = theTarget.lower()
return theResult
+34 -3
View File
@@ -133,6 +133,13 @@ class Config:
self.spellTool = None self.spellTool = None
self.spellLanguage = None self.spellLanguage = None
self.searchCase = False
self.searchWord = False
self.searchRegEx = False
self.searchLoop = False
self.searchNextFile = False
self.searchMatchCap = False
## Backup ## Backup
self.backupPath = "" self.backupPath = ""
self.backupOnClose = False self.backupOnClose = False
@@ -482,6 +489,24 @@ class Config:
self.viewSynopsis = self._parseLine( self.viewSynopsis = self._parseLine(
cnfParse, cnfSec, "viewsynopsis", self.CNF_BOOL, self.viewSynopsis cnfParse, cnfSec, "viewsynopsis", self.CNF_BOOL, self.viewSynopsis
) )
self.searchCase = self._parseLine(
cnfParse, cnfSec, "searchcase", self.CNF_BOOL, self.searchCase
)
self.searchWord = self._parseLine(
cnfParse, cnfSec, "searchword", self.CNF_BOOL, self.searchWord
)
self.searchRegEx = self._parseLine(
cnfParse, cnfSec, "searchregex", self.CNF_BOOL, self.searchRegEx
)
self.searchLoop = self._parseLine(
cnfParse, cnfSec, "searchloop", self.CNF_BOOL, self.searchLoop
)
self.searchNextFile = self._parseLine(
cnfParse, cnfSec, "searchnextfile", self.CNF_BOOL, self.searchNextFile
)
self.searchMatchCap = self._parseLine(
cnfParse, cnfSec, "searchmatchcap", self.CNF_BOOL, self.searchMatchCap
)
## Path ## Path
cnfSec = "Path" cnfSec = "Path"
@@ -571,9 +596,15 @@ class Config:
## State ## State
cnfSec = "State" cnfSec = "State"
cnfParse.add_section(cnfSec) cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec,"showrefpanel",str(self.showRefPanel)) cnfParse.set(cnfSec,"showrefpanel", str(self.showRefPanel))
cnfParse.set(cnfSec,"viewcomments",str(self.viewComments)) cnfParse.set(cnfSec,"viewcomments", str(self.viewComments))
cnfParse.set(cnfSec,"viewsynopsis",str(self.viewSynopsis)) cnfParse.set(cnfSec,"viewsynopsis", str(self.viewSynopsis))
cnfParse.set(cnfSec,"searchcase", str(self.searchCase))
cnfParse.set(cnfSec,"searchword", str(self.searchWord))
cnfParse.set(cnfSec,"searchregex", str(self.searchRegEx))
cnfParse.set(cnfSec,"searchloop", str(self.searchLoop))
cnfParse.set(cnfSec,"searchnextfile", str(self.searchNextFile))
cnfParse.set(cnfSec,"searchmatchcap", str(self.searchMatchCap))
## Path ## Path
cnfSec = "Path" cnfSec = "Path"
-2
View File
@@ -2,7 +2,6 @@
from nw.gui.about import GuiAbout from nw.gui.about import GuiAbout
from nw.gui.build import GuiBuildNovel from nw.gui.build import GuiBuildNovel
from nw.gui.docbars import GuiSearchBar
from nw.gui.doceditor import GuiDocEditor from nw.gui.doceditor import GuiDocEditor
from nw.gui.docmerge import GuiDocMerge from nw.gui.docmerge import GuiDocMerge
from nw.gui.docsplit import GuiDocSplit from nw.gui.docsplit import GuiDocSplit
@@ -23,7 +22,6 @@ from nw.gui.theme import GuiIcons, GuiTheme
__all__ = [ __all__ = [
"GuiAbout", "GuiAbout",
"GuiBuildNovel", "GuiBuildNovel",
"GuiSearchBar",
"GuiDocEditor", "GuiDocEditor",
"GuiDocMerge", "GuiDocMerge",
"GuiDocSplit", "GuiDocSplit",
+1 -1
View File
@@ -459,7 +459,7 @@ class GuiBuildNovel(QDialog):
makeHtml.setStyles(not noStyling) makeHtml.setStyles(not noStyling)
# Make sure the tree order is correct # Make sure the tree order is correct
self.theParent.treeView.saveTreeOrder() self.theParent.treeView.flushTreeOrder()
self.buildProgress.setMaximum(len(self.theProject.projTree)) self.buildProgress.setMaximum(len(self.theProject.projTree))
self.buildProgress.setValue(0) self.buildProgress.setValue(0)
-168
View File
@@ -1,168 +0,0 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Main Window SearchBar
novelWriter GUI Main Window SearchBar
=========================================
Class holding the main window search bar
File History:
Created: 2019-09-29 [0.2.1] GuiSearchBar
This file is a part of novelWriter
Copyright 2020, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
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 logging
import nw
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QPalette, QColor, QIcon
from PyQt5.QtWidgets import (
qApp, QWidget, QFrame, QGridLayout, QLabel, QLineEdit, QPushButton,
QHBoxLayout, QToolButton, QScrollArea
)
from nw.constants import nwDocAction, nwUnicode
logger = logging.getLogger(__name__)
class GuiSearchBar(QWidget):
def __init__(self, theParent):
QWidget.__init__(self, theParent)
logger.debug("Initialising GuiSearchBar ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.repVisible = False
self.setContentsMargins(0, 0, 0, 0)
self.mainBox = QGridLayout(self)
self.setLayout(self.mainBox)
self.searchBox = QLineEdit()
self.replaceBox = QLineEdit()
self.searchLabel = QLabel("Search")
self.replaceLabel = QLabel("Replace")
self.closeButton = QPushButton(self.theTheme.getIcon("close"),"")
self.searchButton = QPushButton(self.theTheme.getIcon("search"),"")
self.replaceButton = QPushButton(self.theTheme.getIcon("search-replace"),"")
self.closeButton.clicked.connect(self._doClose)
self.searchButton.clicked.connect(self._doSearch)
self.replaceButton.clicked.connect(self._doReplace)
self.searchBox.returnPressed.connect(self._doSearch)
self.replaceBox.returnPressed.connect(self._doSearch)
self.mainBox.addWidget(QLabel(""), 0, 0)
self.mainBox.addWidget(self.searchLabel, 0, 1)
self.mainBox.addWidget(self.searchBox, 0, 2)
self.mainBox.addWidget(self.searchButton, 0, 3)
self.mainBox.addWidget(self.closeButton, 0, 4)
self.mainBox.addWidget(self.replaceLabel, 1, 1)
self.mainBox.addWidget(self.replaceBox, 1, 2)
self.mainBox.addWidget(self.replaceButton, 1, 3)
self.mainBox.setColumnStretch(0, 1)
self.mainBox.setColumnStretch(1, 0)
self.mainBox.setColumnStretch(2, 0)
self.mainBox.setColumnStretch(3, 0)
self.mainBox.setColumnStretch(4, 0)
self.mainBox.setContentsMargins(0, 0, 0, 0)
boxWidth = 16*self.theTheme.textNWidth
self.searchBox.setMinimumWidth(boxWidth)
self.replaceBox.setMinimumWidth(boxWidth)
self._replaceVisible(False)
logger.debug("GuiSearchBar initialisation complete")
return
##
# Get and Set Functions
##
def setSearchText(self, theText):
"""Open the search bar and set the search text to the text
provided, if any.
"""
if not self.isVisible():
self.setVisible(True)
self.searchBox.setText(theText)
self.searchBox.setFocus()
logger.verbose("Setting search text to '%s'" % theText)
return True
def setReplaceText(self, theText):
"""Set the replace text.
"""
self._replaceVisible(True)
self.replaceBox.setFocus()
self.replaceBox.setText(theText)
return True
def getSearchText(self):
"""Return the current search text.
"""
return self.searchBox.text()
def getReplaceText(self):
"""Return the current replace text.
"""
return self.replaceBox.text()
##
# Internal Functions
##
def _doClose(self):
"""Hide the search/replace bar.
"""
self._replaceVisible(False)
self.setVisible(False)
return
def _doSearch(self):
"""Call the search action function for the document editor.
"""
modKey = qApp.keyboardModifiers()
if modKey == Qt.ShiftModifier:
self.theParent.docEditor.docAction(nwDocAction.GO_PREV)
else:
self.theParent.docEditor.docAction(nwDocAction.GO_NEXT)
return
def _doReplace(self):
"""Call the replace action function for the document editor.
"""
self.theParent.docEditor.docAction(nwDocAction.REPL_NEXT)
return
def _replaceVisible(self, isVisible):
"""Set the visibility of all the replace widgets.
"""
self.replaceLabel.setVisible(isVisible)
self.replaceBox.setVisible(isVisible)
self.replaceButton.setVisible(isVisible)
self.repVisible = isVisible
return True
# END Class GuiSearchBar
+448 -67
View File
@@ -6,9 +6,11 @@
Class holding the document editor Class holding the document editor
File History: File History:
Created: 2018-09-29 [0.0.1] GuiDocEditor Created: 2018-09-29 [0.0.1] GuiDocEditor
Created: 2019-04-22 [0.0.1] WordCounter Created: 2019-04-22 [0.0.1] BackgroundWordCounter
Created: 2020-04-25 [0.4.5] GuiDocEditHeader Created: 2019-09-29 [0.2.1] GuiDocEditSearch
Created: 2020-04-25 [0.4.5] GuiDocEditHeader
Rewritten: 2020-06-15 [0.9.0] GuiDocEditSearch
This file is a part of novelWriter This file is a part of novelWriter
Copyright 2020, Veronica Berglyd Olsen Copyright 2020, Veronica Berglyd Olsen
@@ -32,20 +34,22 @@ import nw
from time import time from time import time
from PyQt5.QtCore import Qt, QSize, QThread, QTimer, pyqtSlot from PyQt5.QtCore import Qt, QSize, QThread, QTimer, pyqtSlot, QRegExp
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette, QIcon, QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette, QIcon,
QTextDocument, QCursor QTextDocument, QCursor
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel, qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel,
QToolButton, QHBoxLayout QToolBar, QToolButton, QHBoxLayout, QGridLayout, QLineEdit, QPushButton,
QFrame
) )
from nw.core import NWDoc from nw.core import NWDoc
from nw.gui.dochighlight import GuiDocHighlighter from nw.gui.dochighlight import GuiDocHighlighter
from nw.core import NWSpellSimple, countWords from nw.core import NWSpellSimple, countWords
from nw.constants import nwUnicode, nwDocAction, nwItemClass from nw.constants import nwUnicode, nwDocAction, nwItemClass
from nw.common import transferCase
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -88,6 +92,7 @@ class GuiDocEditor(QTextEdit):
# Document Title # Document Title
self.docHeader = GuiDocEditHeader(self) self.docHeader = GuiDocEditHeader(self)
self.docFooter = GuiDocEditFooter(self) self.docFooter = GuiDocEditFooter(self)
self.docSearch = GuiDocEditSearch(self)
# Syntax # Syntax
self.hLight = GuiDocHighlighter(self.qDocument, self.theParent) self.hLight = GuiDocHighlighter(self.qDocument, self.theParent)
@@ -99,6 +104,7 @@ class GuiDocEditor(QTextEdit):
# Editor State # Editor State
self.hasSelection = False self.hasSelection = False
self.setMinimumWidth(self.mainConf.pxInt(300)) self.setMinimumWidth(self.mainConf.pxInt(300))
self.setAutoFillBackground(True)
self.setAcceptRichText(False) self.setAcceptRichText(False)
# Custom Shortcuts # Custom Shortcuts
@@ -127,7 +133,7 @@ class GuiDocEditor(QTextEdit):
self.wcTimer.setInterval(int(self.wcInterval*1000)) self.wcTimer.setInterval(int(self.wcInterval*1000))
self.wcTimer.timeout.connect(self._runCounter) self.wcTimer.timeout.connect(self._runCounter)
self.wCounter = WordCounter(self) self.wCounter = BackgroundWordCounter(self)
self.wCounter.finished.connect(self._updateCounts) self.wCounter.finished.connect(self._updateCounts)
self.initEditor() self.initEditor()
@@ -183,12 +189,15 @@ class GuiDocEditor(QTextEdit):
self.setFont(theFont) self.setFont(theFont)
docPalette = self.palette() docPalette = self.palette()
docPalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack))
docPalette.setColor(QPalette.Base, QColor(*self.theTheme.colBack)) docPalette.setColor(QPalette.Base, QColor(*self.theTheme.colBack))
docPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText)) docPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
self.setPalette(docPalette) self.setPalette(docPalette)
# Set default text margins # Set default text margins
self.qDocument.setDocumentMargin(self.mainConf.getTextMargin()) cM = self.mainConf.getTextMargin()
self.qDocument.setDocumentMargin(0)
self.setViewportMargins(cM, cM, cM, cM)
# Also set the document text options for the document text flow # Also set the document text options for the document text flow
theOpt = QTextOption() theOpt = QTextOption()
@@ -311,6 +320,7 @@ class GuiDocEditor(QTextEdit):
Config.textFixedW is enabled or we're in Zen mode. Otherwise, Config.textFixedW is enabled or we're in Zen mode. Otherwise,
just ensure the margins are set correctly. just ensure the margins are set correctly.
""" """
wW = self.width()
cM = self.mainConf.getTextMargin() cM = self.mainConf.getTextMargin()
vBar = self.verticalScrollBar() vBar = self.verticalScrollBar()
@@ -324,42 +334,29 @@ class GuiDocEditor(QTextEdit):
tW = self.mainConf.getZenWidth() tW = self.mainConf.getZenWidth()
else: else:
tW = self.mainConf.getTextWidth() tW = self.mainConf.getTextWidth()
wW = self.width() tM = (wW - sW - tW)//2
tM = int((wW - sW - tW)/2)
if tM < cM: if tM < cM:
tM = cM tM = cM
else: else:
tM = cM tM = cM
tB = self.frameWidth() tB = self.frameWidth()
tW = self.width() - 2*tB - sW tW = wW - 2*tB - sW
tH = self.docHeader.height() tH = self.docHeader.height()
fH = self.docFooter.height() fH = self.docFooter.height()
fY = self.height() - fH - tB fY = self.height() - fH - tB
tT = cM - tH
bT = cM - fH
self.docHeader.setGeometry(tB, tB, tW, tH) self.docHeader.setGeometry(tB, tB, tW, tH)
self.docFooter.setGeometry(tB, fY, tW, fH) self.docFooter.setGeometry(tB, fY, tW, fH)
self.setViewportMargins(0, tH, 0, fH)
docFormat = self.qDocument.rootFrame().frameFormat() if self.docSearch.isVisible():
docFormat.setLeftMargin(tM) rH = self.docSearch.height()
docFormat.setRightMargin(tM) rW = self.docSearch.width()
docFormat.setTopMargin(max(0, tT)) rL = wW - sW - rW - 2*tB
docFormat.setBottomMargin(max(0, bT)) self.docSearch.move(rL, 2*tB)
else:
rH = 0
# Updating root frame triggers a QTextDocument->contentsChange self.setViewportMargins(tM, max(cM, tH, rH), tM, max(cM, fH))
# signal, which we do not want as it re-runs the syntax
# highlighter and spell checker, so we block it briefly.
# We then emit a signal that does not trigger re-highlighting.
self.qDocument.blockSignals(True)
self.qDocument.rootFrame().setFrameFormat(docFormat)
self.qDocument.blockSignals(False)
# The line below causes issues with large documents as it
# triggers an early repaint that seems to only render a part of
# the document. Leaving it here as a warning for now.
# self.qDocument.contentsChange.emit(0, 0, 0)
return return
@@ -536,7 +533,7 @@ class GuiDocEditor(QTextEdit):
elif theAction == nwDocAction.GO_NEXT: elif theAction == nwDocAction.GO_NEXT:
self._findNext() self._findNext()
elif theAction == nwDocAction.GO_PREV: elif theAction == nwDocAction.GO_PREV:
self._findPrev() self._findNext(isBackward=True)
elif theAction == nwDocAction.REPL_NEXT: elif theAction == nwDocAction.REPL_NEXT:
self._replaceNext() self._replaceNext()
elif theAction == nwDocAction.BLOCK_H1: elif theAction == nwDocAction.BLOCK_H1:
@@ -577,6 +574,12 @@ class GuiDocEditor(QTextEdit):
)) ))
return return
def closeSearch(self):
"""Close the search box.
"""
self.docSearch.closeSearch()
return self.docSearch.isVisible()
## ##
# Document Events and Maintenance # Document Events and Maintenance
## ##
@@ -594,6 +597,9 @@ class GuiDocEditor(QTextEdit):
as it is triggered on every keypress when typing. as it is triggered on every keypress when typing.
""" """
self.hasSelection = self.textCursor().hasSelection() self.hasSelection = self.textCursor().hasSelection()
if self.docSearch.searchBox.hasFocus():
# Block the event when the focus is on the search bar.
return
if keyEvent.modifiers() == Qt.ShiftModifier: if keyEvent.modifiers() == Qt.ShiftModifier:
theKey = keyEvent.key() theKey = keyEvent.key()
@@ -1135,7 +1141,8 @@ class GuiDocEditor(QTextEdit):
selText = theCursor.selectedText() selText = theCursor.selectedText()
else: else:
selText = "" selText = ""
self.theParent.searchBar.setSearchText(selText) self.docSearch.setSearchText(selText)
self.updateDocMargins()
return return
def _beginReplace(self): def _beginReplace(self):
@@ -1143,54 +1150,82 @@ class GuiDocEditor(QTextEdit):
text. text.
""" """
self._beginSearch() self._beginSearch()
self.theParent.searchBar.setReplaceText("") self.docSearch.setReplaceText("")
return return
def _findNext(self): def _findNext(self, isBackward=False):
"""Searches for the next occurrence of the search bar text in """Searches for the next or previous occurrence of the search
the document. Wraps back to the top if not found. bar text in the document. Wraps around if not found and loop is
enabled, or continues to next file if next file is enabled.
""" """
searchFor = self.theParent.searchBar.getSearchText() if not self.docSearch.isVisible():
wasFound = self.find(searchFor) self._beginSearch()
if not wasFound: return
theCursor = self.textCursor()
theCursor.movePosition(QTextCursor.Start)
self.setTextCursor(theCursor)
return
def _findPrev(self): findOpt = QTextDocument.FindFlag(0)
"""Searches for the previous occurrence of the search bar text if isBackward:
in the document. Wraps back to the end if not found. findOpt |= QTextDocument.FindBackward
""" if self.docSearch.isCaseSense:
searchFor = self.theParent.searchBar.getSearchText() findOpt |= QTextDocument.FindCaseSensitively
wasFound = self.find(searchFor, QTextDocument.FindBackward) if self.docSearch.isWholeWord:
findOpt |= QTextDocument.FindWholeWords
searchFor = self.docSearch.getSearchText()
wasFound = self.find(searchFor, findOpt)
if not wasFound: if not wasFound:
theCursor = self.textCursor() if self.docSearch.doNextFile and not isBackward:
theCursor.movePosition(QTextCursor.End) self.theParent.openNextDocument(
self.setTextCursor(theCursor) self.theHandle, wrapAround=self.docSearch.doLoop
)
elif self.docSearch.doLoop:
theCursor = self.textCursor()
theCursor.movePosition(
QTextCursor.End if isBackward else QTextCursor.Start
)
self.setTextCursor(theCursor)
self.find(searchFor, findOpt)
return return
def _replaceNext(self): def _replaceNext(self):
"""Searches for the next occurrence of the search bar text in """Searches for the next occurrence of the search bar text in
the document and replaces it with the replace text. Wraps back the document and replaces it with the replace text. Calls search
to the top if not found. next automatically when done.
""" """
if not self.docSearch.isVisible():
self._beginSearch()
return
if not theCursor.hasSelection():
return
theCursor = self.textCursor() theCursor = self.textCursor()
searchFor = self.theParent.searchBar.getSearchText() searchFor = self.docSearch.getSearchText()
replWith = self.theParent.searchBar.getReplaceText() replWith = self.docSearch.getReplaceText()
if theCursor.hasSelection() and theCursor.selectedText() == searchFor: selText = theCursor.selectedText()
xPos = theCursor.selectionStart()
if self.docSearch.doMatchCap:
replWith = transferCase(selText, replWith)
if not self.docSearch.isCaseSense:
isMatch = searchFor.lower() == selText.lower()
else:
isMatch = searchFor == selText
if isMatch:
theCursor.beginEditBlock() theCursor.beginEditBlock()
theCursor.removeSelectedText() theCursor.removeSelectedText()
theCursor.insertText(replWith) theCursor.insertText(replWith)
theCursor.endEditBlock() theCursor.endEditBlock()
theCursor.setPosition(xPos) theCursor.setPosition(theCursor.selectionEnd())
self.setTextCursor(theCursor) self.setTextCursor(theCursor)
logger.verbose("Replaced occurrence of '%s' with '%s' on line %d" % ( logger.verbose("Replaced occurrence of '%s' with '%s' on line %d" % (
searchFor, replWith, theCursor.blockNumber() searchFor, replWith, theCursor.blockNumber()
)) ))
if searchFor != "":
if searchFor:
self._findNext() self._findNext()
return return
def _setupSpellChecking(self): def _setupSpellChecking(self):
@@ -1209,11 +1244,16 @@ class GuiDocEditor(QTextEdit):
# END Class GuiDocEditor # END Class GuiDocEditor
class WordCounter(QThread): # =============================================================================================== #
# The Off GUI Thread Word Counter
# Runs the word counter in the background for the DocEditor
# =============================================================================================== #
def __init__(self, theParent): class BackgroundWordCounter(QThread):
QThread.__init__(self, theParent)
self.theParent = theParent def __init__(self, docEditor):
QThread.__init__(self, docEditor)
self.docEditor = docEditor
self.charCount = 0 self.charCount = 0
self.wordCount = 0 self.wordCount = 0
self.paraCount = 0 self.paraCount = 0
@@ -1223,16 +1263,357 @@ class WordCounter(QThread):
"""Overloaded run function for the word counter, forwarding the """Overloaded run function for the word counter, forwarding the
call to the function that does the actual counting. call to the function that does the actual counting.
""" """
theText = self.theParent.getText() theText = self.docEditor.getText()
cC, wC, pC = countWords(theText) cC, wC, pC = countWords(theText)
self.charCount = cC self.charCount = cC
self.wordCount = wC self.wordCount = wC
self.paraCount = pC self.paraCount = pC
return
## END Class BackgroundWordCounter
# =============================================================================================== #
# The Embedded Document Search/Replace Feature
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
# =============================================================================================== #
class GuiDocEditSearch(QFrame):
def __init__(self, docEditor):
QFrame.__init__(self, docEditor)
logger.debug("Initialising GuiDocEditSearch ...")
self.mainConf = nw.CONFIG
self.docEditor = docEditor
self.theParent = docEditor.theParent
self.theProject = docEditor.theProject
self.theTheme = docEditor.theTheme
self.repVisible = False
self.isCaseSense = self.mainConf.searchCase
self.isWholeWord = self.mainConf.searchWord
self.isRegEx = self.mainConf.searchRegEx
self.doLoop = self.mainConf.searchLoop
self.doNextFile = self.mainConf.searchNextFile
self.doMatchCap = self.mainConf.searchMatchCap
mPx = self.mainConf.pxInt(6)
fPx = int(0.9*self.theTheme.fontPixelSize)
tPx = int(0.8*self.theTheme.fontPixelSize)
boxFont = self.theTheme.guiFont
boxFont.setPointSizeF(0.9*self.theTheme.fontPointSize)
self.setContentsMargins(0, 0, 0, 0)
self.setAutoFillBackground(True)
self.setFrameStyle(QFrame.StyledPanel | QFrame.Plain)
self.mainBox = QGridLayout(self)
self.setLayout(self.mainBox)
# Text Boxes
# ==========
self.searchBox = QLineEdit()
self.searchBox.setFont(boxFont)
self.searchBox.setPlaceholderText("Search")
self.searchBox.returnPressed.connect(self._doSearch)
self.replaceBox = QLineEdit()
self.replaceBox.setFont(boxFont)
self.replaceBox.setPlaceholderText("Replace")
self.replaceBox.returnPressed.connect(self._doSearch)
self.searchOpt = QToolBar(self)
self.searchOpt.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.searchOpt.setIconSize(QSize(tPx, tPx))
self.searchOpt.setContentsMargins(0, 0, 0, 0)
self.searchOpt.setStyleSheet(r"QToolBar {padding: 0;}")
self.searchLabel = QLabel("Search")
self.searchLabel.setFont(boxFont)
self.searchLabel.setIndent(self.mainConf.pxInt(6))
self.toggleCase = QAction("Case Sensitive", self)
self.toggleCase.setToolTip("Match case")
self.toggleCase.setIcon(self.theTheme.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("Whole Words Only", self)
self.toggleWord.setToolTip("Match whole words")
self.toggleWord.setIcon(self.theTheme.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("RegEx Mode", self)
self.toggleRegEx.setToolTip("Use regular expressions")
self.toggleRegEx.setIcon(self.theTheme.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("Loop Search", self)
self.toggleLoop.setToolTip("Loop the search when reaching the end")
self.toggleLoop.setIcon(self.theTheme.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("Search Next File", self)
self.toggleProject.setToolTip("Continue searching in the next file")
self.toggleProject.setIcon(self.theTheme.getIcon("search_project"))
self.toggleProject.setCheckable(True)
self.toggleProject.setChecked(self.doNextFile)
self.toggleProject.toggled.connect(self._doToggleProject)
self.searchOpt.addAction(self.toggleProject)
self.searchOpt.addSeparator()
self.toggleMatchCap = QAction("Preserve Case", self)
self.toggleMatchCap.setToolTip("Preserve case on replace")
self.toggleMatchCap.setIcon(self.theTheme.getIcon("search_preserve"))
self.toggleMatchCap.setCheckable(True)
self.toggleMatchCap.setChecked(self.doMatchCap)
self.toggleMatchCap.toggled.connect(self._doToggleMatchCap)
self.searchOpt.addAction(self.toggleMatchCap)
self.searchOpt.addSeparator()
self.cancelSearch = QAction("Close Search", self)
self.cancelSearch.setToolTip("Close the search box [Esc]")
self.cancelSearch.setIcon(self.theTheme.getIcon("search_cancel"))
self.cancelSearch.triggered.connect(self._doClose)
self.searchOpt.addAction(self.cancelSearch)
# Buttons
# =======
bPx = self.searchBox.sizeHint().height()
self.showReplace = QToolButton(self)
self.showReplace.setArrowType(Qt.RightArrow)
self.showReplace.setCheckable(True)
self.showReplace.setStyleSheet(r"QToolButton {border: none; background: transparent;}")
self.showReplace.toggled.connect(self._doToggleReplace)
self.searchButton = QPushButton(self.theTheme.getIcon("search"),"")
self.searchButton.setFixedSize(QSize(bPx, bPx))
self.searchButton.setToolTip("Find in current document")
self.searchButton.clicked.connect(self._doSearch)
self.replaceButton = QPushButton(self.theTheme.getIcon("search-replace"),"")
self.replaceButton.setFixedSize(QSize(bPx, bPx))
self.replaceButton.setToolTip("Find and replace in current document")
self.replaceButton.clicked.connect(self._doReplace)
self.mainBox.addWidget(self.searchLabel, 0, 0, 1, 2, Qt.AlignLeft)
self.mainBox.addWidget(self.searchOpt, 0, 2, 1, 2, Qt.AlignRight)
self.mainBox.addWidget(self.showReplace, 1, 0, 1, 1)
self.mainBox.addWidget(self.searchBox, 1, 1, 1, 2)
self.mainBox.addWidget(self.searchButton, 1, 3, 1, 1)
self.mainBox.addWidget(self.replaceBox, 2, 1, 1, 2)
self.mainBox.addWidget(self.replaceButton, 2, 3, 1, 1)
self.mainBox.setColumnStretch(0, 1)
self.mainBox.setColumnStretch(1, 0)
self.mainBox.setColumnStretch(2, 0)
self.mainBox.setColumnStretch(3, 0)
self.mainBox.setColumnStretch(4, 0)
self.mainBox.setSpacing(self.mainConf.pxInt(2))
self.mainBox.setContentsMargins(mPx, mPx, mPx, mPx)
boxWidth = self.mainConf.pxInt(200)
self.searchBox.setFixedWidth(boxWidth)
self.replaceBox.setFixedWidth(boxWidth)
self.replaceBox.setVisible(False)
self.replaceButton.setVisible(False)
self.adjustSize()
# Construct Box Colours
qPalette = self.searchBox.palette()
baseCol = qPalette.base().color()
rCol = baseCol.redF() + 0.1
gCol = baseCol.greenF() - 0.1
bCol = baseCol.blueF() - 0.1
mCol = max(rCol, gCol, bCol, 1.0)
errCol = QColor()
errCol.setRedF(rCol/mCol)
errCol.setGreenF(gCol/mCol)
errCol.setBlueF(bCol/mCol)
self.rxCol = {
True : baseCol,
False : errCol
}
logger.debug("GuiDocEditSearch initialisation complete")
return return
## END Class WordCounter def closeSearch(self):
"""Close the search box.
"""
self.mainConf.searchCase = self.isCaseSense
self.mainConf.searchWord = self.isWholeWord
self.mainConf.searchRegEx = self.isRegEx
self.mainConf.searchLoop = self.doLoop
self.mainConf.searchNextFile = self.doNextFile
self.mainConf.searchMatchCap = self.doMatchCap
self.showReplace.setChecked(False)
self.setVisible(False)
self.docEditor.updateDocMargins()
self.docEditor.setFocus()
return
##
# Get and Set Functions
##
def setSearchText(self, theText):
"""Open the search bar and set the search text to the text
provided, if any.
"""
if not self.isVisible():
self.setVisible(True)
self.searchBox.setText(theText)
self.searchBox.setFocus()
if self.isRegEx:
self._alertSearchValid(True)
logger.verbose("Setting search text to '%s'" % theText)
return True
def setReplaceText(self, theText):
"""Set the replace text.
"""
self.showReplace.setChecked(True)
self.replaceBox.setFocus()
self.replaceBox.setText(theText)
return True
def getSearchText(self):
"""Return the current search text either as text or as a regular
expression object.
"""
theText = self.searchBox.text()
if self.isRegEx and self.mainConf.verQtValue >= 50300:
if self.isCaseSense:
rxCase = Qt.CaseSensitive
else:
rxCase = Qt.CaseInsensitive
theRegEx = QRegExp(theText, rxCase)
self._alertSearchValid(theRegEx.isValid())
return theRegEx
return theText
def getReplaceText(self):
"""Return the current replace text.
"""
return self.replaceBox.text()
##
# Slots
##
def _doClose(self):
"""Hide the search/replace bar.
"""
self.closeSearch()
return
def _doSearch(self):
"""Call the search action function for the document editor.
"""
modKey = qApp.keyboardModifiers()
if modKey == Qt.ShiftModifier:
self.docEditor.docAction(nwDocAction.GO_PREV)
else:
self.docEditor.docAction(nwDocAction.GO_NEXT)
return
def _doReplace(self):
"""Call the replace action function for the document editor.
"""
self.docEditor.docAction(nwDocAction.REPL_NEXT)
return
def _doToggleReplace(self, theState):
"""Toggle the show/hide of the
"""
if theState:
self.showReplace.setArrowType(Qt.DownArrow)
else:
self.showReplace.setArrowType(Qt.RightArrow)
self.replaceBox.setVisible(theState)
self.replaceButton.setVisible(theState)
self.repVisible = theState
self.adjustSize()
self.docEditor.updateDocMargins()
return
def _doToggleCase(self, theState):
"""Enable/disable case sensitive mode.
"""
self.isCaseSense = theState
return
def _doToggleWord(self, theState):
"""Enable/disable whole word search mode.
"""
self.isWholeWord = theState
return
def _doToggleRegEx(self, theState):
"""Enable/disable regular expression search mode.
"""
self.isRegEx = theState
return
def _doToggleLoop(self, theState):
"""Enable/disable looping the search.
"""
self.doLoop = theState
return
def _doToggleProject(self, theState):
"""Enable/disable continuing search in next project file.
"""
self.doNextFile = theState
return
def _doToggleMatchCap(self, theState):
"""Enable/disable preserving capitalisation when replacing.
"""
self.doMatchCap = theState
return
##
# Internal Functions
##
def _alertSearchValid(self, isValid):
"""Highlight the search box to indicate the search string is or
isn't valid. Take the colour from the replace box.
"""
qPalette = self.replaceBox.palette()
qPalette.setColor(QPalette.Base, self.rxCol[isValid])
self.searchBox.setPalette(qPalette)
return
# END Class GuiDocEditSearch
# =============================================================================================== #
# The Embedded Document Header
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
# =============================================================================================== #
# =============================================================================================== # # =============================================================================================== #
# The Embedded Document Header # The Embedded Document Header
+9 -9
View File
@@ -559,16 +559,16 @@ class GuiDocViewHeader(QWidget):
class GuiDocViewFooter(QWidget): class GuiDocViewFooter(QWidget):
def __init__(self, theParent): def __init__(self, docViewer):
QWidget.__init__(self, theParent) QWidget.__init__(self, docViewer)
logger.debug("Initialising GuiDocViewFooter ...") logger.debug("Initialising GuiDocViewFooter ...")
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
self.theParent = theParent self.docViewer = docViewer
self.theTheme = theParent.theTheme self.theParent = docViewer.theParent
self.optState = theParent.theProject.optState self.theTheme = docViewer.theTheme
self.viewMeta = theParent.theParent.viewMeta self.viewMeta = docViewer.theParent.viewMeta
self.theHandle = None self.theHandle = None
# Make a QPalette that matches the Syntax Theme # Make a QPalette that matches the Syntax Theme
@@ -670,9 +670,9 @@ class GuiDocViewFooter(QWidget):
"""Toggle the sticky flag for the reference panel. """Toggle the sticky flag for the reference panel.
""" """
logger.verbose("Reference sticky is %s" % str(theState)) logger.verbose("Reference sticky is %s" % str(theState))
self.theParent.stickyRef = theState self.docViewer.stickyRef = theState
if not theState and self.theParent.theHandle is not None: if not theState and self.docViewer.theHandle is not None:
self.viewMeta.refreshReferences(self.theParent.theHandle) self.viewMeta.refreshReferences(self.docViewer.theHandle)
return return
# END Class GuiDocViewFooter # END Class GuiDocViewFooter
+32 -7
View File
@@ -60,8 +60,9 @@ class GuiProjectTree(QTreeWidget):
self.theProject = theParent.theProject self.theProject = theParent.theProject
# Tree Settings # Tree Settings
self.theMap = None self.theMap = None
self.orphRoot = None self.orphRoot = None
self.treeChanged = False
self.ctxMenu = GuiProjectTreeMenu(self) self.ctxMenu = GuiProjectTreeMenu(self)
self.clearTree() self.clearTree()
@@ -256,7 +257,7 @@ class GuiProjectTree(QTreeWidget):
pItem.insertChild(nIndex, cItem) pItem.insertChild(nIndex, cItem)
self.clearSelection() self.clearSelection()
cItem.setSelected(True) cItem.setSelected(True)
self.theProject.setProjectChanged(True) self._setTreeChanged(True)
else: else:
return False return False
return True return True
@@ -276,6 +277,16 @@ class GuiProjectTree(QTreeWidget):
self.theProject.setTreeOrder(theList) self.theProject.setTreeOrder(theList)
return True return True
def flushTreeOrder(self):
"""Calls saveTreeOrder if there are unsaved changes, otherwise
does nothing.
"""
if self.treeChanged:
logger.verbose("Flushing project tree to project class")
self.saveTreeOrder()
self._setTreeChanged(False)
return
def getTreeFromHandle(self, tHandle): def getTreeFromHandle(self, tHandle):
"""Recursively return all the children items starting from a """Recursively return all the children items starting from a
given item handle. given item handle.
@@ -332,6 +343,9 @@ class GuiProjectTree(QTreeWidget):
continue continue
self.deleteItem(tHandle, True) self.deleteItem(tHandle, True)
if nTrash > 0:
self._setTreeChanged(True)
return True return True
def deleteItem(self, tHandle=None, alreadyAsked=False, askForTrash=False): def deleteItem(self, tHandle=None, alreadyAsked=False, askForTrash=False):
@@ -413,7 +427,7 @@ class GuiProjectTree(QTreeWidget):
trItemT.addChild(trItemC) trItemT.addChild(trItemC)
nwItemS.setParent(self.theProject.projTree.trashRoot()) nwItemS.setParent(self.theProject.projTree.trashRoot())
self.theProject.setProjectChanged(True) self._setTreeChanged(True)
self.theParent.theIndex.deleteHandle(tHandle) self.theParent.theIndex.deleteHandle(tHandle)
elif nwItemS.itemType == nwItemType.FOLDER: elif nwItemS.itemType == nwItemType.FOLDER:
@@ -436,7 +450,7 @@ class GuiProjectTree(QTreeWidget):
if trItemS.childCount() == 0: if trItemS.childCount() == 0:
self.takeTopLevelItem(tIndex) self.takeTopLevelItem(tIndex)
self.theParent.mainMenu.setAvailableRoot() self.theParent.mainMenu.setAvailableRoot()
self.theProject.setProjectChanged(True) self._setTreeChanged(True)
else: else:
self.makeAlert("Cannot delete root folder. It is not empty.", nwAlert.ERROR) self.makeAlert("Cannot delete root folder. It is not empty.", nwAlert.ERROR)
return False return False
@@ -732,6 +746,8 @@ class GuiProjectTree(QTreeWidget):
elif nwItem.itemType == nwItemType.TRASH: elif nwItem.itemType == nwItemType.TRASH:
newItem.setIcon(self.C_NAME, self.theTheme.getIcon(nwLabels.CLASS_ICON[tClass])) newItem.setIcon(self.C_NAME, self.theTheme.getIcon(nwLabels.CLASS_ICON[tClass]))
self._setTreeChanged(True)
return newItem return newItem
def _addTrashRoot(self): def _addTrashRoot(self):
@@ -747,6 +763,7 @@ class GuiProjectTree(QTreeWidget):
self.theProject.projTree[trashHandle] self.theProject.projTree[trashHandle]
) )
trItem.setExpanded(True) trItem.setExpanded(True)
self._setTreeChanged(True)
return trItem return trItem
def _addOrphanedRoot(self): def _addOrphanedRoot(self):
@@ -793,7 +810,7 @@ class GuiProjectTree(QTreeWidget):
nwItemS.setParent(pHandle) nwItemS.setParent(pHandle)
self.propagateCount(tHandle, wC) self.propagateCount(tHandle, wC)
self.setTreeItemValues(tHandle) self.setTreeItemValues(tHandle)
self.theProject.setProjectChanged(True) self._setTreeChanged(True)
logger.debug("The parent of item %s has been changed to %s" % (tHandle,pHandle)) logger.debug("The parent of item %s has been changed to %s" % (tHandle,pHandle))
@@ -815,7 +832,15 @@ class GuiProjectTree(QTreeWidget):
pHandle = trItemP.data(self.C_NAME, Qt.UserRole) pHandle = trItemP.data(self.C_NAME, Qt.UserRole)
nwItemS.setParent(pHandle) nwItemS.setParent(pHandle)
self.setTreeItemValues(tHandle) self.setTreeItemValues(tHandle)
self.theProject.setProjectChanged(True) self._setTreeChanged(True)
return
def _setTreeChanged(self, theState):
"""Set the tree change flag, and propagate to the project.
"""
self.treeChanged = theState
if theState:
self.theProject.setProjectChanged(True)
return return
# END Class GuiProjectTree # END Class GuiProjectTree
+43 -37
View File
@@ -495,52 +495,58 @@ class GuiIcons:
ICON_MAP = { ICON_MAP = {
# Project and GUI icons # Project and GUI icons
"cls_none" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), "cls_none" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_novel" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), "cls_novel" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_plot" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), "cls_plot" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_character" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), "cls_character" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_world" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), "cls_world" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_timeline" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), "cls_timeline" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_object" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), "cls_object" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_entity" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), "cls_entity" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_custom" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), "cls_custom" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_trash" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), "cls_trash" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"proj_document" : (QStyle.SP_FileIcon, "x-office-document"), "proj_document" : (QStyle.SP_FileIcon, "x-office-document"),
"proj_folder" : (QStyle.SP_DirIcon, "folder"), "proj_folder" : (QStyle.SP_DirIcon, "folder"),
"proj_orphan" : (QStyle.SP_MessageBoxWarning, "dialog-warning"), "proj_orphan" : (QStyle.SP_MessageBoxWarning, "dialog-warning"),
"proj_nwx" : (None, None), "proj_nwx" : (None, None),
"status_lang" : (None, None), "status_lang" : (None, None),
"status_time" : (None, None), "status_time" : (None, None),
"status_stats" : (None, None), "status_stats" : (None, None),
"doc_h1" : (QStyle.SP_FileIcon, "x-office-document"), "doc_h1" : (QStyle.SP_FileIcon, "x-office-document"),
"doc_h2" : (QStyle.SP_FileIcon, "x-office-document"), "doc_h2" : (QStyle.SP_FileIcon, "x-office-document"),
"doc_h3" : (QStyle.SP_FileIcon, "x-office-document"), "doc_h3" : (QStyle.SP_FileIcon, "x-office-document"),
"doc_h4" : (QStyle.SP_FileIcon, "x-office-document"), "doc_h4" : (QStyle.SP_FileIcon, "x-office-document"),
"search_case" : (None, None),
"search_regex" : (None, None),
"search_word" : (None, None),
"search_loop" : (None, None),
"search_project" : (None, None),
"search_cancel" : (None, None),
"search_preserve" : (None, None),
## General Button Icons ## General Button Icons
"folder-open" : (QStyle.SP_DirOpenIcon, "folder-open"), "folder-open" : (QStyle.SP_DirOpenIcon, "folder-open"),
"delete" : (QStyle.SP_DialogDiscardButton, "edit-delete"), "delete" : (QStyle.SP_DialogDiscardButton, "edit-delete"),
"add" : (None, "list-add"),
"remove" : (None, "list-remove"),
"close" : (QStyle.SP_DialogCloseButton, "window-close"), "close" : (QStyle.SP_DialogCloseButton, "window-close"),
"done" : (QStyle.SP_DialogApplyButton, None), "done" : (QStyle.SP_DialogApplyButton, None),
"search" : (None, "edit-find"),
"search-replace" : (None, "edit-find-replace"),
"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), "add" : (None, "list-add"),
"check" : (None, None), "remove" : (None, "list-remove"),
"cross" : (None, None), "search" : (None, "edit-find"),
"hash" : (None, None), "search-replace" : (None, "edit-find-replace"),
"maximise" : (None, None), "edit" : (None, None),
"minimise" : (None, None), "check" : (None, None),
"refresh" : (None, None), "cross" : (None, None),
"reference" : (None, None), "hash" : (None, None),
"sticky-on" : (None, None), "maximise" : (None, None),
"sticky-off" : (None, None), "minimise" : (None, None),
"refresh" : (None, None),
"reference" : (None, None),
## Other Icons ## Switches
"warning" : (QStyle.SP_MessageBoxWarning, "dialog-warning"), "sticky-on" : (None, None),
"sticky-off" : (None, None),
} }
DECO_MAP = { DECO_MAP = {
+42 -24
View File
@@ -42,8 +42,8 @@ from PyQt5.QtWidgets import (
from nw.gui import ( from nw.gui import (
GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails, GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails,
GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainMenu, GuiMainStatus, GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainMenu, GuiMainStatus,
GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad, GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad, GuiTheme,
GuiProjectSettings, GuiProjectTree, GuiSearchBar, GuiSessionLogView, GuiTheme GuiProjectSettings, GuiProjectTree, GuiSessionLogView
) )
from nw.core import NWProject, NWDoc, NWIndex from nw.core import NWProject, NWDoc, NWIndex
from nw.constants import nwFiles, nwItemType, nwAlert from nw.constants import nwFiles, nwItemType, nwAlert
@@ -93,7 +93,6 @@ class GuiMain(QMainWindow):
self.docEditor = GuiDocEditor(self) self.docEditor = GuiDocEditor(self)
self.viewMeta = GuiDocViewDetails(self) self.viewMeta = GuiDocViewDetails(self)
self.docViewer = GuiDocViewer(self) self.docViewer = GuiDocViewer(self)
self.searchBar = GuiSearchBar(self)
self.treeMeta = GuiItemDetails(self) self.treeMeta = GuiItemDetails(self)
self.projView = GuiOutline(self) self.projView = GuiOutline(self)
self.projMeta = GuiOutlineDetails(self) self.projMeta = GuiOutlineDetails(self)
@@ -111,22 +110,13 @@ class GuiMain(QMainWindow):
self.treeBox.addWidget(self.treeMeta) self.treeBox.addWidget(self.treeMeta)
self.treePane.setLayout(self.treeBox) self.treePane.setLayout(self.treeBox)
self.editPane = QWidget()
self.docEdit = QVBoxLayout()
self.docEdit.setContentsMargins(0, 0, 0, 0)
self.docEdit.setSpacing(self.mainConf.pxInt(2))
self.docEdit.addWidget(self.searchBar)
self.docEdit.addWidget(self.docEditor)
self.editPane.setLayout(self.docEdit)
self.splitView = QSplitter(Qt.Vertical) self.splitView = QSplitter(Qt.Vertical)
self.splitView.addWidget(self.docViewer) self.splitView.addWidget(self.docViewer)
self.splitView.addWidget(self.viewMeta) self.splitView.addWidget(self.viewMeta)
self.splitView.setSizes(self.mainConf.getViewPanePos()) self.splitView.setSizes(self.mainConf.getViewPanePos())
self.splitDocs = QSplitter(Qt.Horizontal) self.splitDocs = QSplitter(Qt.Horizontal)
self.splitDocs.setOpaqueResize(False) self.splitDocs.addWidget(self.docEditor)
self.splitDocs.addWidget(self.editPane)
self.splitDocs.addWidget(self.splitView) self.splitDocs.addWidget(self.splitView)
self.splitOutline = QSplitter(Qt.Vertical) self.splitOutline = QSplitter(Qt.Vertical)
@@ -144,7 +134,6 @@ class GuiMain(QMainWindow):
xCM = self.mainConf.pxInt(4) xCM = self.mainConf.pxInt(4)
self.splitMain = QSplitter(Qt.Horizontal) self.splitMain = QSplitter(Qt.Horizontal)
self.splitMain.setContentsMargins(xCM, xCM, xCM, xCM) self.splitMain.setContentsMargins(xCM, xCM, xCM, xCM)
self.splitMain.setOpaqueResize(False)
self.splitMain.addWidget(self.treePane) self.splitMain.addWidget(self.treePane)
self.splitMain.addWidget(self.tabWidget) self.splitMain.addWidget(self.tabWidget)
self.splitMain.setSizes(self.mainConf.getMainPanePos()) self.splitMain.setSizes(self.mainConf.getMainPanePos())
@@ -153,7 +142,7 @@ class GuiMain(QMainWindow):
self.idxTree = self.splitMain.indexOf(self.treePane) self.idxTree = self.splitMain.indexOf(self.treePane)
self.idxMain = self.splitMain.indexOf(self.tabWidget) self.idxMain = self.splitMain.indexOf(self.tabWidget)
self.idxEditor = self.splitDocs.indexOf(self.editPane) self.idxEditor = self.splitDocs.indexOf(self.docEditor)
self.idxViewer = self.splitDocs.indexOf(self.splitView) self.idxViewer = self.splitDocs.indexOf(self.splitView)
self.idxViewDoc = self.splitView.indexOf(self.docViewer) self.idxViewDoc = self.splitView.indexOf(self.docViewer)
self.idxViewMeta = self.splitView.indexOf(self.viewMeta) self.idxViewMeta = self.splitView.indexOf(self.viewMeta)
@@ -168,7 +157,7 @@ class GuiMain(QMainWindow):
self.splitView.setCollapsible(self.idxViewMeta, False) self.splitView.setCollapsible(self.idxViewMeta, False)
self.splitView.setVisible(False) self.splitView.setVisible(False)
self.searchBar.setVisible(False) self.docEditor.closeSearch()
# Build the Tree View # Build the Tree View
self.treeView.itemSelectionChanged.connect(self._treeSingleClick) self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
@@ -489,6 +478,37 @@ class GuiMain(QMainWindow):
return False return False
return True return True
def openNextDocument(self, tHandle, wrapAround=False):
"""Opens the next document in the project tree, following the
document with the given handle. Stops when reaching the end.
"""
if self.hasProject:
self.treeView.flushTreeOrder()
nHandle = None # The next handle after tHandle
fHandle = None # The first file handle we encounter
foundIt = False # We've found tHandle, pick the next we see
for tItem in self.theProject.projTree:
if tItem is None:
continue
if tItem.itemType != nwItemType.FILE:
continue
if fHandle is None:
fHandle = tItem.itemHandle
if tItem.itemHandle == tHandle:
foundIt = True
elif foundIt:
nHandle = tItem.itemHandle
break
if nHandle is not None:
self.openDocument(nHandle, tLine=0)
return True
elif wrapAround:
self.openDocument(fHandle, tLine=0)
return False
return False
def saveDocument(self): def saveDocument(self):
"""Save the current documents. """Save the current documents.
""" """
@@ -603,15 +623,13 @@ class GuiMain(QMainWindow):
return True return True
def passDocumentAction(self, theAction): def passDocumentAction(self, theAction):
"""Pass on document action theAction to whatever document has """Pass on document action theAction to the document viewer if
the focus. If no document has focus, the action is discarded. it has focus, otherwise pass it to the document editor.
""" """
if self.docEditor.hasFocus(): if self.docViewer.hasFocus():
self.docEditor.docAction(theAction)
elif self.docViewer.hasFocus():
self.docViewer.docAction(theAction) self.docViewer.docAction(theAction)
else: else:
logger.debug("Document action requested, but no document has focus") self.docEditor.docAction(theAction)
return True return True
## ##
@@ -1077,8 +1095,8 @@ class GuiMain(QMainWindow):
"""When the escape key is pressed somewhere in the main window, """When the escape key is pressed somewhere in the main window,
do the following, in order: do the following, in order:
""" """
if self.searchBar.isVisible(): if self.docEditor.docSearch.isVisible():
self.searchBar.setVisible(False) self.docEditor.closeSearch()
return return
elif self.isZenMode: elif self.isZenMode:
self.toggleZenMode() self.toggleZenMode()
+7 -1
View File
@@ -1,5 +1,5 @@
[Main] [Main]
timestamp = 2020-06-13 22:59:36 timestamp = 2020-06-17 19:45:53
theme = default theme = default
syntax = default_light syntax = default_light
icons = typicons_colour_light icons = typicons_colour_light
@@ -55,6 +55,12 @@ askbeforebackup = True
showrefpanel = True showrefpanel = True
viewcomments = True viewcomments = True
viewsynopsis = True viewsynopsis = True
searchcase = False
searchword = False
searchregex = False
searchloop = False
searchnextfile = False
searchmatchcap = False
[Path] [Path]
lastpath = lastpath =