Merge pull request #315 from vkbo/dev

Dev to Master for 0.9
This commit is contained in:
Veronica K. Berglyd Olsen
2020-06-20 20:15:19 +02:00
committed by GitHub
100 changed files with 2456 additions and 805 deletions
+5 -3
View File
@@ -3,12 +3,14 @@ codecov:
coverage:
precision: 2
round: down
round: up
range: "70...100"
status:
project: yes
patch: yes
project:
default:
threshold: 1%
patch: no
changes: no
parsers:
+1 -1
View File
@@ -18,7 +18,7 @@ install:
- pip install --upgrade pip
- pip install -r requirements.txt
# - pip install pytest-faulthandler
- pip install PyVirtualDisplay==0.2.5
- pip install PyVirtualDisplay
- pip install pytest-xvfb
- pip install pytest-cov
- pip install pytest-qt
+26
View File
@@ -1,5 +1,31 @@
# novelWriter ChangeLog
## Version 0.9rc1 [2020-xx-xx]
**Core Functionality**
* Underline text formatting has been removed. It is not standard HTML5, not Markdown, and was previously implemented using the double underscore notation that in standard Markdown is renderred as bold text. Instead, novelWriter now renders a single `*` or `_` wrapping a piece of text *within* a paragraphs as italicised text, and a double `**` or `__` as bold text. The keyboard shortcuts and automatic features **only** support the `*` notation. A triple set of `***` are treated as both bold and italicised. PR #310.
* Strikethrough formatting has been added back into novelWriter using the standard Markdown `~~` wrapping. PR #310.
* Added support for thin spaces and non-breaking thin spaces. PR #319.
* The `Ctrl+Z` key sequence would not go through the wrapper function for document action for the document editor, but act directly on the document. This caused some of the logic preventing conflict between auto-replace and undo to be bypassed. This has now been resolved by blocking the keypress itself. Issue #320, PR #321.
* The dialog window size and column width setting for the auto-replace feature in Project Settings are now preserved between closing and opening the dialog. Issue #322, PR #324.
**User Interface**
* The Open Project dialog will now ask before removing an entry from the recent projects list. PR #309.
* The text emphasis functions, either selected from the menu or via keyboard shortcuts, will now try to respond to the command in a more meaningful way. That is, the text editor will try to toggle the bold or italics features independently of eachother on the selected text. A feature to apply both at the same time has also been added. PR #310.
* The document editor search tool has been completely rewritten. It now appears as a search box at the top of the document, and has a number of toggle switches added to it. You can modify the search tool to be case sensitive, select only whole words, use regular expression search strings, loop when reaching the end, and continue the search in next file. For the replace feature, you can also select to have the feature try to preserve the case of the replaced word. Issues #84 and #305, PR #314.
* A dialog has been added for selecting quotation mark style. These are now used in the Preferences dialog instead of a plain text box. PR #317.
* Added an insert menu for inserting special symbols like dashes, ellipsis, thin and non-breaking spaces, and hard line breaks. PR #319.
* A menu option to replace straight single and double quotes in a selected piece of text has been added. This uses the same logic as the auto-replace feature. Issue #312, PR #321.
* When pressing `Ctrl+R` while the document editor has focus, the edited document will be viewed or refreshed in the document viewer. Previously, the selected document in the project tree had priority. The document is also now saved before loaded in the viewer, censuring that it shows the very latest changes. Issue #143, PR #323.
* The selection in the project tree should not scroll into view when just opening the document. This can be quite annoying if loading several documents in sequence by double-clicking, as the target may move just when you're about to click. PR #325.
**Other Changes**
* Added the file's class and layout to the meta data string added as the first line of saved document files. This meta data is only used to restore the file meta information into the project if it was lost from the project file. It is also useful information when reading the file in external tools. PR #308.
## Version 0.8 [2020-06-14]
**Bugfixes**
+1 -1
View File
@@ -48,7 +48,7 @@ It allows for a minimal set of formatting needed for writing text documents for
These are currently limited to:
* Headings level 1 to 4 using the `#` syntax only.
* Bold, italic and underline text.
* Bold, italic and strikethrough text.
* Hard line breaks using two or more spaces at the end of a line.
That is it.
+2 -2
View File
@@ -24,9 +24,9 @@ copyright = "2018-2020, Veronica Berglyd Olsen"
author = "Veronica Berglyd Olsen"
# The short X.Y version
version = "0.8"
version = "0.9"
# The full version, including alpha/beta/rc tags
release = "0.8.0"
release = "0.9.0rc1"
# -- General configuration ---------------------------------------------------
+10 -5
View File
@@ -33,8 +33,8 @@ Markdown Format
===============
The document editor uses a simplified markdown format.
That is, it supports basic formatting like bold, italics and underline, as well as four levels of headings.
The formats are listed below.
That is, it supports basic formatting like bold, italics and strikethrough, as well as four levels of headings.
The preference of novelWriter is to use `*` for wrapping emphasised text, but `_` is partially supported when typed, but not by the automatic formatting features and keyboard shortcuts.
In addition to these standard markdown features, the editor also allows for comments, that is text that is ignored by the word counter and not exported or, optionally, hidden in the document viewer.
If the first word of a comment is "Synopsis:" (with the colon), the comment is treated specially, and will show up in the Outline View.
@@ -48,9 +48,12 @@ The editor also has a minimal set of keywords used for setting tags and referenc
"``## Title``", "Heading level two. The space after the # is mandatory."
"``### Title``", "Heading level three. The space after the # is mandatory."
"``#### Title``", "Heading level four. The space after the # is mandatory."
"``*text*``", "The text is rendered as italicised text."
"``**text**``", "The text is rendered as bold text."
"``_text_``", "The text is rendered as italicized text."
"``__text__``", "The text is rendered as underlined text."
"``***text***``", "The text is rendered as bold italicised text."
"``_text_``", "Alternative format for italicised text."
"``__text__``", "Alternative format for bold text."
"``~~text~~``", "Strikethrough text."
"``% text...``", "A comment. The text is not exported by default, seen in viewer, or counted towards word counts."
"``% Synopsis: text...``", "A synopsis comment. Shows up in the Synopsis column of the Outline View, but is otherwise treated as a comment."
"``@keyword: value``", "A keyword argument followed by a value, or a comma separated list of values."
@@ -110,6 +113,7 @@ These are as following:
":kbd:`Ctrl-.`", "Correct word under cursor."
":kbd:`Ctrl-,`", "Open the Preferences dialog."
":kbd:`Ctrl-/`", "Change block format to comment."
":kbd:`Ctrl--`", "Strikethrough selected text, or word under cursor."
":kbd:`Ctrl-0`", "Remove block formatting for block under cursor."
":kbd:`Ctrl-1`", "Change block format to header level 1."
":kbd:`Ctrl-2`", "Change block format to header level 2."
@@ -122,7 +126,7 @@ These are as following:
":kbd:`Ctrl-E`", "If in tree view, edit a document or folder settings. (Same as :kbd:`F2`)"
":kbd:`Ctrl-F`", "Open the search bar and search for selected word, if any is selected."
":kbd:`Ctrl-G`", "Find next occurrence of word in current document. (Same as :kbd:`F3`)"
":kbd:`Ctrl-H`", "Open the search and replace bar and search for selected word, if any is selected. (On Mac, this is :kbd:`Cmd+=`)"
":kbd:`Ctrl-H`", "Open the search and replace bar and search for selected word, if any is selected. (On Mac, this is :kbd:`Cmd-=`)"
":kbd:`Ctrl-I`", "Format selected text, or word under cursor, as italic."
":kbd:`Ctrl-N`", "Create new document."
":kbd:`Ctrl-O`", "Open selected document."
@@ -143,6 +147,7 @@ These are as following:
":kbd:`Ctrl-Shift-/`", "Remove block formatting for block under cursor."
":kbd:`Ctrl-Shift-1`", "Replace occurrence of word in current document, and search for next occurrence."
":kbd:`Ctrl-Shift-A`", "Select all text in current paragraph."
":kbd:`Ctrl-Shift-B`", "Format selected text, or word under cursor, as bold and italic."
":kbd:`Ctrl-Shift-D`", "Wrap selected text, or word under cursor, in single quotes."
":kbd:`Ctrl-Shift-G`", "Find previous occurrence of word in current document. (Same as :kbd:`Shift-F3`"
":kbd:`Ctrl-Shift-I`", "Import text to the current document from a text file."
+3 -2
View File
@@ -40,13 +40,14 @@ __package__ = "novelWriter"
__author__ = "Veronica Berglyd Olsen"
__copyright__ = "Copyright 20182020, Veronica Berglyd Olsen"
__license__ = "GPLv3"
__version__ = "0.8"
__hexversion__ = "0x000800f0"
__version__ = "0.9.0rc1"
__hexversion__ = "0x000900c1"
__date__ = "2020-06-14"
__maintainer__ = "Veronica Berglyd Olsen"
__email__ = "code@vkbo.net"
__status__ = "Pre-Release"
__url__ = "https://github.com/vkbo/novelWriter"
__issuesurl__ = "https://github.com/vkbo/novelWriter/issues"
__domain__ = "novelwriter.io"
__docurl__ = "https://novelwriter.readthedocs.io"
__credits__ = [
@@ -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
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.spellLanguage = None
self.searchCase = False
self.searchWord = False
self.searchRegEx = False
self.searchLoop = False
self.searchNextFile = False
self.searchMatchCap = False
## Backup
self.backupPath = ""
self.backupOnClose = False
@@ -482,6 +489,24 @@ class Config:
self.viewSynopsis = self._parseLine(
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
cnfSec = "Path"
@@ -571,9 +596,15 @@ class Config:
## State
cnfSec = "State"
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec,"showrefpanel",str(self.showRefPanel))
cnfParse.set(cnfSec,"viewcomments",str(self.viewComments))
cnfParse.set(cnfSec,"viewsynopsis",str(self.viewSynopsis))
cnfParse.set(cnfSec,"showrefpanel", str(self.showRefPanel))
cnfParse.set(cnfSec,"viewcomments", str(self.viewComments))
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
cnfSec = "Path"
+7 -2
View File
@@ -1,25 +1,30 @@
# -*- coding: utf-8 -*-
from nw.constants.iso import isoLanguage, isoCountry
from nw.constants.constants import (
nwConst, nwFiles, nwKeyWords, nwLabels, nwQuotes, nwUnicode
nwConst, nwRegEx, nwFiles, nwKeyWords, nwLabels, nwQuotes, nwUnicode,
nwInsertSymbols
)
from nw.constants.enum import (
nwAlert, nwDocAction, nwItemClass, nwItemLayout, nwItemType, nwOutline
nwAlert, nwDocAction, nwItemClass, nwItemLayout, nwItemType, nwOutline,
nwDocInsert
)
__all__ = [
"isoLanguage",
"isoCountry",
"nwConst",
"nwRegEx",
"nwFiles",
"nwKeyWords",
"nwLabels",
"nwQuotes",
"nwUnicode",
"nwInsertSymbols",
"nwAlert",
"nwDocAction",
"nwItemClass",
"nwItemLayout",
"nwItemType",
"nwOutline",
"nwDocInsert",
]
+62 -25
View File
@@ -25,7 +25,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from nw.constants.enum import nwItemClass, nwItemLayout, nwOutline
from nw.constants.enum import nwItemClass, nwItemLayout, nwOutline, nwDocInsert
class nwConst():
@@ -34,6 +34,15 @@ class nwConst():
# END Class nwConst
class nwRegEx():
FMT_B = r"(?<![\w|\*|_|\\])([\*|_]{2})(?!\s|\*|_)(.+?)(?<![\s|\\])(\1)(?!\w)"
FMT_I = r"(?<![\w|\*|_|\\])([\*|_])(?!\s|\*|_)(.+?)(?<![\s|\\])(\1)(?!\w)"
FMT_BI = r"(?<![\w|\*|\\])([\*]{3})(?!\s|\*)(.+?)(?<![\s|\\])(\1)(?!\w)"
FMT_ST = r"(?<![\w|~|\\])([~]{2})(?!\s|~)(.+?)(?<![\s|\\])(\1)(?!\w)"
# END Class nwRegEx
class nwFiles():
PROJ_FILE = "nwProject.nwx"
@@ -160,32 +169,35 @@ class nwQuotes():
Source: https://en.wikipedia.org/wiki/Quotation_mark
"""
SYMBOLS = [
"\u0022", # Quotation mark
"\u0027", # Apostrophe
"\u00ab", # Left-pointing double angle quotation mark
"\u00bb", # Right-pointing double angle quotation mark
"\u2018", # Left single quotation mark
"\u2019", # Right single quotation mark
"\u201a", # Single low-9 quotation mark
"\u201b", # Single high-reversed-9 quotation mark
"\u201c", # Left double quotation mark
"\u201d", # Right double quotation mark
"\u201e", # Double low-9 quotation mark
"\u201f", # Double high-reversed-9 quotation mark
"\u2039", # Single left-pointing angle quotation mark
"\u203a", # Single right-pointing angle quotation mark
"\u2e42", # Double low-reversed-9 quotation mark
"\u300c", # Left corner bracket
"\u300d", # Right corner bracket
"\u300e", # Left white corner bracket
"\u300f", # Right white corner bracket
]
SYMBOLS = {
"\u0027" : "Straight single quotation mark",
"\u0022" : "Straight double quotation mark",
"\u2018" : "Left single quotation mark",
"\u2019" : "Right single quotation mark",
"\u201a" : "Single low-9 quotation mark",
"\u201b" : "Single high-reversed-9 quotation mark",
"\u201c" : "Left double quotation mark",
"\u201d" : "Right double quotation mark",
"\u201e" : "Double low-9 quotation mark",
"\u201f" : "Double high-reversed-9 quotation mark",
"\u2e42" : "Double low-reversed-9 quotation mark",
"\u2039" : "Single left-pointing angle quotation mark",
"\u203a" : "Single right-pointing angle quotation mark",
"\u00ab" : "Left-pointing double angle quotation mark",
"\u00bb" : "Right-pointing double angle quotation mark",
"\u300c" : "Left corner bracket",
"\u300d" : "Right corner bracket",
"\u300e" : "Left white corner bracket",
"\u300f" : "Right white corner bracket",
}
# END Class nwQuotes
class nwUnicode:
"""Suppoted unicode character constants and translation maps for HTML.
"""Supported unicode character constants and translation maps for HTML.
"""
# Unicode Constants
@@ -217,8 +229,14 @@ class nwUnicode:
U_EMDASH = "\u2014" # Long dash
U_HELLIP = "\u2026" # Ellipsis
## Other
## Spaces and Lines
U_NBSP = "\u00a0" # Non-breaking space
U_THNSP = "\u2009" # Thin space
U_THNBSP = "\u202f" # Thin non-breaking space
U_LSEP = "\u2028" # Line separator
U_PSEP = "\u2029" # Paragraph separator
## Symbols
U_CHECK = "\u2714" # Heavy check mark
U_MULT = "\u2715" # Multiplication x
@@ -261,8 +279,12 @@ class nwUnicode:
H_EMDASH = "&mdash;"
H_HELLIP = "&hellip;"
## Other
## Spaces
H_NBSP = "&nbsp;"
H_THNSP = "&thinsp;"
H_THNBSP = "&#8239;"
## Symbols
H_CHECK = "&#10004;"
H_MULT = "&#10005;"
@@ -277,3 +299,18 @@ class nwUnicode:
H_LTRIS = "&#9666;"
# END Class nwUnicode
class nwInsertSymbols():
SYMBOLS = {
nwDocInsert.NO_INSERT : "",
nwDocInsert.HARD_BREAK : " \n",
nwDocInsert.NB_SPACE : nwUnicode.U_NBSP,
nwDocInsert.THIN_SPACE : nwUnicode.U_THNSP,
nwDocInsert.THIN_NB_SPACE : nwUnicode.U_THNBSP,
nwDocInsert.SHORT_DASH : nwUnicode.U_ENDASH,
nwDocInsert.LONG_DASH : nwUnicode.U_EMDASH,
nwDocInsert.ELLIPSIS : nwUnicode.U_HELLIP,
}
# END Enum nwDocInsert
+40 -24
View File
@@ -68,33 +68,49 @@ class nwItemLayout(Enum):
class nwDocAction(Enum):
NO_ACTION = 0
UNDO = 1
REDO = 2
CUT = 3
COPY = 4
PASTE = 5
BOLD = 6
ITALIC = 7
U_LINE = 8
S_QUOTE = 9
D_QUOTE = 10
SEL_ALL = 11
SEL_PARA = 12
FIND = 13
REPLACE = 14
GO_NEXT = 15
GO_PREV = 16
REPL_NEXT = 17
BLOCK_H1 = 18
BLOCK_H2 = 19
BLOCK_H3 = 20
BLOCK_H4 = 21
BLOCK_COM = 22
BLOCK_TXT = 23
NO_ACTION = 0
UNDO = 1
REDO = 2
CUT = 3
COPY = 4
PASTE = 5
ITALIC = 6
BOLD = 7
BOLDITALIC = 8
STRIKE = 9
S_QUOTE = 10
D_QUOTE = 11
SEL_ALL = 12
SEL_PARA = 13
FIND = 14
REPLACE = 15
GO_NEXT = 16
GO_PREV = 17
REPL_NEXT = 18
BLOCK_H1 = 19
BLOCK_H2 = 20
BLOCK_H3 = 21
BLOCK_H4 = 22
BLOCK_COM = 23
BLOCK_TXT = 24
REPL_SNG = 25
REPL_DBL = 26
# END Enum nwDocAction
class nwDocInsert(Enum):
NO_INSERT = 0
HARD_BREAK = 1
NB_SPACE = 2
THIN_SPACE = 3
THIN_NB_SPACE = 4
SHORT_DASH = 5
LONG_DASH = 6
ELLIPSIS = 7
# END Enum nwDocInsert
class nwAlert(Enum):
INFO = 0
+28 -4
View File
@@ -30,8 +30,10 @@ import nw
from os import path, mkdir, rename, unlink
from nw.core.item import NWItem
from nw.constants import nwAlert
from nw.common import isHandle
from nw.constants import nwItemLayout, nwItemClass
logger = logging.getLogger(__name__)
@@ -139,8 +141,18 @@ class NWDoc():
docPath = path.join(self.theProject.projContent, docFile)
docTemp = path.join(self.theProject.projContent, docFile+"~")
itemPath = self.theProject.projTree.getItemPath(self.docHandle)
docMeta = "%%~ "+":".join(itemPath)+":"+self.theItem.itemName+"\n"
if isinstance(self.theItem, NWItem):
itemPath = self.theProject.projTree.getItemPath(self.docHandle)
docMeta = (
"%%~ {handlepath:s}:{itemclass:s}:{itemlayout:s}:{itemname:s}\n"
).format(
handlepath = ":".join(itemPath),
itemclass = self.theItem.itemClass.name,
itemlayout = self.theItem.itemLayout.name,
itemname = self.theItem.itemName,
)
else:
docMeta = ""
try:
with open(docTemp, mode="w", encoding="utf8") as outFile:
@@ -194,7 +206,7 @@ class NWDoc():
"""
if len(self.docMeta) < 14:
# Not enough information
return "", []
return "", [], None, None
theMeta = self.docMeta
@@ -213,6 +225,18 @@ class NWDoc():
else:
break
return theMeta, thePath
theClass = nwItemClass.NO_CLASS
for aClass in nwItemClass:
if theMeta.startswith(aClass.name):
theClass = aClass
theMeta = theMeta.lstrip(aClass.name+":")
theLayout = nwItemLayout.NO_LAYOUT
for aLayout in nwItemLayout:
if theMeta.startswith(aLayout.name):
theLayout = aLayout
theMeta = theMeta.lstrip(aLayout.name+":")
return theMeta, thePath, theClass, theLayout
# END Class NWDoc
+5 -4
View File
@@ -41,7 +41,7 @@ logger = logging.getLogger(__name__)
class NWIndex():
VALID_KEYS = set([
VALID_KEYS = {
nwKeyWords.TAG_KEY,
nwKeyWords.PLOT_KEY,
nwKeyWords.POV_KEY,
@@ -51,7 +51,7 @@ class NWIndex():
nwKeyWords.OBJECT_KEY,
nwKeyWords.ENTITY_KEY,
nwKeyWords.CUSTOM_KEY
])
}
TAG_CLASS = {
nwKeyWords.CHAR_KEY : nwItemClass.CHARACTER,
nwKeyWords.POV_KEY : nwItemClass.CHARACTER,
@@ -305,11 +305,12 @@ class NWIndex():
elif aLine.startswith(r"%"):
if nTitle > 0:
toCheck = aLine[1:].lstrip().lower()
toCheck = aLine[1:].lstrip()
synTag = toCheck[:9].lower()
tLen = len(aLine)
cLen = len(toCheck)
cOff = tLen - cLen
if toCheck.startswith("synopsis:"):
if synTag == "synopsis:":
self._indexSynopsis(tHandle, isNovel, aLine[cOff+9:].strip(), nTitle)
# Count words for remaining text after last heading
+13 -8
View File
@@ -45,7 +45,7 @@ class OptionState():
self.theState = {}
self.validMap = {
"GuiSession": set([
"GuiSession": {
"widthCol0",
"widthCol1",
"widthCol2",
@@ -53,11 +53,11 @@ class OptionState():
"sortOrder",
"hideZeros",
"hideNegative",
]),
"GuiDocSplit": set([
},
"GuiDocSplit": {
"spLevel",
]),
"GuiBuildNovel": set([
},
"GuiBuildNovel": {
"winWidth",
"winHeight",
"addNovel",
@@ -72,12 +72,17 @@ class OptionState():
"incComments",
"incKeywords",
"incBodyText",
]),
"GuiOutline": set([
},
"GuiOutline": {
"headerOrder",
"columnWidth",
"columnHidden",
]),
},
"GuiProjectSettings": {
"winWidth",
"winHeight",
"replaceColW",
}
}
return
+8 -3
View File
@@ -1075,17 +1075,22 @@ class NWProject():
# Look for meta data
oName = ""
if aDoc.openDocument(oHandle, showStatus=False, isOrphan=True):
oName, oPath = aDoc.getMeta()
oName, oPath, oClass, oLayout = aDoc.getMeta()
if oName == "":
nOrph += 1
oName = "Orphaned File %d" % nOrph
if oClass is None:
oClass = nwItemClass.NO_CLASS
if oLayout is None:
oLayout = nwItemLayout.NO_LAYOUT
orphItem = NWItem(self)
orphItem.setName(oName)
orphItem.setType(nwItemType.FILE)
orphItem.setClass(nwItemClass.NO_CLASS)
orphItem.setLayout(nwItemLayout.NO_LAYOUT)
orphItem.setClass(oClass)
orphItem.setLayout(oLayout)
self.projTree.append(oHandle, None, orphItem)
return
+24 -8
View File
@@ -54,6 +54,8 @@ class ToHtml(Tokenizer):
nwUnicode.U_EMDASH : nwUnicode.H_EMDASH,
nwUnicode.U_HELLIP : nwUnicode.H_HELLIP,
nwUnicode.U_NBSP : nwUnicode.H_NBSP,
nwUnicode.U_THNSP : nwUnicode.H_THNSP,
nwUnicode.U_THNBSP : nwUnicode.H_THNBSP,
}
self.revDict = {}
self.reReplace = []
@@ -117,14 +119,28 @@ class ToHtml(Tokenizer):
"""Convert the list of text tokens into a HTML document saved
to theResult.
"""
htmlTags = {
self.FMT_B_B : "<strong>",
self.FMT_B_E : "</strong>",
self.FMT_I_B : "<em>",
self.FMT_I_E : "</em>",
self.FMT_U_B : "<u>",
self.FMT_U_E : "</u>",
}
if self.genMode == self.M_PREVIEW:
htmlTags = { # HTML4 + CSS2
self.FMT_B_B : "<b>",
self.FMT_B_E : "</b>",
self.FMT_I_B : "<i>",
self.FMT_I_E : "</i>",
self.FMT_S_B : "<b><i>",
self.FMT_S_E : "</i></b>",
self.FMT_D_B : "<span style='text-decoration: line-through;'>",
self.FMT_D_E : "</span>",
}
else:
htmlTags = { # HTML5
self.FMT_B_B : "<strong>",
self.FMT_B_E : "</strong>",
self.FMT_I_B : "<em>",
self.FMT_I_E : "</em>",
self.FMT_S_B : "<strong><em>",
self.FMT_S_E : "</em></strong>",
self.FMT_D_B : "<del>",
self.FMT_D_E : "</del>",
}
if self.isNovel and self.genMode != self.M_PREVIEW:
# For novel files for export, we bump the titles one level
+18 -19
View File
@@ -34,7 +34,7 @@ from PyQt5.QtCore import QRegularExpression
from nw.core.document import NWDoc
from nw.core.tools import numberToWord
from nw.constants import nwItemLayout, nwItemType
from nw.constants import nwItemLayout, nwItemType, nwRegEx
logger = logging.getLogger(__name__)
@@ -44,8 +44,10 @@ class Tokenizer():
FMT_B_E = 2 # End bold
FMT_I_B = 3 # Begin italics
FMT_I_E = 4 # End italics
FMT_U_B = 5 # Begin underline
FMT_U_E = 6 # End underline
FMT_S_B = 5 # Begin bold italic
FMT_S_E = 6 # End bold italic
FMT_D_B = 7 # Begin strikeout
FMT_D_E = 8 # End strikeout
T_EMPTY = 1 # Empty line (new paragraph)
T_SYNOPSIS = 2 # Synopsis comment
@@ -292,23 +294,19 @@ class Tokenizer():
The format of the token list is an entry with a four-tuple for
each line in the file. The tuple is as follows:
1: The type of the block, self.T_*
2: The text content of the block, without leading tags
3: The internal formatting map of the text, self.FMT_*
4: The style of the block, self.A_*
2: The line in file where this block occurred
3: The text content of the block, without leading tags
4: The internal formatting map of the text, self.FMT_*
5: The style of the block, self.A_*
"""
# RegExes for adding formatting tags within text lines
# Keep in sync with the DocHighlighter class
rxFormats = [(
QRegularExpression(r"(?<![\w|\\])([\*]{2})(?!\s)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)"),
[None, self.FMT_B_B, None, self.FMT_B_E]
),(
QRegularExpression(r"(?<![\w|_|\\])([_])(?!\s|\1)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)"),
[None, self.FMT_I_B, None, self.FMT_I_E]
),(
QRegularExpression(r"(?<![\w|\\])([_]{2})(?!\s)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)"),
[None, self.FMT_U_B, None, self.FMT_U_E]
)]
rxFormats = [
(QRegularExpression(nwRegEx.FMT_I), [None, self.FMT_I_B, None, self.FMT_I_E]),
(QRegularExpression(nwRegEx.FMT_B), [None, self.FMT_B_B, None, self.FMT_B_E]),
(QRegularExpression(nwRegEx.FMT_BI), [None, self.FMT_S_B, None, self.FMT_S_E]),
(QRegularExpression(nwRegEx.FMT_ST), [None, self.FMT_D_B, None, self.FMT_D_E]),
]
self.theTokens = []
self.theMarkdown = ""
@@ -329,8 +327,9 @@ class Tokenizer():
tmpMarkdown.append("\n")
elif aLine[0] == "%":
cLine = aLine[1:].strip()
if cLine.lower().startswith("synopsis:"):
cLine = aLine[1:].lstrip()
synTag = cLine[:9].lower()
if synTag == "synopsis:":
self.theTokens.append((
self.T_SYNOPSIS,
nLine,
-2
View File
@@ -2,7 +2,6 @@
from nw.gui.about import GuiAbout
from nw.gui.build import GuiBuildNovel
from nw.gui.docbars import GuiSearchBar
from nw.gui.doceditor import GuiDocEditor
from nw.gui.docmerge import GuiDocMerge
from nw.gui.docsplit import GuiDocSplit
@@ -23,7 +22,6 @@ from nw.gui.theme import GuiIcons, GuiTheme
__all__ = [
"GuiAbout",
"GuiBuildNovel",
"GuiSearchBar",
"GuiDocEditor",
"GuiDocMerge",
"GuiDocSplit",
+6 -4
View File
@@ -459,7 +459,7 @@ class GuiBuildNovel(QDialog):
makeHtml.setStyles(not noStyling)
# Make sure the tree order is correct
self.theParent.treeView.saveTreeOrder()
self.theParent.treeView.flushTreeOrder()
self.buildProgress.setMaximum(len(self.theProject.projTree))
self.buildProgress.setValue(0)
@@ -871,8 +871,8 @@ class GuiBuildNovel(QDialog):
"section" : self.fmtSection.text().strip(),
})
winWidth = self.mainConf.pxInt(self.width())
winHeight = self.mainConf.pxInt(self.height())
winWidth = self.mainConf.rpxInt(self.width())
winHeight = self.mainConf.rpxInt(self.height())
justifyText = self.justifyText.isChecked()
noStyling = self.noStyling.isChecked()
textFont = self.textFont.text()
@@ -982,7 +982,9 @@ class GuiBuildNovelDocView(QTextBrowser):
"""
if isinstance(theText, list):
theText = "".join(theText)
theText = theText.replace("&emsp;","&nbsp;"*4)
theText = theText.replace("&emsp;", "&nbsp;"*4)
theText = theText.replace("<del>", "<span style='text-decoration: line-through;'>")
theText = theText.replace("</del>", "</span>")
self.setHtml(theText)
return
+100 -4
View File
@@ -30,17 +30,17 @@
import logging
import nw
from PyQt5.QtGui import QColor, QPalette, QPainter
from PyQt5.QtGui import QColor, QPalette, QPainter, QFontMetrics
from PyQt5.QtCore import (
Qt, QRect, QPoint, QSize, QRectF, QPropertyAnimation, pyqtProperty
)
from PyQt5.QtWidgets import (
QGridLayout, QLabel, QWidget, QVBoxLayout, QHBoxLayout, QSizePolicy,
QAbstractButton, QDialog, QTabWidget, QTabBar, QStyle,
QStylePainter, QStyleOptionTab
QAbstractButton, QDialog, QTabWidget, QTabBar, QStyle, QDialogButtonBox,
QStylePainter, QStyleOptionTab, QListWidget, QListWidgetItem, QFrame
)
from nw.constants import nwUnicode
from nw.constants import nwUnicode, nwQuotes
logger = logging.getLogger(__name__)
@@ -436,3 +436,99 @@ class VerticalTabBar(QTabBar):
return
# END Class VerticalTabBar
# =============================================================================================== #
# Quotes Dialog
# =============================================================================================== #
class QuotesDialog(QDialog):
def __init__(self, theParent=None, currentQuote="\""):
QDialog.__init__(self, parent=theParent)
self.mainConf = nw.CONFIG
self.outerBox = QVBoxLayout()
self.innerBox = QHBoxLayout()
self.labelBox = QVBoxLayout()
self.selectedQuote = currentQuote
qMetrics = QFontMetrics(self.font())
pxW = 7*qMetrics.boundingRectChar("M").width()
pxH = 7*qMetrics.boundingRectChar("M").height()
pxH = 7*qMetrics.boundingRectChar("M").height()
lblFont = self.font()
lblFont.setPointSizeF(4*lblFont.pointSizeF())
# Preview Label
self.previewLabel = QLabel(currentQuote)
self.previewLabel.setFont(lblFont)
self.previewLabel.setFixedSize(QSize(pxW, pxH))
self.previewLabel.setAlignment(Qt.AlignCenter)
self.previewLabel.setFrameStyle(QFrame.Box | QFrame.Plain)
# Quote Symbols
self.listBox = QListWidget()
self.listBox.itemSelectionChanged.connect(self._selectedSymbol)
minSize = 100
for sKey, sLabel in nwQuotes.SYMBOLS.items():
theText = "[ %s ] %s" % (sKey, sLabel)
minSize = max(minSize, qMetrics.boundingRect(theText).width())
qtItem = QListWidgetItem(theText)
qtItem.setData(Qt.UserRole, sKey)
self.listBox.addItem(qtItem)
if sKey == currentQuote:
self.listBox.setCurrentItem(qtItem)
self.listBox.setMinimumWidth(minSize + self.mainConf.pxInt(40))
self.listBox.setMinimumHeight(self.mainConf.pxInt(150))
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doAccept)
self.buttonBox.rejected.connect(self._doReject)
# Assemble
self.labelBox.addWidget(self.previewLabel, 0, Qt.AlignTop)
self.labelBox.addStretch(1)
self.innerBox.addLayout(self.labelBox)
self.innerBox.addWidget(self.listBox)
self.outerBox.addLayout(self.innerBox)
self.outerBox.addWidget(self.buttonBox)
self.setLayout(self.outerBox)
return
##
# Slots
##
def _selectedSymbol(self):
"""Update the preview label and the selected quote style.
"""
selItems = self.listBox.selectedItems()
if selItems:
theSymbol = selItems[0].data(Qt.UserRole)
self.previewLabel.setText(theSymbol)
self.selectedQuote = theSymbol
return
def _doAccept(self):
"""Ok button clicked.
"""
self.accept()
return
def _doReject(self):
"""Cancel button clicked.
"""
self.reject()
return
# END Class QuotesDialog
-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
+693 -130
View File
File diff suppressed because it is too large Load Diff
+52 -44
View File
@@ -33,7 +33,7 @@ from PyQt5.QtGui import (
QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QBrush
)
from nw.constants import nwUnicode
from nw.constants import nwUnicode, nwRegEx
logger = logging.getLogger(__name__)
@@ -78,7 +78,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
"""Initialise the syntax highlighter, setting all the colour
rules and building the regexes.
"""
logger.debug("Setting up highlighting rules")
self.colHead = QColor(*self.theTheme.colHead)
@@ -98,28 +97,28 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.colTrail.setAlpha(64)
self.hStyles = {
"header1" : self._makeFormat(self.colHead, "bold",1.8),
"header2" : self._makeFormat(self.colHead, "bold",1.6),
"header3" : self._makeFormat(self.colHead, "bold",1.4),
"header4" : self._makeFormat(self.colHead, "bold",1.2),
"header1h" : self._makeFormat(self.colHeadH,"bold",1.8),
"header2h" : self._makeFormat(self.colHeadH,"bold",1.6),
"header3h" : self._makeFormat(self.colHeadH,"bold",1.4),
"header4h" : self._makeFormat(self.colHeadH,"bold",1.2),
"bold" : self._makeFormat(self.colEmph, "bold"),
"italic" : self._makeFormat(self.colEmph, "italic"),
"strike" : self._makeFormat(self.colEmph, "strike"),
"underline" : self._makeFormat(self.colEmph, "underline"),
"trailing" : self._makeFormat(self.colTrail,"background"),
"nobreak" : self._makeFormat(self.colTrail,"background"),
"dialogue1" : self._makeFormat(self.colDialN),
"dialogue2" : self._makeFormat(self.colDialD),
"dialogue3" : self._makeFormat(self.colDialS),
"replace" : self._makeFormat(self.colRepTag),
"hidden" : self._makeFormat(self.colComm),
"keyword" : self._makeFormat(self.colKey),
"modifier" : self._makeFormat(self.colMod),
"value" : self._makeFormat(self.colVal),
"header1" : self._makeFormat(self.colHead, "bold", 1.8),
"header2" : self._makeFormat(self.colHead, "bold", 1.6),
"header3" : self._makeFormat(self.colHead, "bold", 1.4),
"header4" : self._makeFormat(self.colHead, "bold", 1.2),
"header1h" : self._makeFormat(self.colHeadH, "bold", 1.8),
"header2h" : self._makeFormat(self.colHeadH, "bold", 1.6),
"header3h" : self._makeFormat(self.colHeadH, "bold", 1.4),
"header4h" : self._makeFormat(self.colHeadH, "bold", 1.2),
"bold" : self._makeFormat(self.colEmph, "bold"),
"italic" : self._makeFormat(self.colEmph, "italic"),
"bolditalic" : self._makeFormat(self.colEmph, ("bold","italic")),
"strike" : self._makeFormat(self.colEmph, "strike"),
"trailing" : self._makeFormat(self.colTrail, "background"),
"nobreak" : self._makeFormat(self.colTrail, "background"),
"dialogue1" : self._makeFormat(self.colDialN),
"dialogue2" : self._makeFormat(self.colDialD),
"dialogue3" : self._makeFormat(self.colDialS),
"replace" : self._makeFormat(self.colRepTag),
"hidden" : self._makeFormat(self.colComm),
"keyword" : self._makeFormat(self.colKey),
"modifier" : self._makeFormat(self.colMod),
"value" : self._makeFormat(self.colVal, "underline"),
}
self.hRules = []
@@ -131,32 +130,39 @@ class GuiDocHighlighter(QSyntaxHighlighter):
}
))
# Non-breaking Space
# Non-Breaking Spaces
self.hRules.append((
"[%s]+" % nwUnicode.U_NBSP, {
"[%s%s]+" % (nwUnicode.U_NBSP, nwUnicode.U_THNBSP), {
0 : self.hStyles["nobreak"],
}
))
# Markdown
self.hRules.append((
r"(?<![\w|\\])([\*]{2})(?!\s)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)", {
1 : self.hStyles["hidden"],
2 : self.hStyles["bold"],
3 : self.hStyles["hidden"],
}
))
self.hRules.append((
r"(?<![\w|_|\\])([_])(?!\s|\1)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)", {
nwRegEx.FMT_I, {
1 : self.hStyles["hidden"],
2 : self.hStyles["italic"],
3 : self.hStyles["hidden"],
}
))
self.hRules.append((
r"(?<![\w|\\])([_]{2})(?!\s)(?m:(.+?))(?<![\s|\\])(\1)(?!\w)", {
nwRegEx.FMT_B, {
1 : self.hStyles["hidden"],
2 : self.hStyles["underline"],
2 : self.hStyles["bold"],
3 : self.hStyles["hidden"],
}
))
self.hRules.append((
nwRegEx.FMT_BI, {
1 : self.hStyles["hidden"],
2 : self.hStyles["bolditalic"],
3 : self.hStyles["hidden"],
}
))
self.hRules.append((
nwRegEx.FMT_ST, {
1 : self.hStyles["hidden"],
2 : self.hStyles["strike"],
3 : self.hStyles["hidden"],
}
))
@@ -196,9 +202,10 @@ class GuiDocHighlighter(QSyntaxHighlighter):
# Build a QRegExp for spell checker
# Include additional characters that the highlighter should
# consider to be word separators
wordSep = "_+"
wordSep = r"_\+"
wordSep += nwUnicode.U_ENDASH
wordSep += nwUnicode.U_EMDASH
self.spellRx = QRegularExpression("\\b[^\\s%s]+\\b" % wordSep)
self.spellRx = QRegularExpression(r"\b[^\s"+wordSep+r"]+\b")
self.spellRx.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
return True
@@ -233,7 +240,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
def highlightBlock(self, theText):
"""Highlight a single block. Prefer to check first character for
all formats that are defined by their initial characters. This
is significantly faster than running the regex checks we use for
is significantly faster than running the regex checks used for
text paragraphs.
"""
if self.theHandle is None or not theText:
@@ -244,9 +251,9 @@ class GuiDocHighlighter(QSyntaxHighlighter):
isValid, theBits, thePos = self.theIndex.scanThis(theText)
isGood = self.theIndex.checkThese(theBits, tItem)
if isValid:
for n in range(len(theBits)):
for n, theBit in enumerate(theBits):
xPos = thePos[n]
xLen = len(theBits[n])
xLen = len(theBit)
if isGood[n]:
if n == 0:
self.setFormat(xPos, xLen, self.hStyles["keyword"])
@@ -279,11 +286,12 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.setFormat(4, len(theText), self.hStyles["header4"])
elif theText.startswith("%"): # Comments
toCheck = theText[1:].lstrip().lower()
toCheck = theText[1:].lstrip()
synTag = toCheck[:9].lower()
tLen = len(theText)
cLen = len(toCheck)
cOff = tLen - cLen
if toCheck.startswith("synopsis:"):
if synTag == "synopsis:":
self.setFormat(0, cOff+9, self.hStyles["modifier"])
self.setFormat(cOff+9, tLen, self.hStyles["hidden"])
else:
@@ -341,7 +349,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
if "underline" in fmtStyle:
theFormat.setFontUnderline(True)
if "background" in fmtStyle:
theFormat.setBackground(QBrush(fmtCol,Qt.SolidPattern))
theFormat.setBackground(QBrush(fmtCol, Qt.SolidPattern))
if fmtSize is not None:
theFormat.setFontPointSize(round(fmtSize*self.mainConf.textSize))
+18 -22
View File
@@ -60,6 +60,7 @@ class GuiDocViewer(QTextBrowser):
self.qDocument = self.document()
self.setMinimumWidth(self.mainConf.pxInt(300))
self.setAutoFillBackground(True)
self.setOpenExternalLinks(False)
self.initViewer()
@@ -104,11 +105,12 @@ class GuiDocViewer(QTextBrowser):
self.setFont(theFont)
docPalette = self.palette()
docPalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack))
docPalette.setColor(QPalette.Base, QColor(*self.theTheme.colBack))
docPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
self.setPalette(docPalette)
self.qDocument.setDocumentMargin(self.mainConf.getTextMargin())
self.qDocument.setDocumentMargin(0)
theOpt = QTextOption()
if self.mainConf.doJustify:
theOpt.setAlignment(Qt.AlignJustify)
@@ -236,24 +238,16 @@ class GuiDocViewer(QTextBrowser):
else:
sW = 0
cM = self.mainConf.getTextMargin()
tB = self.frameWidth()
tW = self.width() - 2*tB - sW
tH = self.docHeader.height()
fH = self.docFooter.height()
fY = self.height() - fH - tB
tT = self.mainConf.getTextMargin() - tH
bT = self.mainConf.getTextMargin() - fH
self.docHeader.setGeometry(tB, tB, tW, tH)
self.docFooter.setGeometry(tB, fY, tW, fH)
self.setViewportMargins(0, tH, 0, fH)
docFormat = self.qDocument.rootFrame().frameFormat()
docFormat.setTopMargin(max(0, tT))
docFormat.setBottomMargin(max(0, bT))
self.qDocument.blockSignals(True)
self.qDocument.rootFrame().setFrameFormat(docFormat)
self.qDocument.blockSignals(False)
self.setViewportMargins(cM, max(cM, tH), cM, max(cM, fH))
return
@@ -535,6 +529,8 @@ class GuiDocViewHeader(QWidget):
def _refreshDocument(self):
"""Reload the content of the document.
"""
if self.docViewer.theHandle == self.theParent.docEditor.theHandle:
self.theParent.saveDocument()
self.docViewer.reloadText()
return
@@ -546,7 +542,7 @@ class GuiDocViewHeader(QWidget):
"""Capture a click on the title and ensure that the item is
selected in the project tree.
"""
self.theParent.treeView.setSelectedHandle(self.theHandle)
self.theParent.treeView.setSelectedHandle(self.theHandle, doScroll=True)
return
# END Class GuiDocViewHeader
@@ -558,16 +554,16 @@ class GuiDocViewHeader(QWidget):
class GuiDocViewFooter(QWidget):
def __init__(self, theParent):
QWidget.__init__(self, theParent)
def __init__(self, docViewer):
QWidget.__init__(self, docViewer)
logger.debug("Initialising GuiDocViewFooter ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.optState = theParent.theProject.optState
self.viewMeta = theParent.theParent.viewMeta
self.docViewer = docViewer
self.theParent = docViewer.theParent
self.theTheme = docViewer.theTheme
self.viewMeta = docViewer.theParent.viewMeta
self.theHandle = None
# Make a QPalette that matches the Syntax Theme
@@ -669,9 +665,9 @@ class GuiDocViewFooter(QWidget):
"""Toggle the sticky flag for the reference panel.
"""
logger.verbose("Reference sticky is %s" % str(theState))
self.theParent.stickyRef = theState
if not theState and self.theParent.theHandle is not None:
self.viewMeta.refreshReferences(self.theParent.theHandle)
self.docViewer.stickyRef = theState
if not theState and self.docViewer.theHandle is not None:
self.viewMeta.refreshReferences(self.docViewer.theHandle)
return
# END Class GuiDocViewFooter
+2 -2
View File
@@ -56,8 +56,8 @@ class GuiItemDetails(QWidget):
self.setLayout(self.mainBox)
self.pS = 0.9*self.theTheme.fontPointSize
self.iPx = self.theTheme.textIconSize
self.sPx = int(round(0.8*self.theTheme.textIconSize))
self.iPx = self.theTheme.baseIconSize
self.sPx = int(round(0.8*self.theTheme.baseIconSize))
self.expCheck = self.theTheme.getPixmap("check", (self.iPx, self.iPx))
self.expCross = self.theTheme.getPixmap("cross", (self.iPx, self.iPx))
+172 -67
View File
@@ -33,7 +33,7 @@ from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QMenuBar, QAction, QMessageBox
from nw.gui.about import GuiAbout
from nw.constants import nwItemType, nwItemClass, nwDocAction
from nw.constants import nwItemType, nwItemClass, nwDocAction, nwDocInsert
logger = logging.getLogger(__name__)
@@ -50,8 +50,9 @@ class GuiMainMenu(QMenuBar):
self._buildProjectMenu()
self._buildDocumentMenu()
self._buildEditMenu()
self._buildViewMenu()
self._buildInsertMenu()
self._buildFormatMenu()
self._buildViewMenu()
self._buildToolsMenu()
self._buildHelpMenu()
@@ -59,6 +60,7 @@ class GuiMainMenu(QMenuBar):
self._docAction = self.theParent.passDocumentAction
self._moveTreeItem = self.theParent.treeView.moveTreeItem
self._newTreeItem = self.theParent.treeView.newTreeItem
self._docInsert = self.theParent.docEditor.insertText
logger.debug("GuiMainMenu initialisation complete")
@@ -97,6 +99,8 @@ class GuiMainMenu(QMenuBar):
##
def _menuExit(self):
"""Exit novelWriter.
"""
self.theParent.closeMain()
return
@@ -123,19 +127,33 @@ class GuiMainMenu(QMenuBar):
return True
def _showAboutQt(self):
"""Show Qt's own About dialog.
"""
msgBox = QMessageBox()
msgBox.aboutQt(self.theParent,"About Qt")
return True
def _openHelp(self):
"""Open the documentation URL in the system's default browser.
"""
QDesktopServices.openUrl(QUrl(nw.__docurl__))
return True
def _openIssue(self):
"""Open the issue tracker URL in the system's default browser.
"""
QDesktopServices.openUrl(QUrl(nw.__issuesurl__))
return True
def _showDocumentLocation(self):
"""Open the dialog showing the location of the editor document.
"""
self.theParent.docEditor.revealLocation()
return True
def _doBackup(self):
"""Call the backup function for the project.
"""
self.theProject.zipIt(True)
return True
@@ -228,14 +246,14 @@ class GuiMainMenu(QMenuBar):
self.projMenu.addSeparator()
# Project > Edit
self.aEditItem = QAction("&Edit Project Item", self)
self.aEditItem = QAction("Edit Project Item", self)
self.aEditItem.setStatusTip("Change item settings")
self.aEditItem.setShortcuts(["Ctrl+E", "F2"])
self.aEditItem.triggered.connect(self.theParent.editItem)
self.projMenu.addAction(self.aEditItem)
# Project > Delete
self.aDeleteItem = QAction("&Delete Project Item", self)
self.aDeleteItem = QAction("Delete Project Item", self)
self.aDeleteItem.setStatusTip("Delete selected item")
self.aDeleteItem.setShortcut("Ctrl+Del")
self.aDeleteItem.triggered.connect(lambda : self.theParent.treeView.deleteItem(None))
@@ -265,21 +283,21 @@ class GuiMainMenu(QMenuBar):
self.docuMenu = self.addMenu("&Document")
# Document > New
self.aNewDoc = QAction("&New Document", self)
self.aNewDoc = QAction("New Document", self)
self.aNewDoc.setStatusTip("Create new document")
self.aNewDoc.setShortcut("Ctrl+N")
self.aNewDoc.triggered.connect(lambda : self._newTreeItem(nwItemType.FILE, None))
self.docuMenu.addAction(self.aNewDoc)
# Document > Open
self.aOpenDoc = QAction("&Open Document", self)
self.aOpenDoc = QAction("Open Document", self)
self.aOpenDoc.setStatusTip("Open selected document")
self.aOpenDoc.setShortcut("Ctrl+O")
self.aOpenDoc.triggered.connect(self.theParent.openSelectedItem)
self.docuMenu.addAction(self.aOpenDoc)
# Document > Save
self.aSaveDoc = QAction("&Save Document", self)
self.aSaveDoc = QAction("Save Document", self)
self.aSaveDoc.setStatusTip("Save current document")
self.aSaveDoc.setShortcut("Ctrl+S")
self.aSaveDoc.triggered.connect(self.theParent.saveDocument)
@@ -341,53 +359,6 @@ class GuiMainMenu(QMenuBar):
return
def _buildViewMenu(self):
# View
self.viewMenu = self.addMenu("&View")
# View > TreeView
self.aFocusTree = QAction("Focus Project Tree", self)
self.aFocusTree.setStatusTip("Move focus to project tree")
self.aFocusTree.setShortcut("Alt+1")
self.aFocusTree.triggered.connect(lambda : self.theParent.setFocus(1))
self.viewMenu.addAction(self.aFocusTree)
# View > Document Pane 1
self.aFocusEditor = QAction("Focus Document Editor", self)
self.aFocusEditor.setStatusTip("Move focus to left document pane")
self.aFocusEditor.setShortcut("Alt+2")
self.aFocusEditor.triggered.connect(lambda : self.theParent.setFocus(2))
self.viewMenu.addAction(self.aFocusEditor)
# View > Document Pane 2
self.aFocusView = QAction("Focus Document Viewer", self)
self.aFocusView.setStatusTip("Move focus to right document pane")
self.aFocusView.setShortcut("Alt+3")
self.aFocusView.triggered.connect(lambda : self.theParent.setFocus(3))
self.viewMenu.addAction(self.aFocusView)
# View > Separator
self.viewMenu.addSeparator()
# View > Toggle Distraction Free Mode
self.aZenMode = QAction("Zen Mode", self)
self.aZenMode.setStatusTip("Toggles distraction free mode, only showing text editor")
self.aZenMode.setShortcut("F8")
self.aZenMode.setCheckable(True)
self.aZenMode.setChecked(self.theParent.isZenMode)
self.aZenMode.toggled.connect(self.theParent.toggleZenMode)
self.viewMenu.addAction(self.aZenMode)
# View > Toggle Full Screen
self.aFullScreen = QAction("Full Screen Mode", self)
self.aFullScreen.setStatusTip("Maximises the main window")
self.aFullScreen.setShortcut("F11")
self.aFullScreen.triggered.connect(self.theParent.toggleFullScreenMode)
self.viewMenu.addAction(self.aFullScreen)
return
def _buildEditMenu(self):
# Edit
@@ -497,18 +468,70 @@ class GuiMainMenu(QMenuBar):
return
def _buildInsertMenu(self):
# Insert
self.insertMenu = self.addMenu("&Insert")
# Insert > Short Dash
self.aInsENDash = QAction("Short Dash", self)
self.aInsENDash.setStatusTip("Insert short dash")
self.aInsENDash.setShortcut("Ctrl+K, -")
self.aInsENDash.triggered.connect(lambda: self._docInsert(nwDocInsert.SHORT_DASH))
self.insertMenu.addAction(self.aInsENDash)
# Insert > Long Dash
self.aInsEMDash = QAction("Long Dash", self)
self.aInsEMDash.setStatusTip("Insert long dash")
self.aInsEMDash.setShortcut("Ctrl+K, _")
self.aInsEMDash.triggered.connect(lambda: self._docInsert(nwDocInsert.LONG_DASH))
self.insertMenu.addAction(self.aInsEMDash)
# Insert > Ellipsis
self.aInsEllipsis = QAction("Ellipsis", self)
self.aInsEllipsis.setStatusTip("Insert ellipsis")
self.aInsEllipsis.setShortcut("Ctrl+K, .")
self.aInsEllipsis.triggered.connect(lambda: self._docInsert(nwDocInsert.ELLIPSIS))
self.insertMenu.addAction(self.aInsEllipsis)
# Insert > Separator
self.insertMenu.addSeparator()
# Insert > Hard Line Break
self.aInsHardBreak = QAction("Hard Line Break", self)
self.aInsHardBreak.setStatusTip("Insert a hard line break")
self.aInsHardBreak.setShortcut("Ctrl+K, Return")
self.aInsHardBreak.triggered.connect(lambda: self._docInsert(nwDocInsert.HARD_BREAK))
self.insertMenu.addAction(self.aInsHardBreak)
# Insert > Non-Breaking Space
self.aInsNBSpace = QAction("Non-Breaking Space", self)
self.aInsNBSpace.setStatusTip("Insert a non-breaking space")
self.aInsNBSpace.setShortcut("Ctrl+K, Space")
self.aInsNBSpace.triggered.connect(lambda: self._docInsert(nwDocInsert.NB_SPACE))
self.insertMenu.addAction(self.aInsNBSpace)
# Insert > Thin Space
self.aInsThinSpace = QAction("Thin Space", self)
self.aInsThinSpace.setStatusTip("Insert a thin space")
self.aInsThinSpace.setShortcut("Ctrl+K, Shift+Space")
self.aInsThinSpace.triggered.connect(lambda: self._docInsert(nwDocInsert.THIN_SPACE))
self.insertMenu.addAction(self.aInsThinSpace)
# Insert > Thin Non-Breaking Space
self.aInsThinNBSpace = QAction("Thin Non-Breaking Space", self)
self.aInsThinNBSpace.setStatusTip("Insert a thin non-breaking space")
self.aInsThinNBSpace.setShortcut("Ctrl+K, Ctrl+Space")
self.aInsThinNBSpace.triggered.connect(lambda: self._docInsert(nwDocInsert.THIN_NB_SPACE))
self.insertMenu.addAction(self.aInsThinNBSpace)
return
def _buildFormatMenu(self):
# Format
self.fmtMenu = self.addMenu("&Format")
# Format > Bold Text
self.aFmtBold = QAction("Bold Text", self)
self.aFmtBold.setStatusTip("Make selected text bold")
self.aFmtBold.setShortcut("Ctrl+B")
self.aFmtBold.triggered.connect(lambda: self._docAction(nwDocAction.BOLD))
self.fmtMenu.addAction(self.aFmtBold)
# Format > Italic Text
self.aFmtItalic = QAction("Italic Text", self)
self.aFmtItalic.setStatusTip("Make selected text italic")
@@ -516,12 +539,26 @@ class GuiMainMenu(QMenuBar):
self.aFmtItalic.triggered.connect(lambda: self._docAction(nwDocAction.ITALIC))
self.fmtMenu.addAction(self.aFmtItalic)
# Format > Bold Text
self.aFmtBold = QAction("Bold Text", self)
self.aFmtBold.setStatusTip("Make selected text bold")
self.aFmtBold.setShortcut("Ctrl+B")
self.aFmtBold.triggered.connect(lambda: self._docAction(nwDocAction.BOLD))
self.fmtMenu.addAction(self.aFmtBold)
# Format > Underline Text
self.aFmtULine = QAction("Underline Text", self)
self.aFmtULine.setStatusTip("Underline selected text")
self.aFmtULine.setShortcut("Ctrl+U")
self.aFmtULine.triggered.connect(lambda: self._docAction(nwDocAction.U_LINE))
self.fmtMenu.addAction(self.aFmtULine)
self.aFmtBoldIt = QAction("Bold Italic Text", self)
self.aFmtBoldIt.setStatusTip("Make selected text bold and italic")
self.aFmtBoldIt.setShortcut("Ctrl+Shift+B")
self.aFmtBoldIt.triggered.connect(lambda: self._docAction(nwDocAction.BOLDITALIC))
self.fmtMenu.addAction(self.aFmtBoldIt)
# Format > Strikethrough
self.aFmtStrike = QAction("Strikethrough Text", self)
self.aFmtStrike.setStatusTip("Strikethrough selected text")
self.aFmtStrike.setShortcut("Ctrl+-")
self.aFmtStrike.triggered.connect(lambda: self._docAction(nwDocAction.STRIKE))
self.fmtMenu.addAction(self.aFmtStrike)
# Edit > Separator
self.fmtMenu.addSeparator()
@@ -540,7 +577,7 @@ class GuiMainMenu(QMenuBar):
self.aFmtSQuote.triggered.connect(lambda: self._docAction(nwDocAction.S_QUOTE))
self.fmtMenu.addAction(self.aFmtSQuote)
# Edit > Separator
# Format > Separator
self.fmtMenu.addSeparator()
# Format > Header 1
@@ -585,6 +622,68 @@ class GuiMainMenu(QMenuBar):
self.aFmtNoFormat.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TXT))
self.fmtMenu.addAction(self.aFmtNoFormat)
# Format > Separator
self.fmtMenu.addSeparator()
# Format > Replace Single Quotes
self.aFmtReplSng = QAction("Replace Single Quotes", self)
self.aFmtReplSng.setStatusTip("Replace all straight single quotes in selected text")
self.aFmtReplSng.triggered.connect(lambda: self._docAction(nwDocAction.REPL_SNG))
self.fmtMenu.addAction(self.aFmtReplSng)
# Format > Replace Double Quotes
self.aFmtReplDbl = QAction("Replace Double Quotes", self)
self.aFmtReplDbl.setStatusTip("Replace all straight double quotes in selected text")
self.aFmtReplDbl.triggered.connect(lambda: self._docAction(nwDocAction.REPL_DBL))
self.fmtMenu.addAction(self.aFmtReplDbl)
return
def _buildViewMenu(self):
# View
self.viewMenu = self.addMenu("&View")
# View > TreeView
self.aFocusTree = QAction("Focus Project Tree", self)
self.aFocusTree.setStatusTip("Move focus to project tree")
self.aFocusTree.setShortcut("Alt+1")
self.aFocusTree.triggered.connect(lambda : self.theParent.setFocus(1))
self.viewMenu.addAction(self.aFocusTree)
# View > Document Pane 1
self.aFocusEditor = QAction("Focus Document Editor", self)
self.aFocusEditor.setStatusTip("Move focus to left document pane")
self.aFocusEditor.setShortcut("Alt+2")
self.aFocusEditor.triggered.connect(lambda : self.theParent.setFocus(2))
self.viewMenu.addAction(self.aFocusEditor)
# View > Document Pane 2
self.aFocusView = QAction("Focus Document Viewer", self)
self.aFocusView.setStatusTip("Move focus to right document pane")
self.aFocusView.setShortcut("Alt+3")
self.aFocusView.triggered.connect(lambda : self.theParent.setFocus(3))
self.viewMenu.addAction(self.aFocusView)
# View > Separator
self.viewMenu.addSeparator()
# View > Toggle Distraction Free Mode
self.aZenMode = QAction("Zen Mode", self)
self.aZenMode.setStatusTip("Toggles distraction free mode, only showing text editor")
self.aZenMode.setShortcut("F8")
self.aZenMode.setCheckable(True)
self.aZenMode.setChecked(self.theParent.isZenMode)
self.aZenMode.toggled.connect(self.theParent.toggleZenMode)
self.viewMenu.addAction(self.aZenMode)
# View > Toggle Full Screen
self.aFullScreen = QAction("Full Screen Mode", self)
self.aFullScreen.setStatusTip("Maximises the main window")
self.aFullScreen.setShortcut("F11")
self.aFullScreen.triggered.connect(self.theParent.toggleFullScreenMode)
self.viewMenu.addAction(self.aFullScreen)
return
def _buildToolsMenu(self):
@@ -696,6 +795,12 @@ class GuiMainMenu(QMenuBar):
self.aHelp.triggered.connect(self._openHelp)
self.helpMenu.addAction(self.aHelp)
# Document > Report Issue
self.aIssue = QAction("Report an Issue", self)
self.aIssue.setStatusTip("View online documentation")
self.aIssue.triggered.connect(self._openIssue)
self.helpMenu.addAction(self.aIssue)
return
# END Class GuiMainMenu
+1 -1
View File
@@ -102,7 +102,7 @@ class GuiOutline(QTreeWidget):
self.itemDoubleClicked.connect(self._treeDoubleClick)
self.itemSelectionChanged.connect(self._itemSelected)
iPx = self.theTheme.textIconSize
iPx = self.theTheme.baseIconSize
self.setIconSize(QSize(iPx, iPx))
self.setIndentation(iPx)
+63 -37
View File
@@ -37,9 +37,9 @@ from PyQt5.QtWidgets import (
QDialogButtonBox, QFileDialog, QFontDialog
)
from nw.gui.custom import QSwitch, QConfigLayout, PagedDialog
from nw.gui.custom import QSwitch, QConfigLayout, PagedDialog, QuotesDialog
from nw.core import NWSpellCheck, NWSpellSimple, NWSpellEnchant
from nw.constants import nwAlert, nwQuotes
from nw.constants import nwQuotes
logger = logging.getLogger(__name__)
@@ -816,51 +816,72 @@ class GuiConfigEditAutoReplaceTab(QWidget):
self.mainForm.addGroupLabel("Quotation Style")
qWidth = self.mainConf.pxInt(40)
bWidth = int(2.5*self.theTheme.getTextWidth("..."))
## Single Quote Style
self.quoteSingleStyleO = QLineEdit()
self.quoteSingleStyleO.setMaxLength(1)
self.quoteSingleStyleO.setReadOnly(True)
self.quoteSingleStyleO.setFixedWidth(qWidth)
self.quoteSingleStyleO.setAlignment(Qt.AlignCenter)
self.quoteSingleStyleO.setText(self.mainConf.fmtSingleQuotes[0])
self.btnSingleStyleO = QPushButton("...")
self.btnSingleStyleO.setMaximumWidth(bWidth)
self.btnSingleStyleO.clicked.connect(self._getSingleOpen)
self.mainForm.addRow(
"Single quote open style",
self.quoteSingleStyleO,
"Auto-replaces apostrophe before words."
"Auto-replaces apostrophe before words.",
theButton=self.btnSingleStyleO
)
self.quoteSingleStyleC = QLineEdit()
self.quoteSingleStyleC.setMaxLength(1)
self.quoteSingleStyleC.setReadOnly(True)
self.quoteSingleStyleC.setFixedWidth(qWidth)
self.quoteSingleStyleC.setAlignment(Qt.AlignCenter)
self.quoteSingleStyleC.setText(self.mainConf.fmtSingleQuotes[1])
self.btnSingleStyleC = QPushButton("...")
self.btnSingleStyleC.setMaximumWidth(bWidth)
self.btnSingleStyleC.clicked.connect(self._getSingleClose)
self.mainForm.addRow(
"Single quote close style",
self.quoteSingleStyleC,
"Auto-replaces apostrophe after words."
"Auto-replaces apostrophe after words.",
theButton=self.btnSingleStyleC
)
## Double Quote Style
self.quoteDoubleStyleO = QLineEdit()
self.quoteDoubleStyleO.setMaxLength(1)
self.quoteDoubleStyleO.setReadOnly(True)
self.quoteDoubleStyleO.setFixedWidth(qWidth)
self.quoteDoubleStyleO.setAlignment(Qt.AlignCenter)
self.quoteDoubleStyleO.setText(self.mainConf.fmtDoubleQuotes[0])
self.btnDoubleStyleO = QPushButton("...")
self.btnDoubleStyleO.setMaximumWidth(bWidth)
self.btnDoubleStyleO.clicked.connect(self._getDoubleOpen)
self.mainForm.addRow(
"Double quote open style",
self.quoteDoubleStyleO,
"Auto-replaces straight quotes before words."
"Auto-replaces straight quotes before words.",
theButton=self.btnDoubleStyleO
)
self.quoteDoubleStyleC = QLineEdit()
self.quoteDoubleStyleC.setMaxLength(1)
self.quoteDoubleStyleC.setReadOnly(True)
self.quoteDoubleStyleC.setFixedWidth(qWidth)
self.quoteDoubleStyleC.setAlignment(Qt.AlignCenter)
self.quoteDoubleStyleC.setText(self.mainConf.fmtDoubleQuotes[1])
self.btnDoubleStyleC = QPushButton("...")
self.btnDoubleStyleC.setMaximumWidth(bWidth)
self.btnDoubleStyleC.clicked.connect(self._getDoubleClose)
self.mainForm.addRow(
"Double quote close style",
self.quoteDoubleStyleC,
"Auto-replaces straight quotes after words."
"Auto-replaces straight quotes after words.",
theButton=self.btnDoubleStyleC
)
return
@@ -889,37 +910,10 @@ class GuiConfigEditAutoReplaceTab(QWidget):
fmtDoubleQuotesO = self.quoteDoubleStyleO.text()
fmtDoubleQuotesC = self.quoteDoubleStyleC.text()
if self._checkQuoteSymbol(fmtSingleQuotesO):
self.mainConf.fmtSingleQuotes[0] = fmtSingleQuotesO
else:
self.theParent.makeAlert(
"Invalid quote symbol: %s" % fmtSingleQuotesO, nwAlert.ERROR
)
validEntries = False
if self._checkQuoteSymbol(fmtSingleQuotesC):
self.mainConf.fmtSingleQuotes[1] = fmtSingleQuotesC
else:
self.theParent.makeAlert(
"Invalid quote symbol: %s" % fmtSingleQuotesC, nwAlert.ERROR
)
validEntries = False
if self._checkQuoteSymbol(fmtDoubleQuotesO):
self.mainConf.fmtDoubleQuotes[0] = fmtDoubleQuotesO
else:
self.theParent.makeAlert(
"Invalid quote symbol: %s" % fmtDoubleQuotesO, nwAlert.ERROR
)
validEntries = False
if self._checkQuoteSymbol(fmtDoubleQuotesC):
self.mainConf.fmtDoubleQuotes[1] = fmtDoubleQuotesC
else:
self.theParent.makeAlert(
"Invalid quote symbol: %s" % fmtDoubleQuotesC, nwAlert.ERROR
)
validEntries = False
self.mainConf.fmtSingleQuotes[0] = fmtSingleQuotesO
self.mainConf.fmtSingleQuotes[1] = fmtSingleQuotesC
self.mainConf.fmtDoubleQuotes[0] = fmtDoubleQuotesO
self.mainConf.fmtDoubleQuotes[1] = fmtDoubleQuotesC
self.mainConf.confChanged = True
@@ -939,6 +933,38 @@ class GuiConfigEditAutoReplaceTab(QWidget):
self.autoReplaceDots.setEnabled(theState)
return
def _getSingleOpen(self):
"""Dialog for single quote open.
"""
qtBox = QuotesDialog(self, currentQuote=self.quoteSingleStyleO.text())
if qtBox.exec_() == QDialog.Accepted:
self.quoteSingleStyleO.setText(qtBox.selectedQuote)
return
def _getSingleClose(self):
"""Dialog for single quote close.
"""
qtBox = QuotesDialog(self, currentQuote=self.quoteSingleStyleC.text())
if qtBox.exec_() == QDialog.Accepted:
self.quoteSingleStyleC.setText(qtBox.selectedQuote)
return
def _getDoubleOpen(self):
"""Dialog for double quote open.
"""
qtBox = QuotesDialog(self, currentQuote=self.quoteDoubleStyleO.text())
if qtBox.exec_() == QDialog.Accepted:
self.quoteDoubleStyleO.setText(qtBox.selectedQuote)
return
def _getDoubleClose(self):
"""Dialog for double quote close.
"""
qtBox = QuotesDialog(self, currentQuote=self.quoteDoubleStyleC.text())
if qtBox.exec_() == QDialog.Accepted:
self.quoteDoubleStyleC.setText(qtBox.selectedQuote)
return
##
# Internal Functions
##
+45 -23
View File
@@ -36,7 +36,7 @@ from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QGridLayout, QPushButton, QTreeWidget,
QAbstractItemView, QTreeWidgetItem, QDialogButtonBox, QLabel, QShortcut,
QFileDialog, QLineEdit
QFileDialog, QLineEdit, QMessageBox
)
from nw.common import formatInt
@@ -50,6 +50,10 @@ class GuiProjectLoad(QDialog):
NEW_STATE = 1
OPEN_STATE = 2
C_NAME = 0
C_COUNT = 1
C_TIME = 2
def __init__(self, theParent):
QDialog.__init__(self, theParent)
@@ -62,7 +66,8 @@ class GuiProjectLoad(QDialog):
self.openPath = None
sPx = self.mainConf.pxInt(16)
iPx = self.mainConf.pxInt(96)
nPx = self.mainConf.pxInt(96)
iPx = self.theTheme.baseIconSize
self.outerBox = QVBoxLayout()
self.innerBox = QHBoxLayout()
@@ -74,13 +79,12 @@ class GuiProjectLoad(QDialog):
self.setMinimumHeight(self.mainConf.pxInt(400))
self.setModal(True)
self.guiDeco = self.theTheme.loadDecoration("nwicon", (iPx, iPx))
self.guiDeco = self.theTheme.loadDecoration("nwicon", (nPx, nPx))
self.innerBox.addWidget(self.guiDeco, 0, Qt.AlignTop)
self.projectForm = QGridLayout()
self.projectForm.setContentsMargins(0, 0, 0, 0)
iPx = self.theTheme.textIconSize
self.listBox = QTreeWidget()
self.listBox.setSelectionMode(QAbstractItemView.SingleSelection)
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
@@ -92,8 +96,8 @@ class GuiProjectLoad(QDialog):
self.listBox.setIconSize(QSize(iPx, iPx))
treeHead = self.listBox.headerItem()
treeHead.setTextAlignment(1, Qt.AlignRight)
treeHead.setTextAlignment(2, Qt.AlignRight)
treeHead.setTextAlignment(self.C_COUNT, Qt.AlignRight)
treeHead.setTextAlignment(self.C_TIME, Qt.AlignRight)
self.lblRecent = QLabel("<b>Recently Opened Projects</b>")
self.lblPath = QLabel("<b>Path</b>")
@@ -150,7 +154,7 @@ class GuiProjectLoad(QDialog):
self._saveDialogState()
selItems = self.listBox.selectedItems()
if selItems:
self.openPath = selItems[0].data(0, Qt.UserRole)
self.openPath = selItems[0].data(self.C_NAME, Qt.UserRole)
self.openState = self.OPEN_STATE
self.accept()
else:
@@ -163,7 +167,7 @@ class GuiProjectLoad(QDialog):
"""
selList = self.listBox.selectedItems()
if selList:
self.selPath.setText(selList[0].data(0, Qt.UserRole))
self.selPath.setText(selList[0].data(self.C_NAME, Qt.UserRole))
return
def _doBrowse(self):
@@ -210,8 +214,23 @@ class GuiProjectLoad(QDialog):
"""
selList = self.listBox.selectedItems()
if selList:
self.mainConf.removeFromRecentCache(selList[0].text(3))
self._populateList()
doRemove = False
if self.mainConf.showGUI:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Remove Entry",
"Remove the selected entry from the recent projects list?"
)
doRemove = (msgRes == QMessageBox.Yes)
else:
doRemove = True
if doRemove:
self.mainConf.removeFromRecentCache(
selList[0].data(self.C_NAME, Qt.UserRole)
)
self._populateList()
return
##
@@ -221,9 +240,10 @@ class GuiProjectLoad(QDialog):
def _saveDialogState(self):
"""Save the changes made to the dialog.
"""
colWidths = [50]*3
for i in range(3):
colWidths[i] = self.listBox.columnWidth(i)
colWidths = [0, 0, 0]
colWidths[self.C_NAME] = self.listBox.columnWidth(self.C_NAME)
colWidths[self.C_COUNT] = self.listBox.columnWidth(self.C_COUNT)
colWidths[self.C_TIME] = self.listBox.columnWidth(self.C_TIME)
self.mainConf.setProjColWidths(colWidths)
return
@@ -251,22 +271,24 @@ class GuiProjectLoad(QDialog):
hasSelection = False
for timeStamp in sorted(listOrder, reverse=True):
newItem = QTreeWidgetItem([""]*4)
newItem.setIcon(0, self.theParent.theTheme.getIcon("proj_nwx"))
newItem.setText(0, listData[timeStamp][0])
newItem.setData(0, Qt.UserRole, listData[timeStamp][2])
newItem.setText(1, formatInt(listData[timeStamp][1]))
newItem.setText(2, datetime.fromtimestamp(timeStamp).strftime("%x %X"))
newItem.setTextAlignment(0, Qt.AlignLeft | Qt.AlignVCenter)
newItem.setTextAlignment(1, Qt.AlignRight | Qt.AlignVCenter)
newItem.setTextAlignment(2, Qt.AlignRight | Qt.AlignVCenter)
newItem.setIcon(self.C_NAME, self.theParent.theTheme.getIcon("proj_nwx"))
newItem.setText(self.C_NAME, listData[timeStamp][0])
newItem.setData(self.C_NAME, Qt.UserRole, listData[timeStamp][2])
newItem.setText(self.C_COUNT, formatInt(listData[timeStamp][1]))
newItem.setText(self.C_TIME, datetime.fromtimestamp(timeStamp).strftime("%x %X"))
newItem.setTextAlignment(self.C_NAME, Qt.AlignLeft | Qt.AlignVCenter)
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight | Qt.AlignVCenter)
newItem.setTextAlignment(self.C_TIME, Qt.AlignRight | Qt.AlignVCenter)
self.listBox.addTopLevelItem(newItem)
if not hasSelection:
newItem.setSelected(True)
hasSelection = True
projColWidth = self.mainConf.getProjColWidths()
for i in range(3):
self.listBox.setColumnWidth(i, projColWidth[i])
if len(projColWidth) == 3:
self.listBox.setColumnWidth(self.C_NAME, projColWidth[self.C_NAME])
self.listBox.setColumnWidth(self.C_COUNT, projColWidth[self.C_COUNT])
self.listBox.setColumnWidth(self.C_TIME, projColWidth[self.C_TIME])
return
+40 -12
View File
@@ -31,10 +31,9 @@ import nw
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QGridLayout, QLineEdit, QPlainTextEdit,
QLabel, QWidget, QTabWidget, QDialogButtonBox, QListWidget, QPushButton,
QListWidgetItem, QColorDialog, QAbstractItemView, QTreeWidget, QCheckBox,
QTreeWidgetItem
QHBoxLayout, QVBoxLayout, QGridLayout, QLineEdit, QPlainTextEdit, QLabel,
QWidget, QDialogButtonBox, QListWidget, QPushButton, QListWidgetItem,
QColorDialog, QAbstractItemView, QTreeWidget, QTreeWidgetItem
)
from nw.constants import nwAlert
@@ -52,9 +51,17 @@ class GuiProjectSettings(PagedDialog):
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theProject = theProject
self.optState = theProject.optState
self.theProject.countStatus()
self.setWindowTitle("Project Settings")
self.setMinimumWidth(self.mainConf.pxInt(570))
self.setMinimumHeight(self.mainConf.pxInt(355))
self.resize(
self.mainConf.pxInt(self.optState.getInt("GuiProjectSettings", "winWidth", 570)),
self.mainConf.pxInt(self.optState.getInt("GuiProjectSettings", "winHeight", 355))
)
self.tabMain = GuiProjectEditMain(self.theParent, self.theProject)
self.tabMeta = GuiProjectEditMeta(self.theParent, self.theProject)
@@ -73,13 +80,17 @@ class GuiProjectSettings(PagedDialog):
self.buttonBox.rejected.connect(self._doClose)
self.addControls(self.buttonBox)
self.show()
logger.debug("GuiProjectSettings initialisation complete")
return
##
# Slots
##
def _doSave(self):
"""Save settings and close dialog.
"""
logger.verbose("GuiProjectSettings save button clicked")
projName = self.tabMain.editName.text()
@@ -103,13 +114,23 @@ class GuiProjectSettings(PagedDialog):
newList = self.tabReplace.getNewList()
self.theProject.setAutoReplace(newList)
self.close()
self._doClose()
return
def _doClose(self):
logger.verbose("GuiProjectSettings close button clicked")
"""Close the dialog.
"""
winWidth = self.mainConf.rpxInt(self.width())
winHeight = self.mainConf.rpxInt(self.height())
replaceColW = self.mainConf.rpxInt(self.tabReplace.listBox.columnWidth(0))
self.optState.setValue("GuiProjectSettings", "winWidth", winWidth)
self.optState.setValue("GuiProjectSettings", "winHeight", winHeight)
self.optState.setValue("GuiProjectSettings", "replaceColW", replaceColW)
self.close()
return
# END Class GuiProjectSettings
@@ -282,7 +303,7 @@ class GuiProjectEditStatus(QWidget):
self.colChanged = False
self.selColour = None
self.iPx = self.theTheme.textIconSize
self.iPx = self.theTheme.baseIconSize
self.outerBox = QVBoxLayout()
self.mainBox = QHBoxLayout()
@@ -455,16 +476,23 @@ class GuiProjectEditReplace(QWidget):
def __init__(self, theParent, theProject):
QWidget.__init__(self, theParent)
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.theProject = theProject
self.optState = theProject.optState
self.arChanged = False
self.outerBox = QVBoxLayout()
self.bottomBox = QHBoxLayout()
self.listBox = QTreeWidget()
self.outerBox = QVBoxLayout()
self.bottomBox = QHBoxLayout()
wCol0 = self.mainConf.pxInt(
self.optState.getInt("GuiProjectSettings", "replaceColW", 100)
)
self.listBox = QTreeWidget()
self.listBox.setHeaderLabels(["Keyword","Replace With"])
self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setColumnWidth(0, wCol0)
self.listBox.setIndentation(0)
for aKey, aVal in self.theProject.autoReplace.items():
+34 -9
View File
@@ -60,8 +60,9 @@ class GuiProjectTree(QTreeWidget):
self.theProject = theParent.theProject
# Tree Settings
self.theMap = None
self.orphRoot = None
self.theMap = None
self.orphRoot = None
self.treeChanged = False
self.ctxMenu = GuiProjectTreeMenu(self)
self.clearTree()
@@ -256,7 +257,7 @@ class GuiProjectTree(QTreeWidget):
pItem.insertChild(nIndex, cItem)
self.clearSelection()
cItem.setSelected(True)
self.theProject.setProjectChanged(True)
self._setTreeChanged(True)
else:
return False
return True
@@ -276,6 +277,16 @@ class GuiProjectTree(QTreeWidget):
self.theProject.setTreeOrder(theList)
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):
"""Recursively return all the children items starting from a
given item handle.
@@ -332,6 +343,9 @@ class GuiProjectTree(QTreeWidget):
continue
self.deleteItem(tHandle, True)
if nTrash > 0:
self._setTreeChanged(True)
return True
def deleteItem(self, tHandle=None, alreadyAsked=False, askForTrash=False):
@@ -413,7 +427,7 @@ class GuiProjectTree(QTreeWidget):
trItemT.addChild(trItemC)
nwItemS.setParent(self.theProject.projTree.trashRoot())
self.theProject.setProjectChanged(True)
self._setTreeChanged(True)
self.theParent.theIndex.deleteHandle(tHandle)
elif nwItemS.itemType == nwItemType.FOLDER:
@@ -436,7 +450,7 @@ class GuiProjectTree(QTreeWidget):
if trItemS.childCount() == 0:
self.takeTopLevelItem(tIndex)
self.theParent.mainMenu.setAvailableRoot()
self.theProject.setProjectChanged(True)
self._setTreeChanged(True)
else:
self.makeAlert("Cannot delete root folder. It is not empty.", nwAlert.ERROR)
return False
@@ -555,14 +569,14 @@ class GuiProjectTree(QTreeWidget):
selHandles.append(selItems[n].data(self.C_NAME, Qt.UserRole))
return selHandles
def setSelectedHandle(self, tHandle):
def setSelectedHandle(self, tHandle, doScroll=False):
"""Set a specific handle as the selected item.
"""
if tHandle in self.theMap:
self.clearSelection()
self.theMap[tHandle].setSelected(True)
selItems = self.selectedIndexes()
if selItems:
if selItems and doScroll:
self.scrollTo(
selItems[0], QAbstractItemView.PositionAtCenter
)
@@ -732,6 +746,8 @@ class GuiProjectTree(QTreeWidget):
elif nwItem.itemType == nwItemType.TRASH:
newItem.setIcon(self.C_NAME, self.theTheme.getIcon(nwLabels.CLASS_ICON[tClass]))
self._setTreeChanged(True)
return newItem
def _addTrashRoot(self):
@@ -747,6 +763,7 @@ class GuiProjectTree(QTreeWidget):
self.theProject.projTree[trashHandle]
)
trItem.setExpanded(True)
self._setTreeChanged(True)
return trItem
def _addOrphanedRoot(self):
@@ -793,7 +810,7 @@ class GuiProjectTree(QTreeWidget):
nwItemS.setParent(pHandle)
self.propagateCount(tHandle, wC)
self.setTreeItemValues(tHandle)
self.theProject.setProjectChanged(True)
self._setTreeChanged(True)
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)
nwItemS.setParent(pHandle)
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
# END Class GuiProjectTree
+1 -1
View File
@@ -61,7 +61,7 @@ class GuiMainStatus(QStatusBar):
colTrue = QColor(*self.theTheme.statUnsaved)
colFalse = QColor(*self.theTheme.statSaved)
iPx = self.theTheme.textIconSize
iPx = self.theTheme.baseIconSize
# Permanent Widgets
# =================
+43 -39
View File
@@ -143,7 +143,6 @@ class GuiTheme:
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()))
self.textNHeight = qMetric.boundingRect("N").height()
self.textNWidth = qMetric.boundingRect("N").width()
@@ -151,7 +150,6 @@ class GuiTheme:
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)
logger.verbose("Text 'N' Height: %d" % self.textNHeight)
logger.verbose("Text 'N' Width: %d" % self.textNWidth)
@@ -497,52 +495,58 @@ class GuiIcons:
ICON_MAP = {
# Project and GUI icons
"cls_none" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_novel" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_plot" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_character" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_world" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_timeline" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_object" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_entity" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_custom" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_trash" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"proj_document" : (QStyle.SP_FileIcon, "x-office-document"),
"proj_folder" : (QStyle.SP_DirIcon, "folder"),
"proj_orphan" : (QStyle.SP_MessageBoxWarning, "dialog-warning"),
"proj_nwx" : (None, None),
"status_lang" : (None, None),
"status_time" : (None, None),
"status_stats" : (None, None),
"doc_h1" : (QStyle.SP_FileIcon, "x-office-document"),
"doc_h2" : (QStyle.SP_FileIcon, "x-office-document"),
"doc_h3" : (QStyle.SP_FileIcon, "x-office-document"),
"doc_h4" : (QStyle.SP_FileIcon, "x-office-document"),
"cls_none" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_novel" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_plot" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_character" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_world" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_timeline" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_object" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_entity" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_custom" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"cls_trash" : (QStyle.SP_DriveHDIcon, "drive-harddisk"),
"proj_document" : (QStyle.SP_FileIcon, "x-office-document"),
"proj_folder" : (QStyle.SP_DirIcon, "folder"),
"proj_orphan" : (QStyle.SP_MessageBoxWarning, "dialog-warning"),
"proj_nwx" : (None, None),
"status_lang" : (None, None),
"status_time" : (None, None),
"status_stats" : (None, None),
"doc_h1" : (QStyle.SP_FileIcon, "x-office-document"),
"doc_h2" : (QStyle.SP_FileIcon, "x-office-document"),
"doc_h3" : (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
"folder-open" : (QStyle.SP_DirOpenIcon, "folder-open"),
"delete" : (QStyle.SP_DialogDiscardButton, "edit-delete"),
"add" : (None, "list-add"),
"remove" : (None, "list-remove"),
"close" : (QStyle.SP_DialogCloseButton, "window-close"),
"done" : (QStyle.SP_DialogApplyButton, None),
"search" : (None, "edit-find"),
"search-replace" : (None, "edit-find-replace"),
"clear" : (QStyle.SP_LineEditClearButton, "clear_left"),
"save" : (QStyle.SP_DialogSaveButton, "document-save"),
"edit" : (None, None),
"check" : (None, None),
"cross" : (None, None),
"hash" : (None, None),
"maximise" : (None, None),
"minimise" : (None, None),
"refresh" : (None, None),
"reference" : (None, None),
"sticky-on" : (None, None),
"sticky-off" : (None, None),
"add" : (None, "list-add"),
"remove" : (None, "list-remove"),
"search" : (None, "edit-find"),
"search-replace" : (None, "edit-find-replace"),
"edit" : (None, None),
"check" : (None, None),
"cross" : (None, None),
"hash" : (None, None),
"maximise" : (None, None),
"minimise" : (None, None),
"refresh" : (None, None),
"reference" : (None, None),
## Other Icons
"warning" : (QStyle.SP_MessageBoxWarning, "dialog-warning"),
## Switches
"sticky-on" : (None, None),
"sticky-off" : (None, None),
}
DECO_MAP = {
+89 -38
View File
@@ -42,8 +42,8 @@ from PyQt5.QtWidgets import (
from nw.gui import (
GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails,
GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainMenu, GuiMainStatus,
GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad,
GuiProjectSettings, GuiProjectTree, GuiSearchBar, GuiSessionLogView, GuiTheme
GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad, GuiTheme,
GuiProjectSettings, GuiProjectTree, GuiSessionLogView
)
from nw.core import NWProject, NWDoc, NWIndex
from nw.constants import nwFiles, nwItemType, nwAlert
@@ -93,7 +93,6 @@ class GuiMain(QMainWindow):
self.docEditor = GuiDocEditor(self)
self.viewMeta = GuiDocViewDetails(self)
self.docViewer = GuiDocViewer(self)
self.searchBar = GuiSearchBar(self)
self.treeMeta = GuiItemDetails(self)
self.projView = GuiOutline(self)
self.projMeta = GuiOutlineDetails(self)
@@ -111,22 +110,13 @@ class GuiMain(QMainWindow):
self.treeBox.addWidget(self.treeMeta)
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.addWidget(self.docViewer)
self.splitView.addWidget(self.viewMeta)
self.splitView.setSizes(self.mainConf.getViewPanePos())
self.splitDocs = QSplitter(Qt.Horizontal)
self.splitDocs.setOpaqueResize(False)
self.splitDocs.addWidget(self.editPane)
self.splitDocs.addWidget(self.docEditor)
self.splitDocs.addWidget(self.splitView)
self.splitOutline = QSplitter(Qt.Vertical)
@@ -144,7 +134,6 @@ class GuiMain(QMainWindow):
xCM = self.mainConf.pxInt(4)
self.splitMain = QSplitter(Qt.Horizontal)
self.splitMain.setContentsMargins(xCM, xCM, xCM, xCM)
self.splitMain.setOpaqueResize(False)
self.splitMain.addWidget(self.treePane)
self.splitMain.addWidget(self.tabWidget)
self.splitMain.setSizes(self.mainConf.getMainPanePos())
@@ -153,7 +142,7 @@ class GuiMain(QMainWindow):
self.idxTree = self.splitMain.indexOf(self.treePane)
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.idxViewDoc = self.splitView.indexOf(self.docViewer)
self.idxViewMeta = self.splitView.indexOf(self.viewMeta)
@@ -168,7 +157,7 @@ class GuiMain(QMainWindow):
self.splitView.setCollapsible(self.idxViewMeta, False)
self.splitView.setVisible(False)
self.searchBar.setVisible(False)
self.docEditor.closeSearch()
# Build the Tree View
self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
@@ -487,6 +476,37 @@ class GuiMain(QMainWindow):
return False
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):
"""Save the current documents.
"""
@@ -498,20 +518,30 @@ class GuiMain(QMainWindow):
"""Load a document for viewing in the view panel.
"""
if tHandle is None:
tHandle = self.treeView.getSelectedHandle()
if tHandle is None:
logger.debug("No document selected, trying editor document")
tHandle = self.docEditor.theHandle
if tHandle is None:
logger.debug("No document selected, trying last viewed")
tHandle = self.theProject.lastViewed
if tHandle is None:
logger.debug("No document selected, giving up")
return False
logger.debug("Viewing document, but no handle provided")
if self.docEditor.hasFocus():
logger.verbose("Trying editor document")
tHandle = self.docEditor.theHandle
if tHandle is not None:
self.saveDocument()
else:
logger.verbose("Trying selected document")
tHandle = self.treeView.getSelectedHandle()
if tHandle is None:
logger.verbose("Trying last viewed document")
tHandle = self.theProject.lastViewed
if tHandle is None:
logger.verbose("No document to view, giving up")
return False
# Make sure main tab is in Editor view
self.tabWidget.setCurrentWidget(self.splitDocs)
logger.debug("Viewing document with handle %s" % tHandle)
if self.docViewer.loadText(tHandle):
if not self.splitView.isVisible():
bPos = self.splitMain.sizes()
@@ -601,15 +631,13 @@ class GuiMain(QMainWindow):
return True
def passDocumentAction(self, theAction):
"""Pass on document action theAction to whatever document has
the focus. If no document has focus, the action is discarded.
"""Pass on document action theAction to the document viewer if
it has focus, otherwise pass it to the document editor.
"""
if self.docEditor.hasFocus():
self.docEditor.docAction(theAction)
elif self.docViewer.hasFocus():
if self.docViewer.hasFocus():
self.docViewer.docAction(theAction)
else:
logger.debug("Document action requested, but no document has focus")
self.docEditor.docAction(theAction)
return True
##
@@ -948,12 +976,15 @@ class GuiMain(QMainWindow):
"""Connect to the main window all menu actions that need to be
available also when the main menu is hidden.
"""
# Project
self.addAction(self.mainMenu.aSaveProject)
self.addAction(self.mainMenu.aExitNW)
# Document
self.addAction(self.mainMenu.aSaveDoc)
self.addAction(self.mainMenu.aFileDetails)
self.addAction(self.mainMenu.aZenMode)
self.addAction(self.mainMenu.aFullScreen)
# Edit
self.addAction(self.mainMenu.aEditUndo)
self.addAction(self.mainMenu.aEditRedo)
self.addAction(self.mainMenu.aEditCut)
@@ -961,9 +992,20 @@ class GuiMain(QMainWindow):
self.addAction(self.mainMenu.aEditPaste)
self.addAction(self.mainMenu.aSelectAll)
self.addAction(self.mainMenu.aSelectPar)
self.addAction(self.mainMenu.aFmtBold)
# Insert
self.addAction(self.mainMenu.aInsENDash)
self.addAction(self.mainMenu.aInsEMDash)
self.addAction(self.mainMenu.aInsEllipsis)
self.addAction(self.mainMenu.aInsHardBreak)
self.addAction(self.mainMenu.aInsNBSpace)
self.addAction(self.mainMenu.aInsThinSpace)
self.addAction(self.mainMenu.aInsThinNBSpace)
# Format
self.addAction(self.mainMenu.aFmtItalic)
self.addAction(self.mainMenu.aFmtULine)
self.addAction(self.mainMenu.aFmtBold)
self.addAction(self.mainMenu.aFmtBoldIt)
self.addAction(self.mainMenu.aFmtDQuote)
self.addAction(self.mainMenu.aFmtSQuote)
self.addAction(self.mainMenu.aFmtHead1)
@@ -972,10 +1014,19 @@ class GuiMain(QMainWindow):
self.addAction(self.mainMenu.aFmtHead4)
self.addAction(self.mainMenu.aFmtComment)
self.addAction(self.mainMenu.aFmtNoFormat)
# View
self.addAction(self.mainMenu.aZenMode)
self.addAction(self.mainMenu.aFullScreen)
# Tools
self.addAction(self.mainMenu.aSpellCheck)
self.addAction(self.mainMenu.aReRunSpell)
self.addAction(self.mainMenu.aPreferences)
# Help
self.addAction(self.mainMenu.aHelp)
return True
def _setWindowTitle(self, projName=None):
@@ -1075,8 +1126,8 @@ class GuiMain(QMainWindow):
"""When the escape key is pressed somewhere in the main window,
do the following, in order:
"""
if self.searchBar.isVisible():
self.searchBar.setVisible(False)
if self.docEditor.docSearch.isVisible():
self.docEditor.closeSearch()
return
elif self.isZenMode:
self.toggleZenMode()
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 14298de4d9524:f7e2d9f330615:f6622b4617424:John Smith
%%~ 14298de4d9524:f7e2d9f330615:f6622b4617424:CHARACTER:NOTE:John Smith
# John Smith
@tag: John
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 53b69b83cdafc:7031beac91f75:Title Page
%%~ 53b69b83cdafc:7031beac91f75:NOVEL:TITLE:Title Page
# My Novel
**By Jane Doh**
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 5eaea4e8cdee8:15c4492bd5107:Mars
%%~ 5eaea4e8cdee8:15c4492bd5107:WORLD:NOTE:Mars
# Mars
@tag: Mars
+5 -3
View File
@@ -1,4 +1,4 @@
%%~ 636b6aa9b697b:e7ded148d6e4a:7031beac91f75:Making a Scene
%%~ 636b6aa9b697b:e7ded148d6e4a:7031beac91f75:NOVEL:SCENE:Making a Scene
### Making a Scene
@pov: Jane
@@ -7,13 +7,15 @@
A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree.
Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and __underscore__.
Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, *italic* and ***bold italic***. You can also ~~strike through~~ text.
In addition, the editor supports automatic formatting of “quotes”, both double and single. Depending on the syntax highlighter, these can be in different colours.
If you have the need for it, you can also add text that can be automatically replaced by other text when you generate a preview or export the project. Now, lets auto-replace this A with <A>, and this C with <C>. While <E> is just <E>. Press Ctrl+R to see what this looks like in the view pane.
The editor also supports non breaking spaces, and the spell checker accepts long dashes—like this—as valid word separators.
The editor also supports non breaking spaces, and the spell checker accepts long dashes—like this—as valid word separators. Regular dashes are also supported and can be automatically inserted when typing two hyphens.
Thin spaces and thin non-breaking spaces are also supported from the Insert menu, and can be used to separate numbers from their units, like: 25kg.
#### Some Section Here
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 6a2d6d5f4f401:e7ded148d6e4a:7031beac91f75:Chapter One
%%~ 6a2d6d5f4f401:e7ded148d6e4a:7031beac91f75:NOVEL:CHAPTER:Chapter One
## So it Begins
@pov: Jane
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 88706ddc78b1b:e7ded148d6e4a:7031beac91f75:Chapter Two
%%~ 88706ddc78b1b:e7ded148d6e4a:7031beac91f75:NOVEL:CHAPTER:Chapter Two
## Where has John Gone?
@pov: Jane
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 96b68994dfa3d:e7ded148d6e4a:7031beac91f75:A Note on Structure
%%~ 96b68994dfa3d:e7ded148d6e4a:7031beac91f75:NOVEL:NOTE:A Note on Structure
# A Note on Structure
This file is just a note. You can save notes anywhere you like in the project tree. Notes can be filtered out when you export the project.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 974e400180a99:7031beac91f75:Page
%%~ 974e400180a99:7031beac91f75:NOVEL:PAGE:Page
This is a plain page with some text on it.
This file should receive no special formatting, but the text will always be left aligned and the content will always start on a fresh page when the project is exported.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ ae7339df26ded:e7ded148d6e4a:7031beac91f75:We Found John!
%%~ ae7339df26ded:e7ded148d6e4a:7031beac91f75:NOVEL:SCENE:We Found John!
### We Found John!
@pov: John
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ b3e74dbc1f584:15c4492bd5107:Earth
%%~ b3e74dbc1f584:15c4492bd5107:WORLD:NOTE:Earth
# Earth
@tag: Earth
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ b8136a5a774a0:98acd8c76c93a:Delete Me!
%%~ b8136a5a774a0:98acd8c76c93a:NOVEL:SCENE:Delete Me!
### Delete Me!
This scene is trash.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ ba8a28a246524:e7ded148d6e4a:7031beac91f75:Interlude
%%~ ba8a28a246524:e7ded148d6e4a:7031beac91f75:NOVEL:UNNUMBERED:Interlude
## Interlude
% Notice that this is a file with the flag N.Un. The N means its a novel file, and the Un means its an unnumbered chapter. Unnumbered chapters can be treated separately from numbered chapters during export. Perfect for when you want to add an interlude, or for a prologue or epilogue.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ bb2c23b3c42cc:f7e2d9f330615:f6622b4617424:Jane Smith
%%~ bb2c23b3c42cc:f7e2d9f330615:f6622b4617424:CHARACTER:NOTE:Jane Smith
# Jane Smith
@tag: Jane
+1 -4
View File
@@ -1,4 +1,4 @@
%%~ bc0cbd2a407f3:e7ded148d6e4a:7031beac91f75:Another Scene
%%~ bc0cbd2a407f3:e7ded148d6e4a:7031beac91f75:NOVEL:SCENE:Another Scene
### Another Scene
@pov: John
@@ -14,6 +14,3 @@ In fact, if you wish, you can add all the scenes in the chapter file too. All no
@location: Earth
This is a second scene in the same file as the previous scene. You can always split the files up later.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ edca4be2fcaf8:7031beac91f75:Part 1
%%~ edca4be2fcaf8:7031beac91f75:NOVEL:PARTITION:Part One
# Part One
The first part.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ f1471bef9f2ae:15c4492bd5107:Space
%%~ f1471bef9f2ae:15c4492bd5107:WORLD:NOTE:Space
# Space
@tag: Space
+13 -13
View File
@@ -1,13 +1,13 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.8" hexVersion="0x000800f0" fileVersion="1.1" timeStamp="2020-06-11 22:21:20">
<novelWriterXML appVersion="0.9.0rc1" hexVersion="0x000900c1" fileVersion="1.1" timeStamp="2020-06-19 19:50:54">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>298</saveCount>
<autoCount>42</autoCount>
<editTime>5113</editTime>
<saveCount>407</saveCount>
<autoCount>71</autoCount>
<editTime>15118</editTime>
</project>
<settings>
<doBackup>False</doBackup>
@@ -15,7 +15,7 @@
<autoOutline>True</autoOutline>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>bb2c23b3c42cc</lastViewed>
<lastWordCount>914</lastWordCount>
<lastWordCount>967</lastWordCount>
<autoReplace>
<A>B</A>
<B>E</B>
@@ -114,10 +114,10 @@
<status>1st Draft</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>1199</charCount>
<wordCount>216</wordCount>
<paraCount>7</paraCount>
<cursorPos>28</cursorPos>
<charCount>1483</charCount>
<wordCount>263</wordCount>
<paraCount>8</paraCount>
<cursorPos>1086</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>Another Scene</name>
@@ -129,7 +129,7 @@
<charCount>476</charCount>
<wordCount>93</wordCount>
<paraCount>3</paraCount>
<cursorPos>551</cursorPos>
<cursorPos>428</cursorPos>
</item>
<item handle="ba8a28a246524" order="3" parent="e7ded148d6e4a">
<name>Interlude</name>
@@ -274,9 +274,9 @@
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<charCount>30</charCount>
<wordCount>6</wordCount>
<paraCount>1</paraCount>
<cursorPos>36</cursorPos>
</item>
</content>
+1 -1
View File
@@ -6,7 +6,7 @@ with open("README.md", "r") as inFile:
setuptools.setup(
name = "novelWriter",
version = "0.8",
version = "0.9rc1",
author = "Veronica Berglyd Olsen",
author_email = "code@vkbo.net",
description = "A markdown-like document editor for writing novels",
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 04468803b92e1:60bdf227455cc:Ancient Europe
%%~ 04468803b92e1:60bdf227455cc:WORLD:NOTE:Ancient Europe
# Ancient Europe
@tag: Europe
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 2426c6f0ca922:6c6afb1247750:Main
%%~ 2426c6f0ca922:6c6afb1247750:PLOT:NOTE:Main
# Main Plot
@tag: Main
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 441420a886d82:6bd935d2490cd:b3643d0f92e32:Chapter Two
%%~ 441420a886d82:6bd935d2490cd:b3643d0f92e32:NOVEL:CHAPTER:Chapter Two
## Chapter Two
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 47666c91c7ccf:6bd935d2490cd:b3643d0f92e32:Scene Five
%%~ 47666c91c7ccf:6bd935d2490cd:b3643d0f92e32:NOVEL:SCENE:Scene Five
### Scene Five
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 4c4f28287af27:67a8707f2f249:Mr. Nobody
%%~ 4c4f28287af27:67a8707f2f249:CHARACTER:NOTE:Mr. Nobody
# Nobody Owens
@tag: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 7a992350f3eb6:b3643d0f92e32:Lorem Ipusm
%%~ 7a992350f3eb6:b3643d0f92e32:NOVEL:TITLE:Lorem Ipusm
# Lorem Ipsum
**By lipsum.com**
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 846352075de7d:b3643d0f92e32:Interlude
%%~ 846352075de7d:b3643d0f92e32:NOVEL:BOOK:Interlude
## Why do we use it?
% Exctracted from the lipsum.com website.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 88243afbe5ed8:45e6b01ca35c1:b3643d0f92e32:Scene One
%%~ 88243afbe5ed8:45e6b01ca35c1:b3643d0f92e32:NOVEL:SCENE:Scene One
### Scene One
@pov: Bod
+2 -2
View File
@@ -1,6 +1,6 @@
%%~ 88d59a277361b:b3643d0f92e32:Prologue
%%~ 88d59a277361b:b3643d0f92e32:NOVEL:UNNUMBERED:Prologue
## Prologue
% Synopsis:Explanation from the lipsum.com website.
_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
*Lorem Ipsum* is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 8c58a65414c23:b3643d0f92e32:Front Matter
%%~ 8c58a65414c23:b3643d0f92e32:NOVEL:PAGE:Front Matter
% Exctracted from the lipsum.com website.
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of “de Finibus Bonorum et Malorum” (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, “Lorem ipsum dolor sit amet..”, comes from a line in section 1.10.32.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ db7e733775d4d:b3643d0f92e32:Act One
%%~ db7e733775d4d:b3643d0f92e32:NOVEL:PARTITION:Act One
# Act One
“Fusce maximus felis libero”
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ eb103bc70c90c:6bd935d2490cd:b3643d0f92e32:Scene Three
%%~ eb103bc70c90c:6bd935d2490cd:b3643d0f92e32:NOVEL:SCENE:Scene Three
### Scene Three
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ f8c0562e50f1b:6bd935d2490cd:b3643d0f92e32:Scene Four
%%~ f8c0562e50f1b:6bd935d2490cd:b3643d0f92e32:NOVEL:SCENE:Scene Four
### Scene Four
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ f96ec11c6a3da:45e6b01ca35c1:b3643d0f92e32:Scene Two
%%~ f96ec11c6a3da:45e6b01ca35c1:b3643d0f92e32:NOVEL:SCENE:Scene Two
### Scene Two
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ fb609cd8319dc:45e6b01ca35c1:b3643d0f92e32:Chapter One
%%~ fb609cd8319dc:45e6b01ca35c1:b3643d0f92e32:NOVEL:CHAPTER:Chapter One
## Chapter One
@pov: Bod
+14 -14
View File
@@ -1,17 +1,20 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="6" autoCount="20" timeStamp="2020-05-30 12:03:01" editTime="1408">
<novelWriterXML appVersion="0.9.0rc1" hexVersion="0x000900c1" fileVersion="1.1" timeStamp="2020-06-13 00:37:49">
<project>
<name>Lorem Ipsum</name>
<title>Lorem Ipsum</title>
<author>lipsum.com</author>
<backup>False</backup>
<saveCount>7</saveCount>
<autoCount>21</autoCount>
<editTime>1459</editTime>
</project>
<settings>
<doBackup>False</doBackup>
<spellCheck>False</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>88d59a277361b</lastEdited>
<lastEdited>04468803b92e1</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>3397</lastWordCount>
<lastWordCount>3847</lastWordCount>
<autoReplace>
<Rep1>Replace Text 1</Rep1>
<Rep2>Replace Text 2</Rep2>
@@ -22,9 +25,6 @@
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
<withSynopsis>False</withSynopsis>
<withComments>False</withComments>
<withKeywords>False</withKeywords>
</titleFormat>
<status>
<entry blue="100" green="100" red="100">New</entry>
@@ -81,7 +81,7 @@
<charCount>584</charCount>
<wordCount>92</wordCount>
<paraCount>1</paraCount>
<cursorPos>35</cursorPos>
<cursorPos>79</cursorPos>
</item>
<item handle="db7e733775d4d" order="3" parent="b3643d0f92e32">
<name>Act One</name>
@@ -238,9 +238,9 @@
<status>Main</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>9</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<charCount>1369</charCount>
<wordCount>195</wordCount>
<paraCount>2</paraCount>
<cursorPos>1387</cursorPos>
</item>
<item handle="60bdf227455cc" order="3" parent="None">
@@ -257,9 +257,9 @@
<status>Minor</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>14</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<charCount>1770</charCount>
<wordCount>259</wordCount>
<paraCount>3</paraCount>
<cursorPos>1792</cursorPos>
</item>
</content>
+1 -1
View File
@@ -14,7 +14,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
## Prologue
_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
*Lorem Ipsum* is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
# Act One
+1 -1
View File
@@ -16,7 +16,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
% Synopsis:Explanation from the lipsum.com website.
_Lorem Ipsum_ is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
*Lorem Ipsum* is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
# Act One
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 0e17daca5f3e1:71ee45a3c0db9:New File
%%~ 0e17daca5f3e1:71ee45a3c0db9:PLOT:NOTE:New File
# Main Plot
@tag: MainPlot
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 1a6562590ef19:811786ad1ae74:New File
%%~ 1a6562590ef19:811786ad1ae74:WORLD:NOTE:New File
# Main Location
@tag: Home
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 31489056e0916:25fc0e7096fc6:73475cb40a568:New Scene
%%~ 31489056e0916:25fc0e7096fc6:73475cb40a568:NOVEL:SCENE:New Scene
# Novel
## Chapter
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 98010bd9270f9:44cb730c42048:New File
%%~ 98010bd9270f9:44cb730c42048:CHARACTER:NOTE:New File
# Jane Doe
@tag: Jane
+6 -6
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 21:07:51">
<novelWriterXML appVersion="0.9.0rc1" hexVersion="0x000900c1" fileVersion="1.1" timeStamp="2020-06-18 22:17:32">
<project>
<name>New Project</name>
<title></title>
@@ -11,9 +11,9 @@
<doBackup>True</doBackup>
<spellCheck>False</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastEdited>31489056e0916</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>0</lastWordCount>
<lastWordCount>59</lastWordCount>
<autoReplace/>
<titleFormat>
<title>%title%</title>
@@ -57,9 +57,9 @@
<status>Note</status>
<exported>False</exported>
<layout>PAGE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<charCount>331</charCount>
<wordCount>59</wordCount>
<paraCount>2</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="44cb730c42048" order="1" parent="None">
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 73475cb40a568:b3643d0f92e32:Chapter One
%%~ 73475cb40a568:b3643d0f92e32:NOVEL:SCENE:Chapter One
## Chapter One
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 031b4af5197ec:0e17daca5f3e1:b3643d0f92e32:Scene One
%%~ 031b4af5197ec:0e17daca5f3e1:b3643d0f92e32:NOVEL:SCENE:Scene One
### Scene One
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 25fc0e7096fc6:811786ad1ae74:b3643d0f92e32:Chapter One
%%~ 25fc0e7096fc6:811786ad1ae74:b3643d0f92e32:NOVEL:CHAPTER:Chapter One
## Chapter One
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 2858dcd1057d3:0e17daca5f3e1:b3643d0f92e32:Scene Two
%%~ 2858dcd1057d3:0e17daca5f3e1:b3643d0f92e32:NOVEL:SCENE:Scene Two
### Scene Two
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 2fca346db6561:0e17daca5f3e1:b3643d0f92e32:Scene Two, Section Two
%%~ 2fca346db6561:0e17daca5f3e1:b3643d0f92e32:NOVEL:SCENE:Scene Two, Section Two
#### Scene Two, Section Two
Suspendisse potenti. Fusce tempus lorem nec laoreet suscipit. Fusce vulputate nisl ac diam tincidunt, nec malesuada quam pellentesque. Maecenas congue, tellus quis commodo rutrum, magna leo egestas arcu, quis suscipit ex risus id ligula. Suspendisse potenti. Morbi blandit lacus vitae laoreet vulputate. Donec vitae tellus eleifend, lobortis eros eu, tincidunt enim. Nullam et ullamcorper nisi. Vivamus tellus ex, lobortis quis rutrum ut, dapibus sit amet turpis. Phasellus pellentesque metus diam, commodo tristique ante commodo ac. Ut mollis ipsum nec diam blandit sollicitudin. Duis bibendum lacus nec commodo dapibus. Sed condimentum luctus ante, id ultricies urna varius nec. Nam convallis magna nec bibendum ultrices. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed auctor pharetra quam, vitae porta ex bibendum eu.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 31489056e0916:811786ad1ae74:b3643d0f92e32:Scene One
%%~ 31489056e0916:811786ad1ae74:b3643d0f92e32:NOVEL:SCENE:Scene One
### Scene One
@pov: Bod
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 41cfc0d1f2d12:0e17daca5f3e1:b3643d0f92e32:Scene One, Section Two
%%~ 41cfc0d1f2d12:0e17daca5f3e1:b3643d0f92e32:NOVEL:SCENE:Scene One, Section Two
#### Scene One, Section Two
Integer vel libero ipsum. Donec varius aliquam libero, sit amet commodo urna hendrerit non. Nullam quis erat mollis nunc viverra volutpat tincidunt in odio. Nam vitae quam sem. Aliquam suscipit nulla non lorem pharetra semper. Ut suscipit erat eu ligula accumsan ultrices. Phasellus nisl tellus, placerat sed laoreet id, consectetur nec dolor. Sed fringilla ipsum id dapibus posuere. Aenean finibus pharetra tincidunt. Ut molestie malesuada nulla, id posuere lorem tincidunt eu. Aliquam tempor eros a est vulputate, scelerisque pulvinar ipsum fermentum. In hac habitasse platea dictumst.
+1 -1
View File
@@ -1,4 +1,4 @@
%%~ 98010bd9270f9:811786ad1ae74:b3643d0f92e32:Scene Two
%%~ 98010bd9270f9:811786ad1ae74:b3643d0f92e32:NOVEL:SCENE:Scene Two
### Scene Two
@pov: Bod
+7 -1
View File
@@ -1,5 +1,5 @@
[Main]
timestamp = 2020-06-13 22:59:36
timestamp = 2020-06-17 19:45:53
theme = default
syntax = default_light
icons = typicons_colour_light
@@ -55,6 +55,12 @@ askbeforebackup = True
showrefpanel = True
viewcomments = True
viewsynopsis = True
searchcase = False
searchword = False
searchregex = False
searchloop = False
searchnextfile = False
searchmatchcap = False
[Path]
lastpath =
+148 -2
View File
@@ -127,6 +127,11 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
assert nwGUI.openSelectedItem()
# Add Some Text
nwGUI.docEditor.replaceText("Hello World!")
assert nwGUI.docEditor.getText() == "Hello World!"
nwGUI.docEditor.replaceText("")
# Type something into the document
nwGUI.setFocus(2)
for c in "# Main Location":
@@ -273,6 +278,7 @@ def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp):
nwGUI.mainConf.backupPath = nwTempGUI
projEdit = GuiProjectSettings(nwGUI, nwGUI.theProject)
projEdit.show()
qtbot.addWidget(projEdit)
projEdit.tabMain.editName.setText("")
@@ -358,6 +364,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
# Create new, save, open project
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject(nwTempGUI, True)
assert nwGUI.openDocument("31489056e0916")
itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "31489056e0916")
qtbot.addWidget(itemEdit)
@@ -374,7 +381,6 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
itemEdit.editExport.setChecked(False)
assert not itemEdit.editExport.isChecked()
itemEdit._doSave()
itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "31489056e0916")
@@ -382,9 +388,16 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
assert itemEdit.editName.text() == "Just a Page"
assert itemEdit.editStatus.currentData() == "Note"
assert itemEdit.editLayout.currentData() == nwItemLayout.PAGE
itemEdit._doClose()
# Check that the header is updated
nwGUI.docEditor.updateDocTitle("31489056e0916")
assert nwGUI.docEditor.docHeader.theTitle.text() == "Novel New Chapter Just a Page"
assert not nwGUI.docEditor.setCursorLine("where?")
assert nwGUI.docEditor.setCursorLine(2)
qtbot.wait(stepDelay)
assert nwGUI.docEditor.getCursorPosition() == 9
qtbot.wait(stepDelay)
assert nwGUI.saveProject()
qtbot.wait(stepDelay)
@@ -610,3 +623,136 @@ def testSplitTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
# qtbot.stopForInteraction()
nwGUI.closeMain()
@pytest.mark.gui
def testDocAction(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
qtbot.wait(stepDelay)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.openProject(nwLipsum)
qtbot.wait(stepDelay)
# CUT = 3
# COPY = 4
# PASTE = 5
# SEL_ALL = 12
# SEL_PARA = 13
# FIND = 14
# REPLACE = 15
# GO_NEXT = 16
# GO_PREV = 17
# REPL_NEXT = 18
# Split By Chapter
assert nwGUI.openDocument("4c4f28287af27")
assert nwGUI.docEditor.setCursorPosition(30)
cleanText = nwGUI.docEditor.getText()[27:74]
# Bold
assert nwGUI.passDocumentAction(nwDocAction.BOLD)
assert nwGUI.docEditor.getText()[27:78] == "**Pellentesque** nec erat ut nulla posuere commodo."
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.BOLD)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
# Italic
assert nwGUI.passDocumentAction(nwDocAction.ITALIC)
assert nwGUI.docEditor.getText()[27:76] == "*Pellentesque* nec erat ut nulla posuere commodo."
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.ITALIC)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
# Bold-Italic
assert nwGUI.passDocumentAction(nwDocAction.BOLDITALIC)
assert nwGUI.docEditor.getText()[27:80] == "***Pellentesque*** nec erat ut nulla posuere commodo."
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.BOLDITALIC)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
# Strikethrough
assert nwGUI.passDocumentAction(nwDocAction.STRIKE)
assert nwGUI.docEditor.getText()[27:78] == "~~Pellentesque~~ nec erat ut nulla posuere commodo."
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.STRIKE)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
# Should get us back to plain
assert nwGUI.passDocumentAction(nwDocAction.BOLD)
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.ITALIC)
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.ITALIC)
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.BOLD)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
# Equivalent of the above
assert nwGUI.passDocumentAction(nwDocAction.BOLDITALIC)
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.ITALIC)
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.BOLD)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
# Double Quotes
assert nwGUI.passDocumentAction(nwDocAction.D_QUOTE)
assert nwGUI.docEditor.getText()[27:76] == "“Pellentesque” nec erat ut nulla posuere commodo."
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.UNDO)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
# Single Quotes
assert nwGUI.passDocumentAction(nwDocAction.S_QUOTE)
assert nwGUI.docEditor.getText()[27:76] == "Pellentesque nec erat ut nulla posuere commodo."
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.UNDO)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
# Block Formats
assert nwGUI.docEditor.setCursorPosition(30)
assert nwGUI.passDocumentAction(nwDocAction.BLOCK_H1)
assert nwGUI.docEditor.getText()[27:76] == "# Pellentesque nec erat ut nulla posuere commodo."
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.BLOCK_H2)
assert nwGUI.docEditor.getText()[27:77] == "## Pellentesque nec erat ut nulla posuere commodo."
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.BLOCK_H3)
assert nwGUI.docEditor.getText()[27:78] == "### Pellentesque nec erat ut nulla posuere commodo."
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.BLOCK_H4)
assert nwGUI.docEditor.getText()[27:79] == "#### Pellentesque nec erat ut nulla posuere commodo."
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.BLOCK_TXT)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.BLOCK_COM)
assert nwGUI.docEditor.getText()[27:76] == "% Pellentesque nec erat ut nulla posuere commodo."
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.BLOCK_TXT)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
# Undo/Redo
assert nwGUI.passDocumentAction(nwDocAction.UNDO)
assert nwGUI.docEditor.getText()[27:76] == "% Pellentesque nec erat ut nulla posuere commodo."
qtbot.wait(stepDelay)
assert nwGUI.passDocumentAction(nwDocAction.REDO)
assert nwGUI.docEditor.getText()[27:74] == cleanText
qtbot.wait(stepDelay)
# qtbot.stopForInteraction()
nwGUI.closeMain()