Merge branch 'dev' into new_ref_panel

This commit is contained in:
Veronica Berglyd Olsen
2023-11-14 11:06:04 +01:00
84 changed files with 673 additions and 1761 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
name: flake8
name: Flake8
on:
push:
+77
View File
@@ -1,5 +1,82 @@
# novelWriter Changelog
## Version 2.2 Beta 1 [2023-11-11]
### Release Notes
This is a beta release of the next release version, and is intended for testing purposes. Please be
careful when using this version on live writing projects, and make sure you take frequent backups.
Please check the changelog for an overview of changes. The full release notes will be added to the
final release.
### Detailed Changelog
**Features**
* novelWriter has a new logo and icon. PR #1593.
* The Document Editor is now a true plain text editor. This has a number of benefits and a couple
of drawbacks. The most important benefits is that the editor responds a lot faster, and can hold
much larger text documents. The big document limit has therefore been removed. It mostly affected
automatic spell checking. The syntax highlighter and spell checker are also more efficient, which
allows for needed improvements to these. The drawbacks are mainly that the editor now scrolls one
line at a time, instead of scrolling pixel by pixel like before. PRs #1521 and #1525.
* Tags and References are now case insensitive. Their display name on the user interface remains
the same as the value set for the `@tag` entry. Issue #1313. PRs #1522 and #1578.
* Keywords for Tags and References, and the References themselves, now have an auto-complete menu
that pops up in the editor on lines starting with the `@` character. It will first suggest what
keyword you want to use, and when it has been added, use that keyword to look up suggestions for
references to add. The suggestions improve as you type by looking for the characters you've
already typed in the tags you've previously defined. Issue #823. PR #1581.
* You can now right-click an undefined tag, and a context menu option to create a Project Note for
that tag will appear in the menu. On selection, it will create a note in the first root folder of
the correct kind, and set the title and tag to match the undefined reference, making it instantly
defined. Issues #1580 and #823. PR #1582.
* Shortcodes have been added to the Document Editor. Shorcodes are HTML-like syntax, but uses
square brackets instead of angular brackets. So `[b]text[/b]` will make the word "text" appear as
bold. Shortcodes currently support bold, italic, striketrough, underline, superscript and
subscript text. The first three are complimentary to the Markdown-like syntax that. The benefit
of the shortcode emphasis syntax, however, is that it does not care about word boundaries, and
can therefore be used any place in the text. Including in the middle of words. Issues #1337 and
#1444. PRs #1540 and #1583.
* A show/hide toolbar has been added to the editor where toolbuttons for formatting options are
available. The toolbar is hidden by default, but can be activated from a three dots icon in the
top left corner of the editor. Issue #1585. PR #1584.
* Build Definitions in the Manuscript Build tool can now be re-ordered, and the order is preserved
when the tool is closed and re-opened. Issue #1542. PR #1591.
**Usability**
* The Settings menu in the sidebar now always pops out to the right and upwards from the bottom of
the icon. The previous behaviour was not guaranteed to stay in the visible area of the screen.
PR #1520.
* The right click action on a misspelled word now uses the actual spell checker data for lookup.
Previously, the spell checker would underline a word that was misspelled, but the right click
action actually had no way of reading where the error line was, so it had to guess again what
word the user was clicking. Since these two parts of the code used different logic, they
sometimes produced different results. The spell checker now saves the location of each spell
check error, and the right click action retrieves this data when generating suggestions, which
should eliminate the problem of picking the correct word boundaries. Issue #1532. PR #1525.
* The language of a project is not set in the New Project Wizard and in Project Settings. It is no
longer defined in the Build Settings panel. Issue #1588. PR #1589.
* The way switching focus and view in the main GUI has changed. Pressing `Ctrl+T` will now switch
focus to the Project or Novel Tree if focus is elsewhere, or if either have focus already, it
will switch view to the other tree. Pressing `Ctrl+E` will switch focus and view to the Document
Editor. Pressing `Ctrl+Shift+T` will do the same for the Outline View. The old Alt-based
shortcuts have been removed. Issues #1310 and #1291. PR #1590.
**User Interface**
* The labels under the sidebar buttons have been removed. The tool tips have the necessary
information. PR #1520.
**Other Improvements**
* Also the Tags and References keywords are now translated into the project language when these are
included in Manuscript builds. As long as the phrases have been translated. PR #1586.
----
## Version 2.1.1 [2023-11-05]
### Release Notes
+6
View File
@@ -52,6 +52,12 @@ Some of the assets bundled with novelWriter were adapted from the following sour
* [Owl](https://github.com/sdras/night-owl-vscode-theme) syntax themes by Sarah Drasner (MIT License)
* [Solarized](https://github.com/altercation/solarized) themes by Ethan Schoonover (MIT License)
## Fonts
The font used for the main novelWriter logo, mimetype and text banners is:
* Pridi by Cadson Demak (Open Font License, Version 1.1)
## Special Mentions
Additional thanks to @johnblommers who was an early user and who has provided a lot of very useful
+4 -5
View File
@@ -1,13 +1,12 @@
# novelWriter
[![release](https://img.shields.io/github/v/release/vkbo/novelwriter)](https://github.com/vkbo/novelWriter/releases)
[![Linux](https://github.com/vkbo/novelWriter/actions/workflows/test_linux.yml/badge.svg?branch=main)](https://github.com/vkbo/novelWriter/actions/workflows/test_linux.yml)
[![Windows](https://github.com/vkbo/novelWriter/actions/workflows/test_win.yml/badge.svg?branch=main)](https://github.com/vkbo/novelWriter/actions/workflows/test_win.yml)
[![macOS](https://github.com/vkbo/novelWriter/actions/workflows/test_mac.yml/badge.svg?branch=main)](https://github.com/vkbo/novelWriter/actions/workflows/test_mac.yml)
[![flake8](https://github.com/vkbo/novelWriter/workflows/flake8/badge.svg)](https://github.com/vkbo/novelWriter/actions)
[![codecov](https://codecov.io/gh/vkbo/novelWriter/branch/main/graph/badge.svg)](https://codecov.io/gh/vkbo/novelWriter)
[![MacOS](https://github.com/vkbo/novelWriter/actions/workflows/test_mac.yml/badge.svg?branch=main)](https://github.com/vkbo/novelWriter/actions/workflows/test_mac.yml)
[![Flake8](https://github.com/vkbo/novelWriter/workflows/flake8/badge.svg)](https://github.com/vkbo/novelWriter/actions)
[![CodeCov](https://codecov.io/gh/vkbo/novelWriter/branch/main/graph/badge.svg)](https://codecov.io/gh/vkbo/novelWriter)
<img align="left" style="margin: 0 16px 4px 0;" src="https://raw.githubusercontent.com/vkbo/novelWriter/main/setup/novelwriter.png">
<img align="left" style="margin: 0 0 4px 0;" src="https://raw.githubusercontent.com/vkbo/novelWriter/main/setup/novelwriter_readme.png">
novelWriter is a plain text editor designed for writing novels assembled from many smaller text
documents. It uses a minimal formatting syntax inspired by Markdown, and adds a meta data syntax
Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

+20 -19
View File
@@ -25,19 +25,19 @@ Main Window Shortcuts
":kbd:`F8`", "Toggle :guilabel:`Focus Mode`"
":kbd:`F9`", "Re-build the project index"
":kbd:`F11`", "Toggle full screen mode"
":kbd:`Alt+1`", "Switch focus to the project tree (Windows :kbd:`Ctrl+Alt+1`)"
":kbd:`Alt+2`", "Switch focus to document editor (Windows :kbd:`Ctrl+Alt+2`)"
":kbd:`Alt+3`", "Switch focus to document viewer (Windows :kbd:`Ctrl+Alt+3`)"
":kbd:`Alt+4`", "Switch focus to outline view (Windows :kbd:`Ctrl+Alt+4`)"
":kbd:`Ctrl+,`", "Open the :guilabel:`Preferences` dialog"
":kbd:`Ctrl+E`", "Switch focus to the document editor"
":kbd:`Ctrl+T`", "Switch focus to the project/novel tree"
":kbd:`Ctrl+Q`", "Exit novelWriter"
":kbd:`Ctrl+Shift+,`", "Open the :guilabel:`Project Settings` dialog"
":kbd:`Ctrl+Shift+O`", "Open a project"
":kbd:`Ctrl+Shift+S`", "Save the current project"
":kbd:`Ctrl+Shift+T`", "Switch focus to the outline view"
":kbd:`Ctrl+Shift+W`", "Close the current project"
":kbd:`Shift+F1`", "Open the local user manual (PDF) if it is available"
":kbd:`Shift+F6`", "Open the :guilabel:`Project Details` dialog"
.. _a_kb_tree:
Project Tree Shortcuts
@@ -46,21 +46,21 @@ Project Tree Shortcuts
.. csv-table::
:header: "Shortcut", "Description"
":kbd:`F2`", "Edit the label of the selected item"
":kbd:`Return`", "Open the selected document in the editor"
":kbd:`Alt+Up`", "Jump or go to the previous item at same level in the tree"
":kbd:`Alt+Down`", "Jump or go to the next item at same level in the tree"
":kbd:`Alt+Left`", "Jump to the parent item in the tree"
":kbd:`Alt+Right`", "Jump to the first child item in the project tree"
":kbd:`Ctrl+.`", "Open the context menu on the selected item"
":kbd:`Ctrl+L`", "Open the :guilabel:`Quick Links` menu"
":kbd:`Ctrl+N`", "Open the :guilabel:`Create New Item` menu"
":kbd:`Ctrl+O`", "Open selected document"
":kbd:`Ctrl+R`", "Open the selected document in the viewer"
":kbd:`Ctrl+Up`", "Move selected item one step up in the tree"
":kbd:`Ctrl+Down`", "Move selected item one step down in the tree"
":kbd:`Ctrl+Shift+Z`", "Undo the last move of a project item, if possible"
":kbd:`Ctrl+Shift+Del`", "Move the selected item to Trash"
":kbd:`F2`", "Edit the label of the selected item"
":kbd:`Return`", "Open the selected document in the editor"
":kbd:`Alt+Up`", "Jump or go to the previous item at same level in the tree"
":kbd:`Alt+Down`", "Jump or go to the next item at same level in the tree"
":kbd:`Alt+Left`", "Jump to the parent item in the tree"
":kbd:`Alt+Right`", "Jump to the first child item in the project tree"
":kbd:`Ctrl+.`", "Open the context menu on the selected item"
":kbd:`Ctrl+L`", "Open the :guilabel:`Quick Links` menu"
":kbd:`Ctrl+N`", "Open the :guilabel:`Create New Item` menu"
":kbd:`Ctrl+O`", "Open selected document"
":kbd:`Ctrl+R`", "Open the selected document in the viewer"
":kbd:`Ctrl+Up`", "Move selected item one step up in the tree"
":kbd:`Ctrl+Down`", "Move selected item one step down in the tree"
":kbd:`Ctrl+Del`", "Move the selected item to Trash"
":kbd:`Ctrl+Shift+Z`", "Undo the last move of a project item, if possible"
.. _a_kb_editor:
@@ -134,6 +134,7 @@ Other Editor Shortcuts
":kbd:`Ctrl+Shift+A`", "Select all text in the current paragraph"
":kbd:`Ctrl+Shift+I`", "Import text to the current document from a text file"
.. _a_kb_ins:
Insert Shortcuts
+5 -23
View File
@@ -33,26 +33,8 @@ from novelwriter.error import exceptionHandler, logException
from novelwriter.config import Config
from novelwriter.shared import SharedData
##
# Version Scheme
# ================
# Generally follows PEP 440
# Hex Version:
# - Digit 1,2 : Major Version (01-ff)
# - Digit 3,4 : Minor Version (01-ff)
# - Digit 5,6 : Patch Version (01-ff)
# - Digit 7 : Release Type (a: alpha, b: beta, c: candidate, f: final)
# - Digit 8 : Release Number (0-f)
#
# Example : Full Short Description
# --------------------------------------------------------------------
# 0x010200a0 : 1.2-alpha0 1.2a0 Use while developing next release
# 0x010200a1 : 1.2-alpha1 1.2a1 First alpha release
# 0x010200b1 : 1.2-beta1 1.2b1 First beta release
# 0x010200c1 : 1.2-rc1 1.2rc1 First release candidate
# 0x010200f0 : 1.2.0 1.2.0 Final release
# 0x010201f0 : 1.2.1 1.2.1 Patch release
##
# Package Meta
# ============
__package__ = "novelwriter"
__copyright__ = "Copyright 20182023, Veronica Berglyd Olsen"
@@ -60,9 +42,9 @@ __license__ = "GPLv3"
__author__ = "Veronica Berglyd Olsen"
__maintainer__ = "Veronica Berglyd Olsen"
__email__ = "code@vkbo.net"
__version__ = "2.2-alpha1"
__hexversion__ = "0x020200a1"
__date__ = "2023-09-01"
__version__ = "2.2-beta1"
__hexversion__ = "0x020200b1"
__date__ = "2023-11-12"
__status__ = "Stable"
__domain__ = "novelwriter.io"
@@ -2,6 +2,16 @@
"Synopsis": "Samandrag",
"Comment": "Kommentar",
"Notes": "Notat",
"Tag": "Knagg",
"Point of View": "Perspektiv",
"Focus": "Fokus",
"Characters": "Karakterar",
"Plot": "Plott",
"Timeline": "Tidslinje",
"Locations": "Lokasjonar",
"Objects": "Objekt",
"Entities": "Eining",
"Custom": "Anna",
"0": "null",
"1": "ein",
"2": "to",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 167 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 167 KiB

@@ -1,207 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg8"
version="1.1"
viewBox="0 0 270.93333 270.93334"
height="1024"
width="1024">
<defs
id="defs2">
<linearGradient
id="linearGradient2515">
<stop
style="stop-color:#f7f7f7;stop-opacity:1"
offset="0"
id="stop2511" />
<stop
style="stop-color:#e5e5e5;stop-opacity:1"
offset="1"
id="stop2513" />
</linearGradient>
<rect
x="60.484347"
y="205.79962"
width="130.57787"
height="65.785888"
id="rect852" />
<rect
id="rect1474"
height="28.631245"
width="177.70083"
y="18.019813"
x="13.463703" />
<rect
id="rect852-7"
height="65.785889"
width="130.57787"
y="205.79962"
x="60.484348" />
<rect
id="rect1544"
height="65.785889"
width="130.57787"
y="205.79962"
x="60.484348" />
<rect
x="60.484348"
y="205.79962"
width="130.57787"
height="65.785889"
id="rect1570" />
<rect
x="13.463703"
y="18.019813"
width="177.70083"
height="28.631245"
id="rect1587" />
<filter
id="filter1444"
style="color-interpolation-filters:sRGB">
<feFlood
id="feFlood1434"
result="flood"
flood-color="rgb(0,0,0)"
flood-opacity="0.498039" />
<feComposite
id="feComposite1436"
result="composite1"
operator="in"
in2="SourceGraphic"
in="flood" />
<feGaussianBlur
id="feGaussianBlur1438"
result="blur"
stdDeviation="3"
in="composite1" />
<feOffset
id="feOffset1440"
result="offset"
dy="6"
dx="6" />
<feComposite
id="feComposite1442"
result="composite2"
operator="over"
in2="offset"
in="SourceGraphic" />
</filter>
<filter
id="filter1456"
style="color-interpolation-filters:sRGB">
<feFlood
id="feFlood1446"
result="flood"
flood-color="rgb(0,0,0)"
flood-opacity="0.498039" />
<feComposite
id="feComposite1448"
result="composite1"
operator="in"
in2="SourceGraphic"
in="flood" />
<feGaussianBlur
id="feGaussianBlur1450"
result="blur"
stdDeviation="3"
in="composite1" />
<feOffset
id="feOffset1452"
result="offset"
dy="6"
dx="6" />
<feComposite
id="feComposite1454"
result="composite2"
operator="over"
in2="offset"
in="SourceGraphic" />
</filter>
<radialGradient
xlink:href="#linearGradient2515"
id="radialGradient2517"
cx="605.42725"
cy="623.97186"
fx="605.42725"
fy="623.97186"
r="410"
gradientTransform="matrix(2.4110274,1.7617596,-0.719517,0.98463462,-776.23441,-1372.7537)"
gradientUnits="userSpaceOnUse" />
</defs>
<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
id="layer3"
style="display:inline">
<path
clip-path="none"
transform="scale(0.26458333)"
d="m 192,22 c -44.32,0 -80,35.680002 -80,80 v 820 c 0,44.32002 35.68,80 80,80 h 640 c 44.31999,0 80,-35.67998 80,-80 V 213.44727 L 720.55469,22 Z"
style="fill:url(#radialGradient2517);fill-opacity:1;stroke:#999999;stroke-width:20;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1481" />
<path
id="path1490"
style="display:inline;fill:#e0e0e0;fill-opacity:1;stroke:#999999;stroke-width:5.29167;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 241.30037,56.474215 190.64713,5.8204593 V 35.307549 c 0,11.726333 9.44034,21.166666 21.16667,21.166666 z" />
<g
aria-label=".nwx"
transform="matrix(1.7893481,0,0,1.782571,-45.41919,-191.90571)"
id="text850"
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;white-space:pre;shape-inside:url(#rect852);display:inline;fill:#666666;fill-opacity:1">
<path
d="m 63.088865,241.76928 q 0,-1.36426 0.909505,-2.27376 0.930175,-0.93018 2.480467,-0.93018 1.550291,0 2.459796,0.93018 0.909504,0.9095 0.909504,2.27376 0,1.34359 -0.909504,2.27376 -0.909505,0.9095 -2.459796,0.9095 -1.550292,0 -2.480467,-0.9095 -0.909505,-0.93017 -0.909505,-2.27376 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
id="path856" />
<path
d="m 84.75161,226.78312 q -2.542479,0 -3.782712,2.14974 v 15.79231 h -5.973791 v -22.36554 h 5.601721 l 0.206705,2.56314 q 2.397785,-2.97656 6.428544,-2.97656 3.162595,0 5.105627,1.88103 1.943033,1.88102 1.963703,6.55256 v 14.34537 h -5.994461 v -14.26269 q 0,-2.14973 -0.930175,-2.91454 -0.909505,-0.76482 -2.625161,-0.76482 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
id="path858" />
<path
d="m 126.96089,222.35963 -5.68441,22.36554 h -5.00227 l -4.25814,-14.07665 -4.23746,14.07665 h -4.9816 l -5.705078,-22.36554 h 5.767088 l 2.95589,14.22134 4.05143,-14.22134 h 4.32014 l 4.09277,14.20067 2.91455,-14.20067 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
id="path860" />
<path
d="m 134.77436,222.35963 3.65869,6.65592 3.76204,-6.65592 h 6.3872 l -6.36653,10.93472 6.63525,11.43082 h -6.3872 l -4.01009,-7.04866 -3.94808,7.04866 h -6.42854 l 6.63525,-11.43082 -6.36654,-10.93472 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
id="path862" />
</g>
</g>
<g
id="g2509"
transform="matrix(1.2371037,0,0,1.2371037,206.3182,37.341857)">
<path
transform="matrix(0.16895991,0,0,0.18329422,-134.6942,-19.491243)"
id="rect1406-1"
style="display:inline;fill:#a11212;fill-opacity:1;stroke:#7e1b1b;stroke-width:9.99999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter1444)"
d="m 145.18984,56.414257 c -44.32,0 -79.999998,35.68 -79.999998,80.000003 v 199.99805 a 279.99999,279.99999 0 0 0 0,0.002 279.99999,279.99999 0 0 0 0,0.0195 v 199.98242 c 0,44.31999 35.679998,80 79.999998,80 44.32,0 80,-35.68001 80,-80 v -200 a 120,120 0 0 1 120,-120 120,120 0 0 1 120,120 v 282.83592 h 160 V 336.41621 a 279.99999,279.99999 0 0 0 -280,-280.000003 279.99999,279.99999 0 0 0 -0.81055,0 279.99999,279.99999 0 0 0 -133.46484,34.29688 c -14.4245,-20.75262 -38.42829,-34.29883 -65.72461,-34.29883 z M 376.65078,619.64082 c -0.62174,0.0972 -1.2172,0.22317 -1.7793,0.37696 0.5621,-0.15379 1.15756,-0.27973 1.7793,-0.37696 z m -1.7793,0.37696 c -0.5621,0.15378 -1.09071,0.33534 -1.58008,0.53906 0.48937,-0.20372 1.01798,-0.38528 1.58008,-0.53906 z m 338.85742,-0.37696 c 0.62174,0.0972 1.2172,0.22317 1.7793,0.37696 -0.5621,-0.15379 -1.15756,-0.27973 -1.7793,-0.37696 z m 1.7793,0.37696 c 0.5621,0.15378 1.09071,0.33534 1.58008,0.53906 -0.48938,-0.20372 -1.01798,-0.38528 -1.58008,-0.53906 z" />
<path
transform="matrix(0.16895991,0,0,0.18329422,-134.6942,-19.491243)"
id="path897"
style="display:inline;fill:#730d0d;fill-opacity:1;stroke:#590d0d;stroke-width:9.99999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter1456)"
d="m 465.18984,619.25215 v 0.16406 h -85.58398 c -5.49302,0 -9.91602,2.2329 -9.91602,5.00586 0,0.52294 0.15913,1.02696 0.45117,1.5 h -0.44531 c 0.30916,2.03607 1.24102,3.99791 2.81641,5.57227 l 165.59961,165.49414 c 3.92061,3.91808 10.23371,3.91812 14.15429,0 L 717.86757,631.49434 c 1.57569,-1.57463 2.50725,-3.53578 2.81641,-5.57227 h -0.44531 c 0.29216,-0.47304 0.45117,-0.97706 0.45117,-1.5 0,-2.77296 -4.42304,-5.00586 -9.91602,-5.00586 h -85.58398 v -0.16406 z" />
<rect
style="display:inline;fill:#c32222;fill-opacity:1;stroke:none;stroke-width:1.75982;stroke-linejoin:round;stroke-opacity:1"
id="rect914"
width="6.7583966"
height="82.482399"
x="-118.27303"
y="1.2969339"
rx="3.3791983"
ry="3.6658845" />
</g>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="256" height="256" version="1.1" viewBox="0 0 67.733 67.733" xmlns="http://www.w3.org/2000/svg">
<path d="m12.869 1.7145c-2.9081 0-5.2493 2.3412-5.2493 5.2493v53.806c0 2.9081 2.3412 5.2493 5.2493 5.2493h41.995c2.9081 0 5.2493-2.3412 5.2493-5.2493v-46.493l-12.562-12.562z" fill="#ececec" stroke="#999" stroke-linejoin="round" stroke-width="1.3123"/>
<path d="m60.113 14.277-12.562-12.562v7.3128c0 2.9081 2.3412 5.2493 5.2493 5.2493z" fill="#e0e0e0" stroke="#999" stroke-linejoin="round" stroke-width="1.3123"/>
<path d="m34.617 50.796c-0.31888 0-0.57319 0.09267-0.7628 0.27797-0.1853 0.18099-0.27797 0.41156-0.27797 0.69167 0 0.2758 0.09268 0.50636 0.27797 0.69166 0.18961 0.18099 0.44392 0.27144 0.7628 0.27144s0.57306-0.09044 0.76266-0.27144c0.18961-0.1853 0.28438-0.41587 0.28438-0.69166 0-0.28011-0.09477-0.51067-0.28438-0.69167-0.18961-0.1853-0.44378-0.27797-0.76266-0.27797zm-21.512 0.36204v9.4116h1.9456v-3.3226h1.7323c1.1118 0 1.978-0.26924 2.5985-0.8079 0.62484-0.53867 0.93722-1.2627 0.93722-2.172 0-0.90065-0.31238-1.644-0.93722-2.2301-0.62053-0.58607-1.4867-0.87903-2.5985-0.87903zm38.395 0.69154v1.7259h-1.0278v1.364h1.0278v3.6909c0 0.74121 0.1809 1.2713 0.54287 1.5902 0.36198 0.31889 0.84896 0.47828 1.4609 0.47828 0.40076 0 0.77566-0.0582 1.1247-0.17455v-1.4156c-0.17237 0.03448-0.3534 0.05178-0.543 0.05178-0.23701 0-0.41581-0.04745-0.53647-0.14226-0.12066-0.09481-0.18096-0.28867-0.18096-0.58171v-3.497h1.1893v-1.364h-1.1893v-1.7259zm-36.45 0.87916h1.7323c0.55589 0 0.95662 0.15729 1.2022 0.47188 0.24562 0.31027 0.36845 0.67008 0.36845 1.0795 0 0.40939-0.12283 0.7456-0.36845 1.0085-0.24562 0.26287-0.64636 0.39421-1.2022 0.39421h-1.7323zm9.8574 0.71755c-0.67224 0-1.1915 0.32101-1.5578 0.96311l-0.05818-0.83392h-1.7646v6.9942h1.8744v-4.583c0.21977-0.47834 0.68093-0.71756 1.3833-0.71756l0.67872 0.05178 0.01934-1.7906c-0.14651-0.05602-0.33829-0.08395-0.5753-0.08395zm4.2402 0c-1.0514 0-1.8659 0.34041-2.4433 1.0213-0.57743 0.67657-0.86609 1.5233-0.86609 2.5403v0.21979c0.01724 0.99546 0.31244 1.8228 0.88557 2.4822 0.57743 0.65933 1.3897 0.98899 2.4368 0.98899 1.0558 0 1.8702-0.33831 2.4433-1.0149 0.57312-0.67657 0.85968-1.5233 0.85968-2.5403v-0.13572c0-1.017-0.28655-1.8638-0.85968-2.5403-0.57313-0.68088-1.3919-1.0213-2.4563-1.0213zm10.93 0c-0.99974 0-1.7948 0.33819-2.3851 1.0148-0.58605 0.67657-0.87903 1.5429-0.87903 2.5987v0.27144c0.0043 0.93082 0.31236 1.7259 0.92427 2.3853 0.61191 0.65502 1.4608 0.98246 2.5467 0.98246 0.68085 0 1.2498-0.13134 1.7065-0.39421 0.45678-0.26287 0.79721-0.55386 1.0213-0.87275l-0.9244-1.0213c-0.41368 0.52143-0.97605 0.78214-1.6871 0.78214-0.47832 0-0.86398-0.13789-1.157-0.41369-0.29303-0.28011-0.47183-0.64634-0.53647-1.0988h4.4536v-0.79509c0-1.0342-0.26296-1.8659-0.78868-2.4951-0.52572-0.62917-1.2906-0.94376-2.2947-0.94376zm7.0715 0c-1.0816 0-1.8896 0.34251-2.424 1.0277-0.53434 0.68519-0.8015 1.5212-0.8015 2.508v0.18108c0 0.99115 0.26716 1.8293 0.8015 2.5145 0.53865 0.68088 1.3488 1.0213 2.4304 1.0213 0.81013 0 1.4932-0.23909 2.0491-0.71743 0.5602-0.47834 0.84453-1.0881 0.85314-1.8293h-1.7581c-0.0043 0.31889-0.11639 0.57306-0.33616 0.76267-0.21977 0.1853-0.4999 0.27797-0.84033 0.27797-0.55158 0-0.91141-0.20251-1.0795-0.6076-0.16375-0.40939-0.24555-0.88337-0.24555-1.422 0-0.65933 0.08403-1.1916 0.25208-1.5967 0.16806-0.40939 0.52357-0.614 1.0665-0.614 0.37921 0 0.67009 0.12059 0.87262 0.36192 0.20253 0.23702 0.30596 0.52578 0.31027 0.86622h1.7581c-0.0086-0.84032-0.2822-1.5061-0.82085-1.9973-0.53434-0.49127-1.2303-0.73691-2.0878-0.73691zm-13.432 0.12918v7.382c0 0.60331-0.28223 0.90492-0.84674 0.90492-0.19822 0-0.39643-0.01939-0.59465-0.05818v1.4803c0.29734 0.07326 0.60972 0.10983 0.93722 0.10983 0.75411 0 1.338-0.20894 1.7517-0.62695 0.41799-0.41801 0.62707-1.0214 0.62707-1.81v-7.382zm-4.57 1.3769c0.53003 0 0.90284 0.20462 1.1183 0.614 0.21977 0.40508 0.32962 0.88561 0.32962 1.4415 0 0.66364-0.10985 1.1937-0.32962 1.5902-0.21546 0.39646-0.58395 0.59465-1.1054 0.59465-0.53004 0-0.90494-0.19819-1.1247-0.59465-0.21546-0.40077-0.32321-0.88562-0.32321-1.4545 0-0.65072 0.10775-1.1786 0.32321-1.5836 0.21546-0.40508 0.58604-0.6076 1.1118-0.6076zm10.917 0c0.43092 0 0.7454 0.12492 0.94362 0.37486 0.20254 0.24563 0.30819 0.5559 0.3168 0.93081v0.14226h-2.5985c0.06464-0.43094 0.20477-0.78 0.42023-1.0472 0.21546-0.26718 0.52142-0.40075 0.91786-0.40075z" fill="#666" stroke-width=".1773" style="shape-inside:url(#rect2964);white-space:pre"/>
<path d="m16.933 38.766h1.5478q1.2383 0 1.7336-0.49531 0.55722-0.55722 0.55722-1.7955v-15.912q0-1.2383-0.49531-1.7336-0.49531-0.55722-1.7336-0.55722h-1.3002v-4.6435h7.9249l0.37148 4.1482q2.1051-2.6623 4.7673-3.8386 2.7242-1.2383 6.5628-1.2383 9.9062 0 9.9062 10.278v13.497q0 1.2383 0.49531 1.7955 0.55722 0.49531 1.7955 0.49531h1.7336v4.5816h-10.649v-20.308q0-3.0957-1.3002-4.4578-1.3002-1.424-4.2101-1.424-3.1576 0-5.2007 1.7955-2.0431 1.7955-2.0431 5.015v12.507q0 1.2383 0.43339 1.7955 0.49531 0.49531 1.6717 0.49531h1.6098v4.5816h-14.178z" fill="#346598" stroke-linecap="round" stroke-width=".16123"/>
</svg>

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

+7
View File
@@ -61,6 +61,13 @@ more contributions are listed on the project's <a href="https://crowdin.com/proj
<a href="https://github.com/altercation/solarized">Solarized</a> themes by Ethan Schoonover (MIT License)
</p>
<h3>Fonts</h3>
<p>The font used for the main novelWriter logo, mimetype and text banners is:</p>
<p>
Pridi by Cadson Demak (Open Font License, Version 1.1)
</p>
<h3>Special Mentions</h3>
<p>Additional thanks to <a href="https://github.com/johnblommers">@johnblommers</a> who was an early user and who has provided a lot
+7 -37
View File
@@ -2,46 +2,16 @@
<html>
<body>
<h2>Release Notes for 2.1</h2>
<p><i>Released on 17 October 2023</i></p>
<h2>Release Notes for 2.2 Beta 1</h2>
<p><i>Released on 11 November 2023</i></p>
<p>Scroll down for <a href="#patch">Patch Notes</a></p>
<p>The primary focus of this release has been a complete redesign of the Build Tool, that is, the
tool that assembles your project into a manuscript document. The new tool, called the "Manuscript
Build Tool" allows you to define multiple build definitions for your project. The build definitions
are edited in a new Manuscript Build Settings dialog, with a lot more options than the old tool.</p>
<p>The reason for this redesign is a long list of feature requests that could not easily be
accommodated in the old, much simpler tool. Far from all the features have been added yet, but now
that the new tool is in place, they will be gradually added in the coming releases.</p>
<p>The key feature added in this release is the extended control you now have for selecting exactly
what part of your project is included in a given build definition. You have the same filters for
selecting documents and notes, and turning on or off root folders as before, but you can now easily
override on a per-document basis what is included or excluded in addition to the filter.</p>
<p>A second major improvement is a better tool to format your manuscript headings. You no longer
have to look up formatting codes and add them manually. Instead, there is now a heading format
editor in the Build Settings dialog for creating the header format, with syntax highlighting
included.</p>
<h3>Other Changes</h3>
<p>Among other features is a new option to duplicate documents and folders in the project tree. The
duplicate feature is available from the right-click menu. A proper light colour theme has also been
added. In most cases it will be the same as the default theme, depending on your platform.</p>
<p>There are other, minor improvements as well, and a lot of code improvements under the hood. For
a full list of changes, see the detailed changelogs.</p>
<p>This is a beta release of the next release version, and is intended for testing purposes. Please
be careful when using this version on live writing projects, and make sure you take frequent
backups.</p>
<p>Please check the changelog for an overview of changes. The full release notes will be added to
the final release.</p>
<p><i>See also the <a href="https://github.com/vkbo/novelWriter/releases">Releases</a> page.</i></p>
<a name="patch"></a><h2>Patch Notes</h2>
<h3>Patch 2.2.1 &ndash; 5 November 2023</h3>
<p>This is a patch release that fixes a layout issue and internationalisation issues with the new
Manuscript Build tool. It also fixes a number of issues related to bugs in the underlying Qt
framework that affects drag and drop functionality in the project tree. These issues were mostly
only affecting Debian Linux package releases.</p>
<p>Other, minor issues related to updating the editor on colour theme change and project word list
changes have been fixed as well. See the full changelog for more details.</p>
</body>
</html>
+3
View File
@@ -148,6 +148,7 @@ class Config:
self.autoScroll = False # Typewriter-like scrolling
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
self.scrollPastEnd = True # Scroll past end of document, and centre cursor
self.wordCountTimer = 5.0 # Interval for word count update in seconds
self.incNotesWCount = True # The status bar word count includes notes
@@ -577,6 +578,7 @@ class Config:
self.doReplaceDots = conf.rdBool(sec, "repdots", self.doReplaceDots)
self.autoScroll = conf.rdBool(sec, "autoscroll", self.autoScroll)
self.autoScrollPos = conf.rdInt(sec, "autoscrollpos", self.autoScrollPos)
self.scrollPastEnd = conf.rdBool(sec, "scrollpastend", self.scrollPastEnd)
self.fmtSQuoteOpen = conf.rdStr(sec, "fmtsquoteopen", self.fmtSQuoteOpen)
self.fmtSQuoteClose = conf.rdStr(sec, "fmtsquoteclose", self.fmtSQuoteClose)
self.fmtDQuoteOpen = conf.rdStr(sec, "fmtdquoteopen", self.fmtDQuoteOpen)
@@ -701,6 +703,7 @@ class Config:
"repdots": str(self.doReplaceDots),
"autoscroll": str(self.autoScroll),
"autoscrollpos": str(self.autoScrollPos),
"scrollpastend": str(self.scrollPastEnd),
"fmtsquoteopen": str(self.fmtSQuoteOpen),
"fmtsquoteclose": str(self.fmtSQuoteClose),
"fmtdquoteopen": str(self.fmtDQuoteOpen),
+1 -2
View File
@@ -327,8 +327,7 @@ class nwQuotes:
class nwUnicode:
"""Supported unicode character constants and their HTML equivalents.
"""
"""Supported unicode character constants and their HTML equivalents."""
# Unicode Constants
# =================
+39 -23
View File
@@ -64,7 +64,6 @@ SETTINGS_TEMPLATE = {
"text.includeKeywords": (bool, False),
"text.includeBodyText": (bool, True),
"text.addNoteHeadings": (bool, True),
"format.buildLang": (str, "en_GB"),
"format.textFont": (str, CONFIG.textFont),
"format.textSize": (int, 12),
"format.lineHeight": (float, 1.15, 0.75, 3.0),
@@ -107,7 +106,6 @@ SETTINGS_LABELS = {
"text.addNoteHeadings": QT_TRANSLATE_NOOP("Builds", "Add Titles for Notes"),
"format.grpFormat": QT_TRANSLATE_NOOP("Builds", "Text Format"),
"format.buildLang": QT_TRANSLATE_NOOP("Builds", "Language"),
"format.textFont": QT_TRANSLATE_NOOP("Builds", "Font Family"),
"format.textSize": QT_TRANSLATE_NOOP("Builds", "Font Size"),
"format.lineHeight": QT_TRANSLATE_NOOP("Builds", "Line Height"),
@@ -158,6 +156,7 @@ class BuildSettings:
self._uuid = str(uuid.uuid4())
self._path = Path.home()
self._build = ""
self._order = 0
self._format = nwBuildFmt.ODT
self._skipRoot = set()
self._excluded = set()
@@ -166,20 +165,32 @@ class BuildSettings:
self._changed = False
return
@classmethod
def fromDict(cls, data: dict) -> BuildSettings:
"""Create a build settings object from a dict."""
cls = BuildSettings()
cls.unpack(data)
return cls
##
# Properties
##
@property
def name(self) -> str:
"""The build name."""
"""Return the build name."""
return self._name
@property
def buildID(self) -> str:
"""The build ID as a UUID."""
"""Return the build ID as a UUID."""
return self._uuid
@property
def order(self) -> int:
"""Return the build order."""
return self._order
@property
def lastPath(self) -> Path:
"""The last used build path."""
@@ -253,6 +264,12 @@ class BuildSettings:
self._uuid = value
return
def setOrder(self, value: int) -> None:
"""Set the build order."""
if isinstance(value, int):
self._order = value
return
def setLastPath(self, path: Path | str | None) -> None:
"""Set the last used build path."""
if isinstance(path, str):
@@ -400,6 +417,7 @@ class BuildSettings:
"uuid": self._uuid,
"path": str(self._path),
"build": self._build,
"order": self._order,
"format": self._format.name,
"settings": self._settings.copy(),
"content": {
@@ -411,14 +429,15 @@ class BuildSettings:
def unpack(self, data: dict) -> None:
"""Unpack a dictionary and populate the class."""
content = data.get("content", {})
settings = data.get("settings", {})
content = data.get("content", {})
included = content.get("included", [])
excluded = content.get("excluded", [])
skipRoot = content.get("skipRoot", [])
self.setName(data.get("name", ""))
self.setBuildID(data.get("uuid", ""))
self.setOrder(data.get("order", 0))
self.setLastPath(data.get("path", None))
self.setLastBuildName(data.get("build", ""))
@@ -455,9 +474,9 @@ class BuildCollection:
def __init__(self, project: NWProject) -> None:
self._project = project
self._builds = {}
self._lastBuild = ""
self._defaultBuild = ""
self._builds: dict[str, BuildSettings] = {}
self._loadCollection()
return
@@ -485,21 +504,19 @@ class BuildCollection:
def getBuild(self, buildID: str) -> BuildSettings | None:
"""Get a specific build settings object."""
if buildID not in self._builds:
return None
build = BuildSettings()
build.unpack(self._builds[buildID])
return build
return self._builds.get(buildID, None)
##
# Setters
##
def setLastBuild(self, buildID: str) -> None:
def setBuildsState(self, lastBuild: str, order: list[str]) -> None:
"""Set the last active build id."""
if buildID != self._lastBuild:
self._lastBuild = buildID
self._saveCollection()
for i, key in enumerate(order):
if build := self._builds.get(key):
build.setOrder(i)
self._lastBuild = lastBuild
self._saveCollection()
return
def setDefaultBuild(self, buildID: str) -> None:
@@ -512,8 +529,7 @@ class BuildCollection:
def setBuild(self, build: BuildSettings) -> None:
"""Set build settings data in the collection."""
if isinstance(build, BuildSettings):
buildID = build.buildID
self._builds[buildID] = build.pack()
self._builds[build.buildID] = build
self._saveCollection()
return
@@ -522,15 +538,15 @@ class BuildCollection:
##
def removeBuild(self, buildID: str) -> None:
"""Remove the a build from the collection."""
"""Remove a build from the collection."""
self._builds.pop(buildID, None)
self._saveCollection()
return
def builds(self) -> Iterable[tuple[str, str]]:
"""Iterate over all available builds."""
for buildID in self._builds:
yield buildID, self._builds[buildID].get("name", "")
for buildID, build in sorted(self._builds.items(), key=lambda x: x[1].order):
yield buildID, build.name
return
##
@@ -569,7 +585,7 @@ class BuildCollection:
elif key == "defaultBuild":
self._defaultBuild = str(entry)
elif isinstance(entry, dict):
self._builds[key] = entry
self._builds[key] = BuildSettings.fromDict(entry)
return True
@@ -581,11 +597,11 @@ class BuildCollection:
logger.debug("Saving builds file")
try:
data = {
data: dict[str, str | dict] = {
"lastBuild": self._lastBuild,
"defaultBuild": self._defaultBuild,
}
data.update(self._builds)
data.update({k: b.pack() for k, b in self._builds.items()})
with open(buildsFile, mode="w+", encoding="utf-8") as outFile:
outFile.write(jsonEncode({"novelWriter.builds": data}, nmax=4))
except Exception:
+2
View File
@@ -353,11 +353,13 @@ class ProjectBuilder:
projName = data.get("projName", lblNewProject)
projTitle = data.get("projTitle", lblNewProject)
projAuthor = data.get("projAuthor", "")
projLang = data.get("projLang", "en_GB")
project.data.setUuid(None)
project.data.setName(projName)
project.data.setTitle(projTitle)
project.data.setAuthor(projAuthor)
project.data.setLanguage(projLang)
project.setDefaultStatusImport()
project.session.startSession()
+1 -5
View File
@@ -248,13 +248,9 @@ class NWBuildDocument:
def _setupBuild(self, bldObj: Tokenizer) -> dict:
"""Configure the build object."""
# Get Settings
buildLang = self._build.getStr("format.buildLang")
textFont = self._build.getStr("format.textFont")
textSize = self._build.getInt("format.textSize")
# The language lookup dict is reloaded if needed
self._project.setProjectLang(buildLang)
fontFamily = textFont or CONFIG.textFont
bldFont = QFont(fontFamily, textSize)
fontInfo = QFontInfo(bldFont)
@@ -287,7 +283,7 @@ class NWBuildDocument:
if isinstance(bldObj, ToOdt):
bldObj.setColourHeaders(self._build.getBool("odt.addColours"))
bldObj.setLanguage(buildLang)
bldObj.setLanguage(self._project.data.language)
scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0)
pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0))
+2 -2
View File
@@ -152,14 +152,14 @@ class NWProjectData:
"""Return the initial count of words for novel and note
documents.
"""
return tuple(self._initCounts)
return self._initCounts[0], self._initCounts[1]
@property
def currCounts(self) -> tuple[int, int]:
"""Return the current count of words for novel and note
documents.
"""
return tuple(self._currCounts)
return self._currCounts[0], self._currCounts[1]
@property
def lastHandle(self) -> dict[str, str | None]:
+1 -2
View File
@@ -456,11 +456,10 @@ class ToHtml(Tokenizer):
def _formatSynopsis(self, text: str) -> str:
"""Apply HTML formatting to synopsis."""
sSynop = self._localLookup("Synopsis")
if self._genMode == self.M_PREVIEW:
sSynop = self._trSynopsis
return f"<p class='comment'><span class='synopsis'>{sSynop}:</span> {text}</p>\n"
else:
sSynop = self._localLookup("Synopsis")
return f"<p class='synopsis'><strong>{sSynop}:</strong> {text}</p>\n"
def _formatComments(self, text: str) -> str:
-3
View File
@@ -167,9 +167,6 @@ class Tokenizer(ABC):
self._localLookup = self._project.localLookup
self.tr = partial(QCoreApplication.translate, "Tokenizer")
# Cached Translations
self._trSynopsis = self.tr("Synopsis")
# Format RegEx
self._rxMarkdown = [
(QRegularExpression(nwRegEx.FMT_EI), [0, self.FMT_I_B, 0, self.FMT_I_E]),
+80 -82
View File
@@ -26,7 +26,7 @@ from __future__ import annotations
import logging
from PyQt5.QtGui import QFont
from PyQt5.QtCore import Qt
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import (
QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox,
QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox
@@ -43,12 +43,11 @@ logger = logging.getLogger(__name__)
class GuiPreferences(NPagedDialog):
def __init__(self, mainGui):
super().__init__(parent=mainGui)
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
logger.debug("Create: GuiPreferences")
self.setObjectName("GuiPreferences")
self.setWindowTitle(self.tr("Preferences"))
self.tabGeneral = GuiPreferencesGeneral(self)
@@ -93,26 +92,27 @@ class GuiPreferences(NPagedDialog):
##
@property
def updateTheme(self):
def updateTheme(self) -> bool:
return self._updateTheme
@property
def updateSyntax(self):
def updateSyntax(self) -> bool:
return self._updateSyntax
@property
def needsRestart(self):
def needsRestart(self) -> bool:
return self._needsRestart
@property
def refreshTree(self):
def refreshTree(self) -> bool:
return self._refreshTree
##
# Slots
# Private Slots
##
def _doSave(self):
@pyqtSlot()
def _doSave(self) -> None:
"""Trigger all the save functions in the tabs, and collect the
status of the saves.
"""
@@ -132,9 +132,9 @@ class GuiPreferences(NPagedDialog):
return
def _doClose(self):
"""Close the preferences without saving the changes.
"""
@pyqtSlot()
def _doClose(self) -> None:
"""Close the preferences without saving the changes."""
self._saveWindowSize()
self.reject()
return
@@ -143,9 +143,8 @@ class GuiPreferences(NPagedDialog):
# Internal Functions
##
def _saveWindowSize(self):
"""Save the dialog window size.
"""
def _saveWindowSize(self) -> None:
"""Save the dialog window size."""
CONFIG.setPreferencesWinSize(self.width(), self.height())
return
@@ -154,7 +153,7 @@ class GuiPreferences(NPagedDialog):
class GuiPreferencesGeneral(QWidget):
def __init__(self, prefsGui):
def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui)
self.prefsGui = prefsGui
@@ -285,9 +284,8 @@ class GuiPreferencesGeneral(QWidget):
return
def saveValues(self):
"""Save the values set for this tab.
"""
def saveValues(self) -> None:
"""Save the values set for this tab."""
guiLocale = self.guiLocale.currentData()
guiTheme = self.guiTheme.currentData()
guiSyntax = self.guiSyntax.currentData()
@@ -316,12 +314,12 @@ class GuiPreferencesGeneral(QWidget):
return
##
# Slots
# Private Slots
##
def _selectFont(self):
"""Open the QFontDialog and set a font for the font style.
"""
@pyqtSlot()
def _selectFont(self) -> None:
"""Open the QFontDialog and set a font for the font style."""
currFont = QFont()
currFont.setFamily(CONFIG.guiFont)
currFont.setPointSize(CONFIG.guiFontSize)
@@ -336,7 +334,7 @@ class GuiPreferencesGeneral(QWidget):
class GuiPreferencesProjects(QWidget):
def __init__(self, prefsGui):
def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui)
# The Form
@@ -438,9 +436,8 @@ class GuiPreferencesProjects(QWidget):
return
def saveValues(self):
"""Save the values set for this tab.
"""
def saveValues(self) -> None:
"""Save the values set for this tab."""
# Automatic Save
CONFIG.autoSaveDoc = self.autoSaveDoc.value()
CONFIG.autoSaveProj = self.autoSaveProj.value()
@@ -457,12 +454,12 @@ class GuiPreferencesProjects(QWidget):
return
##
# Slots
# Private Slots
##
def _backupFolder(self):
"""Open a dialog to select the backup folder.
"""
@pyqtSlot()
def _backupFolder(self) -> None:
"""Open a dialog to select the backup folder."""
currDir = self.backupPath or ""
newDir = QFileDialog.getExistingDirectory(
self, self.tr("Backup Directory"), str(currDir), options=QFileDialog.ShowDirsOnly
@@ -472,15 +469,13 @@ class GuiPreferencesProjects(QWidget):
self.mainForm.setHelpText(
self.backupPathRow, self.tr("Path: {0}").format(self.backupPath)
)
return True
return
return
return False
def _toggledBackupOnClose(self, theState):
"""Enable or disable switch that depends on the backup on close
switch.
"""
self.askBeforeBackup.setEnabled(theState)
@pyqtSlot(bool)
def _toggledBackupOnClose(self, state: bool) -> None:
"""Toggle switch that depends on the backup on close switch."""
self.askBeforeBackup.setEnabled(state)
return
# END Class GuiPreferencesProjects
@@ -488,7 +483,7 @@ class GuiPreferencesProjects(QWidget):
class GuiPreferencesDocuments(QWidget):
def __init__(self, prefsGui):
def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui)
# The Form
@@ -604,9 +599,8 @@ class GuiPreferencesDocuments(QWidget):
return
def saveValues(self):
"""Save the values set for this tab.
"""
def saveValues(self) -> None:
"""Save the values set for this tab."""
# Text Style
CONFIG.setTextFont(self.textFont.text(), self.textSize.value())
@@ -621,12 +615,12 @@ class GuiPreferencesDocuments(QWidget):
return
##
# Slots
# Private Slots
##
@pyqtSlot()
def _selectFont(self):
"""Open the QFontDialog and set a font for the font style.
"""
"""Open the QFontDialog and set a font for the font style."""
currFont = QFont()
currFont.setFamily(CONFIG.textFont)
currFont.setPointSize(CONFIG.textSize)
@@ -642,7 +636,7 @@ class GuiPreferencesDocuments(QWidget):
class GuiPreferencesEditor(QWidget):
def __init__(self, prefsGui):
def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui)
# The Form
@@ -726,6 +720,15 @@ class GuiPreferencesEditor(QWidget):
# ================
self.mainForm.addGroupLabel(self.tr("Scroll Behaviour"))
# Scroll Past End
self.scrollPastEnd = NSwitch()
self.scrollPastEnd.setChecked(CONFIG.scrollPastEnd)
self.mainForm.addRow(
self.tr("Scroll past end of the document"),
self.scrollPastEnd,
self.tr("Also centres the cursor when scrolling.")
)
# Typewriter Scrolling
self.autoScroll = NSwitch()
self.autoScroll.setChecked(CONFIG.autoScroll)
@@ -750,7 +753,7 @@ class GuiPreferencesEditor(QWidget):
return
def saveValues(self):
def saveValues(self) -> None:
"""Save the values set for this tab."""
# Spell Checking
CONFIG.spellLanguage = self.spellLanguage.currentData()
@@ -766,6 +769,7 @@ class GuiPreferencesEditor(QWidget):
# Scroll Behaviour
CONFIG.autoScroll = self.autoScroll.isChecked()
CONFIG.autoScrollPos = self.autoScrollPos.value()
CONFIG.scrollPastEnd = self.scrollPastEnd.isChecked()
return
@@ -774,7 +778,7 @@ class GuiPreferencesEditor(QWidget):
class GuiPreferencesSyntax(QWidget):
def __init__(self, prefsGui):
def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui)
self.prefsGui = prefsGui
@@ -840,9 +844,8 @@ class GuiPreferencesSyntax(QWidget):
return
def saveValues(self):
"""Save the values set for this tab.
"""
def saveValues(self) -> None:
"""Save the values set for this tab."""
highlightQuotes = self.highlightQuotes.isChecked()
allowOpenSQuote = self.allowOpenSQuote.isChecked()
allowOpenDQuote = self.allowOpenDQuote.isChecked()
@@ -862,15 +865,14 @@ class GuiPreferencesSyntax(QWidget):
return
##
# Slots
# Private Slots
##
def _toggleHighlightQuotes(self, theState):
"""Enables or disables switches controlled by the highlight
quotes switch.
"""
self.allowOpenSQuote.setEnabled(theState)
self.allowOpenDQuote.setEnabled(theState)
@pyqtSlot(bool)
def _toggleHighlightQuotes(self, state: bool) -> None:
"""Toggle switches controlled by the highlight quotes switch."""
self.allowOpenSQuote.setEnabled(state)
self.allowOpenDQuote.setEnabled(state)
return
# END Class GuiPreferencesSyntax
@@ -878,7 +880,7 @@ class GuiPreferencesSyntax(QWidget):
class GuiPreferencesAutomation(QWidget):
def __init__(self, prefsGui):
def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui)
# The Form
@@ -989,9 +991,8 @@ class GuiPreferencesAutomation(QWidget):
return
def saveValues(self):
"""Save the values set for this tab.
"""
def saveValues(self) -> None:
"""Save the values set for this tab."""
# Automatic Features
CONFIG.autoSelect = self.autoSelect.isChecked()
CONFIG.doReplace = self.doReplace.isChecked()
@@ -1010,18 +1011,17 @@ class GuiPreferencesAutomation(QWidget):
return
##
# Slots
# Private Slots
##
def _toggleAutoReplaceMain(self, theState):
"""Enables or disables switches controlled by the main auto
replace switch.
"""
self.doReplaceSQuote.setEnabled(theState)
self.doReplaceDQuote.setEnabled(theState)
self.doReplaceDash.setEnabled(theState)
self.doReplaceDots.setEnabled(theState)
self.fmtPadThin.setEnabled(theState)
@pyqtSlot(bool)
def _toggleAutoReplaceMain(self, state: bool) -> None:
"""Toggle switches controlled by the auto replace switch."""
self.doReplaceSQuote.setEnabled(state)
self.doReplaceDQuote.setEnabled(state)
self.doReplaceDash.setEnabled(state)
self.doReplaceDots.setEnabled(state)
self.fmtPadThin.setEnabled(state)
return
# END Class GuiPreferencesAutomation
@@ -1029,7 +1029,7 @@ class GuiPreferencesAutomation(QWidget):
class GuiPreferencesQuotes(QWidget):
def __init__(self, prefsGui):
def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui)
# The Form
@@ -1113,9 +1113,8 @@ class GuiPreferencesQuotes(QWidget):
return
def saveValues(self):
"""Save the values set for this tab.
"""
def saveValues(self) -> None:
"""Save the values set for this tab."""
# Quotation Style
CONFIG.fmtSQuoteOpen = self.quoteSym["SO"].text()
CONFIG.fmtSQuoteClose = self.quoteSym["SC"].text()
@@ -1124,12 +1123,11 @@ class GuiPreferencesQuotes(QWidget):
return
##
# Slots
# Internal Functions
##
def _getQuote(self, qType):
"""Dialog for single quote open.
"""
def _getQuote(self, qType: str) -> None:
"""Dialog for single quote open."""
qtBox = GuiQuoteSelect(self, currentQuote=self.quoteSym[qType].text())
if qtBox.exec_() == QDialog.Accepted:
self.quoteSym[qType].setText(qtBox.selectedQuote)
+101 -110
View File
@@ -96,21 +96,22 @@ class GuiProjectSettings(NPagedDialog):
return
def __del__(self): # pragma: no cover
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiProjectSettings")
return
##
# Slots
# Private Slots
##
def _doSave(self):
"""Save settings and close dialog.
"""
project = SHARED.project
@pyqtSlot()
def _doSave(self) -> None:
"""Save settings and close dialog."""
project = SHARED.project
projName = self.tabMain.editName.text()
bookTitle = self.tabMain.editTitle.text()
bookAuthor = self.tabMain.editAuthor.text()
projLang = self.tabMain.projLang.currentData()
spellLang = self.tabMain.spellLang.currentData()
doBackup = not self.tabMain.doBackup.isChecked()
@@ -119,6 +120,7 @@ class GuiProjectSettings(NPagedDialog):
project.data.setAuthor(bookAuthor)
project.data.setDoBackup(doBackup)
project.data.setSpellLang(spellLang)
project.setProjectLang(projLang)
if self.tabStatus.colChanged:
newList, delList = self.tabStatus.getNewList()
@@ -140,9 +142,9 @@ class GuiProjectSettings(NPagedDialog):
return
def _doClose(self):
"""Save settings and close the dialog.
"""
@pyqtSlot()
def _doClose(self) -> None:
"""Save settings and close the dialog."""
self._saveGuiSettings()
self.reject()
return
@@ -151,9 +153,8 @@ class GuiProjectSettings(NPagedDialog):
# Internal Functions
##
def _focusTab(self, tab):
"""Change which is the focused tab.
"""
def _focusTab(self, tab: int) -> None:
"""Change which is the focused tab."""
if tab == self.TAB_MAIN:
self.setCurrentWidget(self.tabMain)
elif tab == self.TAB_STATUS:
@@ -164,9 +165,8 @@ class GuiProjectSettings(NPagedDialog):
self.setCurrentWidget(self.tabReplace)
return
def _saveGuiSettings(self):
"""Save GUI settings.
"""
def _saveGuiSettings(self) -> None:
"""Save GUI settings."""
winWidth = CONFIG.rpxInt(self.width())
winHeight = CONFIG.rpxInt(self.height())
replaceColW = CONFIG.rpxInt(self.tabReplace.listBox.columnWidth(0))
@@ -187,8 +187,8 @@ class GuiProjectSettings(NPagedDialog):
class GuiProjectEditMain(QWidget):
def __init__(self, projGui):
super().__init__(parent=projGui)
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
# The Form
self.mainForm = NConfigLayout()
@@ -230,24 +230,41 @@ class GuiProjectEditMain(QWidget):
self.tr("Change whenever you want!")
)
self.projLang = QComboBox(self)
self.projLang.setMaximumWidth(xW)
for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ):
self.projLang.addItem(language, tag)
self.mainForm.addRow(
self.tr("Project language"),
self.projLang,
self.tr("Used when building the manuscript.")
)
langIdx = 0
if pData.language is not None:
langIdx = self.projLang.findData(pData.language)
if langIdx == -1:
langIdx = self.projLang.findData("en_GB")
if langIdx != -1:
self.projLang.setCurrentIndex(langIdx)
self.spellLang = QComboBox(self)
self.spellLang.setMaximumWidth(xW)
self.spellLang.addItem(self.tr("Default"), "None")
if CONFIG.hasEnchant:
for tag, language in SHARED.spelling.listDictionaries():
self.spellLang.addItem(language, tag)
self.mainForm.addRow(
self.tr("Spell check language"),
self.spellLang,
self.tr("Overrides main preferences.")
)
spellIdx = 0
langIdx = 0
if pData.spellLang is not None:
spellIdx = self.spellLang.findData(pData.spellLang)
if spellIdx != -1:
self.spellLang.setCurrentIndex(spellIdx)
langIdx = self.spellLang.findData(pData.spellLang)
if langIdx != -1:
self.spellLang.setCurrentIndex(langIdx)
self.doBackup = NSwitch(self)
self.doBackup.setChecked(not pData.doBackup)
@@ -271,8 +288,8 @@ class GuiProjectEditStatus(QWidget):
COL_ROLE = Qt.ItemDataRole.UserRole + 1
NUM_ROLE = Qt.ItemDataRole.UserRole + 2
def __init__(self, projGui, isStatus):
super().__init__(parent=projGui)
def __init__(self, parent: QWidget, isStatus: bool) -> None:
super().__init__(parent=parent)
if isStatus:
self.theStatus = SHARED.project.data.itemStatus
@@ -372,9 +389,8 @@ class GuiProjectEditStatus(QWidget):
return
def getNewList(self):
"""Return list of entries.
"""
def getNewList(self) -> tuple[list, list]:
"""Return list of entries."""
if self.colChanged:
newList = []
for n in range(self.listBox.topLevelItemCount()):
@@ -394,9 +410,8 @@ class GuiProjectEditStatus(QWidget):
##
@pyqtSlot()
def _selectColour(self):
"""Open a dialog to select the status icon colour.
"""
def _selectColour(self) -> None:
"""Open a dialog to select the status icon colour."""
if self.selColour is not None:
newCol = QColorDialog.getColor(
self.selColour, self, self.tr("Select Colour")
@@ -410,17 +425,15 @@ class GuiProjectEditStatus(QWidget):
return
@pyqtSlot()
def _newItem(self):
"""Create a new status item.
"""
def _newItem(self) -> None:
"""Create a new status item."""
self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
self.colChanged = True
return
@pyqtSlot()
def _delItem(self):
"""Delete a status item.
"""
def _delItem(self) -> None:
"""Delete a status item."""
selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem):
iRow = self.listBox.indexOfTopLevelItem(selItem)
@@ -433,9 +446,8 @@ class GuiProjectEditStatus(QWidget):
return
@pyqtSlot()
def _saveItem(self):
"""Save changes made to a status item.
"""
def _saveItem(self) -> None:
"""Save changes made to a status item."""
selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem):
selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
@@ -444,11 +456,10 @@ class GuiProjectEditStatus(QWidget):
self.selColour.red(), self.selColour.green(), self.selColour.blue()
))
self.colChanged = True
return
@pyqtSlot()
def _selectedItem(self):
def _selectedItem(self) -> None:
"""Extract the info of a selected item and populate the settings
boxes and button. If no item is selected, clear the form.
"""
@@ -456,7 +467,6 @@ class GuiProjectEditStatus(QWidget):
if isinstance(selItem, QTreeWidgetItem):
cols = selItem.data(self.COL_LABEL, self.COL_ROLE)
name = selItem.text(self.COL_LABEL)
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(*cols))
self.selColour = QColor(*cols)
@@ -464,31 +474,27 @@ class GuiProjectEditStatus(QWidget):
self.colButton.setIcon(QIcon(pixmap))
self.editName.selectAll()
self.editName.setFocus()
self.editName.setEnabled(True)
self.colButton.setEnabled(True)
self.saveButton.setEnabled(True)
else:
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(100, 100, 100))
self.selColour = QColor(100, 100, 100)
self.editName.setText("")
self.colButton.setIcon(QIcon(pixmap))
self.editName.setEnabled(False)
self.colButton.setEnabled(False)
self.saveButton.setEnabled(False)
return
##
# Internal Functions
##
def _addItem(self, key, name, cols, count):
"""Add a status item to the list.
"""
def _addItem(self, key: str | None, name: str,
cols: tuple[int, int, int], count: int) -> None:
"""Add a status item to the list."""
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(*cols))
@@ -504,9 +510,8 @@ class GuiProjectEditStatus(QWidget):
return
def _moveItem(self, step):
"""Move and item up or down step.
"""
def _moveItem(self, step: int) -> None:
"""Move and item up or down step."""
selItem = self._getSelectedItem()
if selItem is None:
return
@@ -527,17 +532,15 @@ class GuiProjectEditStatus(QWidget):
return
def _getSelectedItem(self):
"""Get the currently selected item.
"""
def _getSelectedItem(self) -> QTreeWidgetItem | None:
"""Get the currently selected item."""
selItem = self.listBox.selectedItems()
if len(selItem) > 0:
return selItem[0]
return None
def _usageString(self, nUse):
"""Generate usage string.
"""
def _usageString(self, nUse: int) -> str:
"""Generate usage string."""
if nUse == 0:
return self.tr("Not in use")
elif nUse == 1:
@@ -553,8 +556,8 @@ class GuiProjectEditReplace(QWidget):
COL_KEY = 0
COL_REPL = 1
def __init__(self, projGui):
super().__init__(parent=projGui)
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
self.arChanged = False
@@ -635,25 +638,23 @@ class GuiProjectEditReplace(QWidget):
return
def getNewList(self):
"""Extract the list from the widget.
"""
newList = {}
def getNewList(self) -> dict:
"""Extract the list from the widget."""
new = {}
for n in range(self.listBox.topLevelItemCount()):
tItem = self.listBox.topLevelItem(n)
if tItem is not None:
aKey = self._stripNotAllowed(tItem.text(0))
aVal = tItem.text(1)
if len(aKey) > 0:
newList[aKey] = aVal
return newList
new[aKey] = aVal
return new
##
# Internal Functions
##
def _selectedItem(self):
def _selectedItem(self) -> bool:
"""Extract the details from the selected item and populate the
edit form.
"""
@@ -670,63 +671,53 @@ class GuiProjectEditReplace(QWidget):
self.editKey.setFocus()
return True
def _saveEntry(self):
"""Save the form data into the list widget.
"""
def _saveEntry(self) -> None:
"""Save the form data into the list widget."""
selItem = self._getSelectedItem()
if selItem is None:
return False
newKey = self.editKey.text()
newVal = self.editValue.text()
saveKey = self._stripNotAllowed(newKey)
if len(saveKey) > 0 and len(newVal) > 0:
selItem.setText(self.COL_KEY, "<%s>" % saveKey)
selItem.setText(self.COL_REPL, newVal)
self.editKey.clear()
self.editValue.clear()
self.editKey.setEnabled(False)
self.editValue.setEnabled(False)
self.listBox.clearSelection()
self.arChanged = True
if selItem:
newKey = self.editKey.text()
newVal = self.editValue.text()
saveKey = self._stripNotAllowed(newKey)
if len(saveKey) > 0 and len(newVal) > 0:
selItem.setText(self.COL_KEY, "<%s>" % saveKey)
selItem.setText(self.COL_REPL, newVal)
self.editKey.clear()
self.editValue.clear()
self.editKey.setEnabled(False)
self.editValue.setEnabled(False)
self.listBox.clearSelection()
self.arChanged = True
return
def _addEntry(self):
"""Add a new list entry.
"""
def _addEntry(self) -> None:
"""Add a new list entry."""
saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
newVal = ""
newItem = QTreeWidgetItem([saveKey, newVal])
self.listBox.addTopLevelItem(newItem)
return True
return
def _delEntry(self):
"""Delete the selected entry.
"""
def _delEntry(self) -> None:
"""Delete the selected entry."""
selItem = self._getSelectedItem()
if selItem is None:
return False
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
self.arChanged = True
return True
if selItem:
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
self.arChanged = True
return
def _getSelectedItem(self):
"""Extract the currently selected item.
"""
def _getSelectedItem(self) -> QTreeWidgetItem | None:
"""Extract the currently selected item."""
selItem = self.listBox.selectedItems()
if len(selItem) == 0:
return None
return selItem[0]
def _stripNotAllowed(self, theKey):
"""Clean up the replace key string.
"""
retKey = ""
for c in theKey:
def _stripNotAllowed(self, key: str) -> str:
"""Clean up the replace key string."""
result = ""
for c in key:
if c.isalnum():
retKey += c
return retKey
result += c
return result
# END Class GuiProjectEditReplace
+99 -56
View File
@@ -10,6 +10,8 @@ Created: 2020-04-25 [0.4.5] GuiDocEditHeader
Rewritten: 2020-06-15 [0.9] GuiDocEditSearch
Created: 2020-06-27 [0.10] GuiDocEditFooter
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
Created: 2023-11-06 [2.2b1] MetaCompleter
Created: 2023-11-07 [2.2b1] GuiDocToolBar
This file is a part of novelWriter
Copyright 20182023, Veronica Berglyd Olsen
@@ -67,6 +69,16 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__)
class _SelectAction(Enum):
NO_DECISION = 0
KEEP_SELECTION = 1
KEEP_POSITION = 2
MOVE_AFTER = 3
# END Class _SelectAction
class GuiDocEditor(QPlainTextEdit):
"""Gui Widget: Main Document Editor"""
@@ -152,7 +164,6 @@ class GuiDocEditor(QPlainTextEdit):
self.setMinimumWidth(CONFIG.pxInt(300))
self.setAutoFillBackground(True)
self.setFrameStyle(QFrame.NoFrame)
self.setCenterOnScroll(True)
# Custom Shortcuts
self.keyContext = QShortcut(self)
@@ -330,7 +341,8 @@ class GuiDocEditor(QPlainTextEdit):
self._qDocument.setDefaultTextOption(options)
# Scroll bars
# Scrolling
self.setCenterOnScroll(CONFIG.scrollPastEnd)
if CONFIG.hideVScroll:
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
else:
@@ -996,7 +1008,9 @@ class GuiDocEditor(QPlainTextEdit):
return
text = block.text()
if text.startswith("@"):
if text.startswith("@") and added + removed == 1:
# Only run on single keypresses, otherwise it will trigger
# at unwanted times when other changes are made to the document
cursor = self.textCursor()
bPos = cursor.positionInBlock()
if bPos > 0:
@@ -1004,8 +1018,10 @@ class GuiDocEditor(QPlainTextEdit):
point = self.cursorRect().bottomRight()
self._completer.move(self.viewport().mapToGlobal(point))
self._completer.setVisible(show)
else:
self._completer.setVisible(False)
elif self._doReplace and added == 1:
if self._doReplace and added == 1:
self._docAutoReplace(text)
return
@@ -1410,17 +1426,28 @@ class GuiDocEditor(QPlainTextEdit):
If more than one block is selected, the formatting is applied to
the first block.
"""
cursor = self._autoSelect()
if not cursor.hasSelection():
logger.warning("No selection made, nothing to do")
return False
cursor = self.textCursor()
posO = cursor.position()
if cursor.hasSelection():
select = _SelectAction.KEEP_SELECTION
else:
cursor = self._autoSelect()
if cursor.hasSelection() and posO == cursor.selectionEnd():
select = _SelectAction.MOVE_AFTER
else:
select = _SelectAction.KEEP_POSITION
posS = cursor.selectionStart()
posE = cursor.selectionEnd()
if self._qDocument.characterAt(posO - 1) == fChar:
logger.warning("Format repetition, cancelling action")
cursor.clearSelection()
cursor.setPosition(posO)
self.setTextCursor(cursor)
return False
blockS = self._qDocument.findBlock(posS)
blockE = self._qDocument.findBlock(posE)
if blockS != blockE:
posE = blockS.position() + blockS.length() - 1
cursor.clearSelection()
@@ -1443,34 +1470,26 @@ class GuiDocEditor(QPlainTextEdit):
break
if fLen == min(numA, numB):
self._clearSurrounding(cursor, fLen)
cursor.clearSelection()
cursor.beginEditBlock()
cursor.setPosition(posS)
for i in range(fLen):
cursor.deletePreviousChar()
cursor.setPosition(posE)
for i in range(fLen):
cursor.deletePreviousChar()
cursor.endEditBlock()
cursor.clearSelection()
cursor.setPosition(posO - fLen)
self.setTextCursor(cursor)
else:
self._wrapSelection(fChar*fLen)
self._wrapSelection(fChar*fLen, pos=posO, select=select)
return True
def _clearSurrounding(self, cursor: QTextCursor, nChars: int) -> bool:
"""Clear n characters before and after the cursor."""
if not cursor.hasSelection():
logger.warning("No selection made, nothing to do")
return False
posS = cursor.selectionStart()
posE = cursor.selectionEnd()
cursor.clearSelection()
cursor.beginEditBlock()
cursor.setPosition(posS)
for i in range(nChars):
cursor.deletePreviousChar()
cursor.setPosition(posE)
for i in range(nChars):
cursor.deletePreviousChar()
cursor.endEditBlock()
cursor.clearSelection()
return True
def _wrapSelection(self, before: str, after: str | None = None) -> bool:
def _wrapSelection(self, before: str, after: str | None = None, pos: int | None = None,
select: _SelectAction = _SelectAction.NO_DECISION) -> bool:
"""Wrap the selected text in whatever is in tBefore and tAfter.
If there is no selection, the autoSelect setting decides the
action. AutoSelect will select the word under the cursor before
@@ -1479,10 +1498,17 @@ class GuiDocEditor(QPlainTextEdit):
if after is None:
after = before
cursor = self._autoSelect()
if not cursor.hasSelection():
logger.warning("No selection made, nothing to do")
return False
cursor = self.textCursor()
posO = pos if isinstance(pos, int) else cursor.position()
if select == _SelectAction.NO_DECISION:
if cursor.hasSelection():
select = _SelectAction.KEEP_SELECTION
else:
cursor = self._autoSelect()
if cursor.hasSelection() and posO == cursor.selectionEnd():
select = _SelectAction.MOVE_AFTER
else:
select = _SelectAction.KEEP_POSITION
posS = cursor.selectionStart()
posE = cursor.selectionEnd()
@@ -1500,8 +1526,14 @@ class GuiDocEditor(QPlainTextEdit):
cursor.insertText(before)
cursor.endEditBlock()
cursor.setPosition(posE + len(before), QTextCursor.MoveAnchor)
cursor.setPosition(posS + len(before), QTextCursor.KeepAnchor)
if select == _SelectAction.MOVE_AFTER:
cursor.setPosition(posE + len(before + after))
elif select == _SelectAction.KEEP_SELECTION:
cursor.setPosition(posE + len(before), QTextCursor.MoveMode.MoveAnchor)
cursor.setPosition(posS + len(before), QTextCursor.MoveMode.KeepAnchor)
elif select == _SelectAction.KEEP_POSITION:
cursor.setPosition(posO + len(before))
self.setTextCursor(cursor)
return True
@@ -1908,27 +1940,38 @@ class GuiDocEditor(QPlainTextEdit):
def _autoSelect(self) -> QTextCursor:
"""Return a cursor which may or may not have a selection based
on user settings and document action.
on user settings and document action. The selection will be the
word closest to the cursor consisting of alphanumerical unicode
characters.
"""
cursor = self.textCursor()
if CONFIG.autoSelect and not cursor.hasSelection():
cursor.select(QTextCursor.WordUnderCursor)
posS = cursor.selectionStart()
posE = cursor.selectionEnd()
cPos = cursor.position()
bPos = cursor.block().position()
bLen = cursor.block().length()
# Underscore counts as a part of the word, so check that the
# selection isn't wrapped in italics markers.
reSelect = False
if self._qDocument.characterAt(posS) == "_":
posS += 1
reSelect = True
if self._qDocument.characterAt(posE) == "_":
posE -= 1
reSelect = True
if reSelect:
cursor.clearSelection()
cursor.setPosition(posS, QTextCursor.MoveAnchor)
cursor.setPosition(posE-1, QTextCursor.KeepAnchor)
# Scan backwards
sPos = cPos
for i in range(cPos - bPos):
sPos = cPos - i - 1
if not self._qDocument.characterAt(sPos).isalnum():
sPos += 1
break
# Scan forwards
ePos = cPos
for i in range(bPos + bLen - cPos):
ePos = cPos + i
if not self._qDocument.characterAt(ePos).isalnum():
break
if ePos - sPos <= 0:
# No selection possible
return cursor
cursor.clearSelection()
cursor.setPosition(sPos, QTextCursor.MoveAnchor)
cursor.setPosition(ePos, QTextCursor.KeepAnchor)
self.setTextCursor(cursor)
+36 -20
View File
@@ -54,6 +54,7 @@ class GuiMainMenu(QMenuBar):
requestDocInsert = pyqtSignal(nwDocInsert)
requestDocInsertText = pyqtSignal(str)
requestDocKeyWordInsert = pyqtSignal(str)
requestFocusChange = pyqtSignal(nwWidget)
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
@@ -173,7 +174,7 @@ class GuiMainMenu(QMenuBar):
# Project > Delete
self.aDeleteItem = self.projMenu.addAction(self.tr("Delete Item"))
self.aDeleteItem.setShortcut("Ctrl+Shift+Del")
self.aDeleteItem.setShortcuts(["Ctrl+Del", "Ctrl+Shift+Del"]) # Latter is deprecated
self.aDeleteItem.triggered.connect(lambda: self.mainGui.projView.requestDeleteItem(None))
# Project > Empty Trash
@@ -246,12 +247,16 @@ class GuiMainMenu(QMenuBar):
# Edit > Undo
self.aEditUndo = self.editMenu.addAction(self.tr("Undo"))
self.aEditUndo.setShortcut("Ctrl+Z")
self.aEditUndo.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.UNDO))
self.aEditUndo.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.UNDO)
)
# Edit > Redo
self.aEditRedo = self.editMenu.addAction(self.tr("Redo"))
self.aEditRedo.setShortcut("Ctrl+Y")
self.aEditRedo.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.REDO))
self.aEditRedo.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.REDO)
)
# Edit > Separator
self.editMenu.addSeparator()
@@ -259,17 +264,23 @@ class GuiMainMenu(QMenuBar):
# Edit > Cut
self.aEditCut = self.editMenu.addAction(self.tr("Cut"))
self.aEditCut.setShortcut("Ctrl+X")
self.aEditCut.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.CUT))
self.aEditCut.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.CUT)
)
# Edit > Copy
self.aEditCopy = self.editMenu.addAction(self.tr("Copy"))
self.aEditCopy.setShortcut("Ctrl+C")
self.aEditCopy.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.COPY))
self.aEditCopy.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.COPY)
)
# Edit > Paste
self.aEditPaste = self.editMenu.addAction(self.tr("Paste"))
self.aEditPaste.setShortcut("Ctrl+V")
self.aEditPaste.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.PASTE))
self.aEditPaste.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.PASTE)
)
# Edit > Separator
self.editMenu.addSeparator()
@@ -277,12 +288,16 @@ class GuiMainMenu(QMenuBar):
# Edit > Select All
self.aSelectAll = self.editMenu.addAction(self.tr("Select All"))
self.aSelectAll.setShortcut("Ctrl+A")
self.aSelectAll.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.SEL_ALL))
self.aSelectAll.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SEL_ALL)
)
# Edit > Select Paragraph
self.aSelectPar = self.editMenu.addAction(self.tr("Select Paragraph"))
self.aSelectPar.setShortcut("Ctrl+Shift+A")
self.aSelectPar.triggered.connect(lambda: self.requestDocAction.emit(nwDocAction.SEL_PARA))
self.aSelectPar.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.SEL_PARA)
)
return
@@ -293,23 +308,24 @@ class GuiMainMenu(QMenuBar):
# View > TreeView
self.aFocusTree = self.viewMenu.addAction(self.tr("Go to Project Tree"))
self.aFocusTree.setShortcut("Ctrl+Alt+1" if CONFIG.osWindows else "Alt+1")
self.aFocusTree.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.TREE))
self.aFocusTree.setShortcut("Ctrl+T")
self.aFocusTree.triggered.connect(
lambda: self.requestFocusChange.emit(nwWidget.TREE)
)
# View > Document Pane 1
# View > Document Editor
self.aFocusEditor = self.viewMenu.addAction(self.tr("Go to Document Editor"))
self.aFocusEditor.setShortcut("Ctrl+Alt+2" if CONFIG.osWindows else "Alt+2")
self.aFocusEditor.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.EDITOR))
# View > Document Pane 2
self.aFocusView = self.viewMenu.addAction(self.tr("Go to Document Viewer"))
self.aFocusView.setShortcut("Ctrl+Alt+3" if CONFIG.osWindows else "Alt+3")
self.aFocusView.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.VIEWER))
self.aFocusEditor.setShortcut("Ctrl+E")
self.aFocusEditor.triggered.connect(
lambda: self.requestFocusChange.emit(nwWidget.EDITOR)
)
# View > Outline
self.aFocusOutline = self.viewMenu.addAction(self.tr("Go to Outline"))
self.aFocusOutline.setShortcut("Ctrl+Alt+4" if CONFIG.osWindows else "Alt+4")
self.aFocusOutline.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.OUTLINE))
self.aFocusOutline.setShortcut("Ctrl+Shift+T")
self.aFocusOutline.triggered.connect(
lambda: self.requestFocusChange.emit(nwWidget.OUTLINE)
)
# View > Separator
self.viewMenu.addSeparator()
+1 -1
View File
@@ -189,7 +189,7 @@ class GuiProjectView(QWidget):
self.projTree.buildTree()
return
def setFocus(self) -> None:
def setTreeFocus(self) -> None:
"""Forward the set focus call to the tree widget."""
self.projTree.setFocus()
return
+8 -8
View File
@@ -57,32 +57,32 @@ class GuiSideBar(QWidget):
# Buttons
self.tbProject = QToolButton(self)
self.tbProject.setToolTip(self.tr("Project Tree View"))
self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View")))
self.tbProject.setIconSize(iconSize)
self.tbProject.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))
self.tbNovel = QToolButton(self)
self.tbNovel.setToolTip(self.tr("Novel Tree View"))
self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View")))
self.tbNovel.setIconSize(iconSize)
self.tbNovel.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))
self.tbOutline = QToolButton(self)
self.tbOutline.setToolTip(self.tr("Novel Outline View"))
self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
self.tbOutline.setIconSize(iconSize)
self.tbOutline.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))
self.tbBuild = QToolButton(self)
self.tbBuild.setToolTip(self.tr("Build Manuscript"))
self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
self.tbBuild.setIconSize(iconSize)
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
self.tbDetails = QToolButton(self)
self.tbDetails.setToolTip(self.tr("Project Details"))
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Project Details")))
self.tbDetails.setIconSize(iconSize)
self.tbDetails.clicked.connect(self.mainGui.showProjectDetailsDialog)
self.tbStats = QToolButton(self)
self.tbStats.setToolTip(self.tr("Writing Statistics"))
self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
self.tbStats.setIconSize(iconSize)
self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
@@ -111,7 +111,7 @@ class GuiSideBar(QWidget):
self.outerBox.addWidget(self.tbDetails)
self.outerBox.addWidget(self.tbStats)
self.outerBox.addWidget(self.tbSettings)
self.outerBox.setContentsMargins(0, 0, CONFIG.pxInt(2), 0)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.outerBox.setSpacing(CONFIG.pxInt(4))
self.setLayout(self.outerBox)
@@ -131,7 +131,7 @@ class GuiSideBar(QWidget):
buttonStyle = (
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
).format(CONFIG.pxInt(4), fadeCol.red(), fadeCol.green(), fadeCol.blue())
).format(CONFIG.pxInt(6), fadeCol.red(), fadeCol.green(), fadeCol.blue())
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
self.tbProject.setIcon(SHARED.theme.getIcon("view_editor"))
+44 -33
View File
@@ -31,7 +31,7 @@ from pathlib import Path
from datetime import datetime
from PyQt5.QtCore import Qt, QTimer, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QCursor, QIcon, QKeySequence
from PyQt5.QtGui import QCloseEvent, QCursor, QIcon
from PyQt5.QtWidgets import (
QDialog, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QShortcut,
QSplitter, QStackedWidget, QVBoxLayout, QWidget, qApp
@@ -247,6 +247,7 @@ class GuiMain(QMainWindow):
self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert)
self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord)
self.mainMenu.requestFocusChange.connect(self.switchFocus)
self.sideBar.viewChangeRequested.connect(self._changeView)
@@ -300,17 +301,17 @@ class GuiMain(QMainWindow):
# Shortcuts and Actions
self._connectMenuActions()
keyReturn = QShortcut(self)
keyReturn.setKey(QKeySequence(Qt.Key_Return))
keyReturn.activated.connect(self._keyPressReturn)
self.keyReturn = QShortcut(self)
self.keyReturn.setKey(Qt.Key.Key_Return)
self.keyReturn.activated.connect(self._keyPressReturn)
keyEnter = QShortcut(self)
keyEnter.setKey(QKeySequence(Qt.Key_Enter))
keyEnter.activated.connect(self._keyPressReturn)
self.keyEnter = QShortcut(self)
self.keyEnter.setKey(Qt.Key.Key_Enter)
self.keyEnter.activated.connect(self._keyPressReturn)
keyEscape = QShortcut(self)
keyEscape.setKey(QKeySequence(Qt.Key_Escape))
keyEscape.activated.connect(self._keyPressEscape)
self.keyEscape = QShortcut(self)
self.keyEscape.setKey(Qt.Key.Key_Escape)
self.keyEscape.activated.connect(self._keyPressEscape)
# Check that config loaded fine
self.reportConfErr()
@@ -1093,25 +1094,6 @@ class GuiMain(QMainWindow):
return True
def switchFocus(self, paneNo: nwWidget) -> None:
"""Switch focus between main GUI views."""
if paneNo == nwWidget.TREE:
tabIdx = self.projStack.currentIndex()
if tabIdx == self.idxProjView:
self.projView.setFocus()
elif tabIdx == self.idxNovelView:
self.novelView.setTreeFocus()
elif paneNo == nwWidget.EDITOR:
self._changeView(nwView.EDITOR)
self.docEditor.setFocus()
elif paneNo == nwWidget.VIEWER:
self._changeView(nwView.EDITOR)
self.docViewer.setFocus()
elif paneNo == nwWidget.OUTLINE:
self._changeView(nwView.OUTLINE)
self.outlineView.setTreeFocus()
return
def closeDocViewer(self, byUser: bool = True) -> bool:
"""Close the document view panel."""
self.docViewer.clearViewer()
@@ -1189,6 +1171,33 @@ class GuiMain(QMainWindow):
return
@pyqtSlot(nwWidget)
def switchFocus(self, paneNo: nwWidget) -> None:
"""Switch focus between main GUI views."""
if paneNo == nwWidget.TREE:
if self.projStack.currentWidget() is self.projView:
if self.projView.treeHasFocus():
self._changeView(nwView.NOVEL)
self.novelView.setTreeFocus()
else:
self.projView.setTreeFocus()
else:
if self.novelView.treeHasFocus():
self._changeView(nwView.PROJECT)
self.projView.setTreeFocus()
else:
self.novelView.setTreeFocus()
elif paneNo == nwWidget.EDITOR:
self._changeView(nwView.EDITOR)
self.docEditor.setFocus()
elif paneNo == nwWidget.VIEWER:
self._changeView(nwView.EDITOR)
self.docViewer.setFocus()
elif paneNo == nwWidget.OUTLINE:
self._changeView(nwView.OUTLINE)
self.outlineView.setTreeFocus()
return
##
# Private Slots
##
@@ -1224,18 +1233,14 @@ class GuiMain(QMainWindow):
if view == nwView.EDITOR:
# Only change the main stack, but not the project stack
self.mainStack.setCurrentWidget(self.splitMain)
elif view == nwView.PROJECT:
self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.projView)
elif view == nwView.NOVEL:
self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.novelView)
elif view == nwView.OUTLINE:
self.mainStack.setCurrentWidget(self.outlineView)
return
@pyqtSlot(nwDocAction)
@@ -1482,6 +1487,12 @@ class GuiMain(QMainWindow):
projData["numChapters"] = newProj.field("numChapters")
projData["numScenes"] = newProj.field("numScenes")
try:
langIdx = newProj.field("projLang")
projData["projLang"] = CONFIG.listLanguages(CONFIG.LANG_PROJ)[langIdx][0]
except Exception:
projData["projLang"] = "en_GB"
return projData
def _getTagSource(self, tag: str) -> tuple[str | None, str | None]:
+10 -2
View File
@@ -144,6 +144,8 @@ class GuiManuscript(QDialog):
self.buildList.setIconSize(QSize(iPx, iPx))
self.buildList.doubleClicked.connect(self._editSelectedBuild)
self.buildList.currentItemChanged.connect(self._updateBuildDetails)
self.buildList.setSelectionMode(QAbstractItemView.SingleSelection)
self.buildList.setDragDropMode(QAbstractItemView.InternalMove)
self.buildDetails = _DetailsWidget(self)
self.buildDetails.setColumnWidth(
@@ -417,9 +419,15 @@ class GuiManuscript(QDialog):
"""Save the user GUI settings."""
logger.debug("Saving GuiManuscript settings")
buildOrder = []
for i in range(self.buildList.count()):
if item := self.buildList.item(i):
buildOrder.append(item.data(self.D_KEY))
current = self.buildList.currentItem()
if isinstance(current, QListWidgetItem):
self._builds.setLastBuild(current.data(self.D_KEY))
lastBuild = current.data(self.D_KEY) if isinstance(current, QListWidgetItem) else ""
self._builds.setBuildsState(lastBuild, buildOrder)
winWidth = CONFIG.rpxInt(self.width())
winHeight = CONFIG.rpxInt(self.height())
-14
View File
@@ -974,15 +974,6 @@ class _FormatTab(QWidget):
self.formFormat = NConfigLayout()
self.formFormat.addGroupLabel(self._build.getLabel("format.grpFormat"))
# Build Language
self.buildLang = QComboBox()
langauges = CONFIG.listLanguages(CONFIG.LANG_PROJ)
self.buildLang.addItem("[%s]" % self.tr("Not Set"), "None")
for langID, langName in langauges:
self.buildLang.addItem(langName, langID)
self.formFormat.addRow(self._build.getLabel("format.buildLang"), self.buildLang)
# Font Family
self.textFont = QLineEdit()
self.textFont.setReadOnly(True)
@@ -1103,10 +1094,6 @@ class _FormatTab(QWidget):
def loadContent(self) -> None:
"""Populate the widgets."""
langIdx = self.buildLang.findData(self._build.getStr("format.buildLang"))
if langIdx != -1:
self.buildLang.setCurrentIndex(langIdx)
textFont = self._build.getStr("format.textFont")
if not textFont:
textFont = str(CONFIG.textFont)
@@ -1146,7 +1133,6 @@ class _FormatTab(QWidget):
def saveContent(self) -> None:
"""Save choices back into build object."""
self._build.setValue("format.buildLang", str(self.buildLang.currentData()))
self._build.setValue("format.textFont", self.textFont.text())
self._build.setValue("format.textSize", self.textSize.value())
self._build.setValue("format.lineHeight", self.lineHeight.value())
+16 -2
View File
@@ -28,8 +28,9 @@ import logging
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QFileDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel, QLineEdit,
QPushButton, QRadioButton, QSpinBox, QVBoxLayout, QWizard, QWizardPage
QComboBox, QFileDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel,
QLineEdit, QPushButton, QRadioButton, QSpinBox, QVBoxLayout, QWizard,
QWizardPage
)
from novelwriter import CONFIG, SHARED
@@ -127,15 +128,28 @@ class ProjWizardIntroPage(QWizardPage):
self.projAuthor.setFixedWidth(xW)
self.projAuthor.setPlaceholderText(self.tr("Optional"))
self.projLang = QComboBox(self)
self.projLang.setMaximumWidth(xW)
for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ):
self.projLang.addItem(language, tag)
langIdx = self.projLang.findData(CONFIG.guiLocale)
if langIdx == -1:
langIdx = self.projLang.findData("en_GB")
if langIdx != -1:
self.projLang.setCurrentIndex(langIdx)
self.mainForm = QFormLayout()
self.mainForm.addRow(self.tr("Project Name"), self.projName)
self.mainForm.addRow(self.tr("Novel Title"), self.projTitle)
self.mainForm.addRow(self.tr("Author(s)"), self.projAuthor)
self.mainForm.addRow(self.tr("Language"), self.projLang)
self.mainForm.setVerticalSpacing(fS)
self.registerField("projName*", self.projName)
self.registerField("projTitle", self.projTitle)
self.registerField("projAuthor", self.projAuthor)
self.registerField("projLang", self.projLang)
# Assemble
self.outerBox = QVBoxLayout()
+5 -5
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.2-alpha1" hexVersion="0x020200a1" fileVersion="1.5" fileRevision="1" timeStamp="2023-11-06 11:44:21">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1609" autoCount="255" editTime="81165">
<novelWriterXML appVersion="2.2-beta1" hexVersion="0x020200b1" fileVersion="1.5" fileRevision="1" timeStamp="2023-11-11 22:48:30">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1611" autoCount="255" editTime="81193">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
@@ -36,7 +36,7 @@
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
</importance>
</settings>
<content items="27" novelWords="989" notesWords="409">
<content items="27" novelWords="987" notesWords="409">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" />
<name status="sc24b8f" import="ia857f0">Novel</name>
@@ -46,7 +46,7 @@
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H0" charCount="269" wordCount="51" paraCount="3" cursorPos="275" />
<meta expanded="no" heading="H0" charCount="250" wordCount="49" paraCount="2" cursorPos="275" />
<name status="sf12341" import="ia857f0" active="yes">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
@@ -58,7 +58,7 @@
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2901" wordCount="513" paraCount="15" cursorPos="749" />
<meta expanded="no" heading="H3" charCount="2901" wordCount="513" paraCount="15" cursorPos="48" />
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 B

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 705 B

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

@@ -1,207 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg8"
version="1.1"
viewBox="0 0 270.93333 270.93334"
height="1024"
width="1024">
<defs
id="defs2">
<linearGradient
id="linearGradient2515">
<stop
style="stop-color:#f7f7f7;stop-opacity:1"
offset="0"
id="stop2511" />
<stop
style="stop-color:#e5e5e5;stop-opacity:1"
offset="1"
id="stop2513" />
</linearGradient>
<rect
x="60.484347"
y="205.79962"
width="130.57787"
height="65.785888"
id="rect852" />
<rect
id="rect1474"
height="28.631245"
width="177.70083"
y="18.019813"
x="13.463703" />
<rect
id="rect852-7"
height="65.785889"
width="130.57787"
y="205.79962"
x="60.484348" />
<rect
id="rect1544"
height="65.785889"
width="130.57787"
y="205.79962"
x="60.484348" />
<rect
x="60.484348"
y="205.79962"
width="130.57787"
height="65.785889"
id="rect1570" />
<rect
x="13.463703"
y="18.019813"
width="177.70083"
height="28.631245"
id="rect1587" />
<filter
id="filter1444"
style="color-interpolation-filters:sRGB">
<feFlood
id="feFlood1434"
result="flood"
flood-color="rgb(0,0,0)"
flood-opacity="0.498039" />
<feComposite
id="feComposite1436"
result="composite1"
operator="in"
in2="SourceGraphic"
in="flood" />
<feGaussianBlur
id="feGaussianBlur1438"
result="blur"
stdDeviation="3"
in="composite1" />
<feOffset
id="feOffset1440"
result="offset"
dy="6"
dx="6" />
<feComposite
id="feComposite1442"
result="composite2"
operator="over"
in2="offset"
in="SourceGraphic" />
</filter>
<filter
id="filter1456"
style="color-interpolation-filters:sRGB">
<feFlood
id="feFlood1446"
result="flood"
flood-color="rgb(0,0,0)"
flood-opacity="0.498039" />
<feComposite
id="feComposite1448"
result="composite1"
operator="in"
in2="SourceGraphic"
in="flood" />
<feGaussianBlur
id="feGaussianBlur1450"
result="blur"
stdDeviation="3"
in="composite1" />
<feOffset
id="feOffset1452"
result="offset"
dy="6"
dx="6" />
<feComposite
id="feComposite1454"
result="composite2"
operator="over"
in2="offset"
in="SourceGraphic" />
</filter>
<radialGradient
xlink:href="#linearGradient2515"
id="radialGradient2517"
cx="605.42725"
cy="623.97186"
fx="605.42725"
fy="623.97186"
r="410"
gradientTransform="matrix(2.4110274,1.7617596,-0.719517,0.98463462,-776.23441,-1372.7537)"
gradientUnits="userSpaceOnUse" />
</defs>
<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
id="layer3"
style="display:inline">
<path
clip-path="none"
transform="scale(0.26458333)"
d="m 192,22 c -44.32,0 -80,35.680002 -80,80 v 820 c 0,44.32002 35.68,80 80,80 h 640 c 44.31999,0 80,-35.67998 80,-80 V 213.44727 L 720.55469,22 Z"
style="fill:url(#radialGradient2517);fill-opacity:1;stroke:#999999;stroke-width:20;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1481" />
<path
id="path1490"
style="display:inline;fill:#e0e0e0;fill-opacity:1;stroke:#999999;stroke-width:5.29167;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 241.30037,56.474215 190.64713,5.8204593 V 35.307549 c 0,11.726333 9.44034,21.166666 21.16667,21.166666 z" />
<g
aria-label=".nwx"
transform="matrix(1.7893481,0,0,1.782571,-45.41919,-191.90571)"
id="text850"
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;white-space:pre;shape-inside:url(#rect852);display:inline;fill:#666666;fill-opacity:1">
<path
d="m 63.088865,241.76928 q 0,-1.36426 0.909505,-2.27376 0.930175,-0.93018 2.480467,-0.93018 1.550291,0 2.459796,0.93018 0.909504,0.9095 0.909504,2.27376 0,1.34359 -0.909504,2.27376 -0.909505,0.9095 -2.459796,0.9095 -1.550292,0 -2.480467,-0.9095 -0.909505,-0.93017 -0.909505,-2.27376 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
id="path856" />
<path
d="m 84.75161,226.78312 q -2.542479,0 -3.782712,2.14974 v 15.79231 h -5.973791 v -22.36554 h 5.601721 l 0.206705,2.56314 q 2.397785,-2.97656 6.428544,-2.97656 3.162595,0 5.105627,1.88103 1.943033,1.88102 1.963703,6.55256 v 14.34537 h -5.994461 v -14.26269 q 0,-2.14973 -0.930175,-2.91454 -0.909505,-0.76482 -2.625161,-0.76482 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
id="path858" />
<path
d="m 126.96089,222.35963 -5.68441,22.36554 h -5.00227 l -4.25814,-14.07665 -4.23746,14.07665 h -4.9816 l -5.705078,-22.36554 h 5.767088 l 2.95589,14.22134 4.05143,-14.22134 h 4.32014 l 4.09277,14.20067 2.91455,-14.20067 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
id="path860" />
<path
d="m 134.77436,222.35963 3.65869,6.65592 3.76204,-6.65592 h 6.3872 l -6.36653,10.93472 6.63525,11.43082 h -6.3872 l -4.01009,-7.04866 -3.94808,7.04866 h -6.42854 l 6.63525,-11.43082 -6.36654,-10.93472 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
id="path862" />
</g>
</g>
<g
id="g2509"
transform="matrix(1.2371037,0,0,1.2371037,206.3182,37.341857)">
<path
transform="matrix(0.16895991,0,0,0.18329422,-134.6942,-19.491243)"
id="rect1406-1"
style="display:inline;fill:#a11212;fill-opacity:1;stroke:#7e1b1b;stroke-width:9.99999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter1444)"
d="m 145.18984,56.414257 c -44.32,0 -79.999998,35.68 -79.999998,80.000003 v 199.99805 a 279.99999,279.99999 0 0 0 0,0.002 279.99999,279.99999 0 0 0 0,0.0195 v 199.98242 c 0,44.31999 35.679998,80 79.999998,80 44.32,0 80,-35.68001 80,-80 v -200 a 120,120 0 0 1 120,-120 120,120 0 0 1 120,120 v 282.83592 h 160 V 336.41621 a 279.99999,279.99999 0 0 0 -280,-280.000003 279.99999,279.99999 0 0 0 -0.81055,0 279.99999,279.99999 0 0 0 -133.46484,34.29688 c -14.4245,-20.75262 -38.42829,-34.29883 -65.72461,-34.29883 z M 376.65078,619.64082 c -0.62174,0.0972 -1.2172,0.22317 -1.7793,0.37696 0.5621,-0.15379 1.15756,-0.27973 1.7793,-0.37696 z m -1.7793,0.37696 c -0.5621,0.15378 -1.09071,0.33534 -1.58008,0.53906 0.48937,-0.20372 1.01798,-0.38528 1.58008,-0.53906 z m 338.85742,-0.37696 c 0.62174,0.0972 1.2172,0.22317 1.7793,0.37696 -0.5621,-0.15379 -1.15756,-0.27973 -1.7793,-0.37696 z m 1.7793,0.37696 c 0.5621,0.15378 1.09071,0.33534 1.58008,0.53906 -0.48938,-0.20372 -1.01798,-0.38528 -1.58008,-0.53906 z" />
<path
transform="matrix(0.16895991,0,0,0.18329422,-134.6942,-19.491243)"
id="path897"
style="display:inline;fill:#730d0d;fill-opacity:1;stroke:#590d0d;stroke-width:9.99999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter1456)"
d="m 465.18984,619.25215 v 0.16406 h -85.58398 c -5.49302,0 -9.91602,2.2329 -9.91602,5.00586 0,0.52294 0.15913,1.02696 0.45117,1.5 h -0.44531 c 0.30916,2.03607 1.24102,3.99791 2.81641,5.57227 l 165.59961,165.49414 c 3.92061,3.91808 10.23371,3.91812 14.15429,0 L 717.86757,631.49434 c 1.57569,-1.57463 2.50725,-3.53578 2.81641,-5.57227 h -0.44531 c 0.29216,-0.47304 0.45117,-0.97706 0.45117,-1.5 0,-2.77296 -4.42304,-5.00586 -9.91602,-5.00586 h -85.58398 v -0.16406 z" />
<rect
style="display:inline;fill:#c32222;fill-opacity:1;stroke:none;stroke-width:1.75982;stroke-linejoin:round;stroke-opacity:1"
id="rect914"
width="6.7583966"
height="82.482399"
x="-118.27303"
y="1.2969339"
rx="3.3791983"
ry="3.6658845" />
</g>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="256" height="256" version="1.1" viewBox="0 0 67.733 67.733" xmlns="http://www.w3.org/2000/svg">
<path d="m12.869 1.7145c-2.9081 0-5.2493 2.3412-5.2493 5.2493v53.806c0 2.9081 2.3412 5.2493 5.2493 5.2493h41.995c2.9081 0 5.2493-2.3412 5.2493-5.2493v-46.493l-12.562-12.562z" fill="#ececec" stroke="#999" stroke-linejoin="round" stroke-width="1.3123"/>
<path d="m60.113 14.277-12.562-12.562v7.3128c0 2.9081 2.3412 5.2493 5.2493 5.2493z" fill="#e0e0e0" stroke="#999" stroke-linejoin="round" stroke-width="1.3123"/>
<path d="m34.617 50.796c-0.31888 0-0.57319 0.09267-0.7628 0.27797-0.1853 0.18099-0.27797 0.41156-0.27797 0.69167 0 0.2758 0.09268 0.50636 0.27797 0.69166 0.18961 0.18099 0.44392 0.27144 0.7628 0.27144s0.57306-0.09044 0.76266-0.27144c0.18961-0.1853 0.28438-0.41587 0.28438-0.69166 0-0.28011-0.09477-0.51067-0.28438-0.69167-0.18961-0.1853-0.44378-0.27797-0.76266-0.27797zm-21.512 0.36204v9.4116h1.9456v-3.3226h1.7323c1.1118 0 1.978-0.26924 2.5985-0.8079 0.62484-0.53867 0.93722-1.2627 0.93722-2.172 0-0.90065-0.31238-1.644-0.93722-2.2301-0.62053-0.58607-1.4867-0.87903-2.5985-0.87903zm38.395 0.69154v1.7259h-1.0278v1.364h1.0278v3.6909c0 0.74121 0.1809 1.2713 0.54287 1.5902 0.36198 0.31889 0.84896 0.47828 1.4609 0.47828 0.40076 0 0.77566-0.0582 1.1247-0.17455v-1.4156c-0.17237 0.03448-0.3534 0.05178-0.543 0.05178-0.23701 0-0.41581-0.04745-0.53647-0.14226-0.12066-0.09481-0.18096-0.28867-0.18096-0.58171v-3.497h1.1893v-1.364h-1.1893v-1.7259zm-36.45 0.87916h1.7323c0.55589 0 0.95662 0.15729 1.2022 0.47188 0.24562 0.31027 0.36845 0.67008 0.36845 1.0795 0 0.40939-0.12283 0.7456-0.36845 1.0085-0.24562 0.26287-0.64636 0.39421-1.2022 0.39421h-1.7323zm9.8574 0.71755c-0.67224 0-1.1915 0.32101-1.5578 0.96311l-0.05818-0.83392h-1.7646v6.9942h1.8744v-4.583c0.21977-0.47834 0.68093-0.71756 1.3833-0.71756l0.67872 0.05178 0.01934-1.7906c-0.14651-0.05602-0.33829-0.08395-0.5753-0.08395zm4.2402 0c-1.0514 0-1.8659 0.34041-2.4433 1.0213-0.57743 0.67657-0.86609 1.5233-0.86609 2.5403v0.21979c0.01724 0.99546 0.31244 1.8228 0.88557 2.4822 0.57743 0.65933 1.3897 0.98899 2.4368 0.98899 1.0558 0 1.8702-0.33831 2.4433-1.0149 0.57312-0.67657 0.85968-1.5233 0.85968-2.5403v-0.13572c0-1.017-0.28655-1.8638-0.85968-2.5403-0.57313-0.68088-1.3919-1.0213-2.4563-1.0213zm10.93 0c-0.99974 0-1.7948 0.33819-2.3851 1.0148-0.58605 0.67657-0.87903 1.5429-0.87903 2.5987v0.27144c0.0043 0.93082 0.31236 1.7259 0.92427 2.3853 0.61191 0.65502 1.4608 0.98246 2.5467 0.98246 0.68085 0 1.2498-0.13134 1.7065-0.39421 0.45678-0.26287 0.79721-0.55386 1.0213-0.87275l-0.9244-1.0213c-0.41368 0.52143-0.97605 0.78214-1.6871 0.78214-0.47832 0-0.86398-0.13789-1.157-0.41369-0.29303-0.28011-0.47183-0.64634-0.53647-1.0988h4.4536v-0.79509c0-1.0342-0.26296-1.8659-0.78868-2.4951-0.52572-0.62917-1.2906-0.94376-2.2947-0.94376zm7.0715 0c-1.0816 0-1.8896 0.34251-2.424 1.0277-0.53434 0.68519-0.8015 1.5212-0.8015 2.508v0.18108c0 0.99115 0.26716 1.8293 0.8015 2.5145 0.53865 0.68088 1.3488 1.0213 2.4304 1.0213 0.81013 0 1.4932-0.23909 2.0491-0.71743 0.5602-0.47834 0.84453-1.0881 0.85314-1.8293h-1.7581c-0.0043 0.31889-0.11639 0.57306-0.33616 0.76267-0.21977 0.1853-0.4999 0.27797-0.84033 0.27797-0.55158 0-0.91141-0.20251-1.0795-0.6076-0.16375-0.40939-0.24555-0.88337-0.24555-1.422 0-0.65933 0.08403-1.1916 0.25208-1.5967 0.16806-0.40939 0.52357-0.614 1.0665-0.614 0.37921 0 0.67009 0.12059 0.87262 0.36192 0.20253 0.23702 0.30596 0.52578 0.31027 0.86622h1.7581c-0.0086-0.84032-0.2822-1.5061-0.82085-1.9973-0.53434-0.49127-1.2303-0.73691-2.0878-0.73691zm-13.432 0.12918v7.382c0 0.60331-0.28223 0.90492-0.84674 0.90492-0.19822 0-0.39643-0.01939-0.59465-0.05818v1.4803c0.29734 0.07326 0.60972 0.10983 0.93722 0.10983 0.75411 0 1.338-0.20894 1.7517-0.62695 0.41799-0.41801 0.62707-1.0214 0.62707-1.81v-7.382zm-4.57 1.3769c0.53003 0 0.90284 0.20462 1.1183 0.614 0.21977 0.40508 0.32962 0.88561 0.32962 1.4415 0 0.66364-0.10985 1.1937-0.32962 1.5902-0.21546 0.39646-0.58395 0.59465-1.1054 0.59465-0.53004 0-0.90494-0.19819-1.1247-0.59465-0.21546-0.40077-0.32321-0.88562-0.32321-1.4545 0-0.65072 0.10775-1.1786 0.32321-1.5836 0.21546-0.40508 0.58604-0.6076 1.1118-0.6076zm10.917 0c0.43092 0 0.7454 0.12492 0.94362 0.37486 0.20254 0.24563 0.30819 0.5559 0.3168 0.93081v0.14226h-2.5985c0.06464-0.43094 0.20477-0.78 0.42023-1.0472 0.21546-0.26718 0.52142-0.40075 0.91786-0.40075z" fill="#666" stroke-width=".1773" style="shape-inside:url(#rect2964);white-space:pre"/>
<path d="m16.933 38.766h1.5478q1.2383 0 1.7336-0.49531 0.55722-0.55722 0.55722-1.7955v-15.912q0-1.2383-0.49531-1.7336-0.49531-0.55722-1.7336-0.55722h-1.3002v-4.6435h7.9249l0.37148 4.1482q2.1051-2.6623 4.7673-3.8386 2.7242-1.2383 6.5628-1.2383 9.9062 0 9.9062 10.278v13.497q0 1.2383 0.49531 1.7955 0.55722 0.49531 1.7955 0.49531h1.7336v4.5816h-10.649v-20.308q0-3.0957-1.3002-4.4578-1.3002-1.424-4.2101-1.424-3.1576 0-5.2007 1.7955-2.0431 1.7955-2.0431 5.015v12.507q0 1.2383 0.43339 1.7955 0.49531 0.49531 1.6717 0.49531h1.6098v4.5816h-14.178z" fill="#346598" stroke-linecap="round" stroke-width=".16123"/>
</svg>

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

+2 -2
View File
@@ -10,9 +10,9 @@ synchronisation tools. All text is saved as plain text files with a meta data he
project structure is stored in a single project XML file, and other meta data is primarily saved as
JSON files.
The application is written with Python 3 (3.8+) using Qt5 and PyQt5 (5.3+). It is developed on
The application is written with Python 3 (3.8+) using Qt5 and PyQt5 (5.10+). It is developed on
Linux, but should in principle work fine on other operating systems as well as long as dependencies
are met. It is regularly tested on Debian and Ubuntu Linux, Windows, and macOS.
are met. It is regularly tested on Debian and Ubuntu Linux, Windows, and MacOS.
novelWriter is developed and maintained by [Veronica Berglyd Olsen](https://github.com/vkbo).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 167 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 167 KiB

+7 -206
View File
@@ -1,207 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg8"
version="1.1"
viewBox="0 0 270.93333 270.93334"
height="1024"
width="1024">
<defs
id="defs2">
<linearGradient
id="linearGradient2515">
<stop
style="stop-color:#f7f7f7;stop-opacity:1"
offset="0"
id="stop2511" />
<stop
style="stop-color:#e5e5e5;stop-opacity:1"
offset="1"
id="stop2513" />
</linearGradient>
<rect
x="60.484347"
y="205.79962"
width="130.57787"
height="65.785888"
id="rect852" />
<rect
id="rect1474"
height="28.631245"
width="177.70083"
y="18.019813"
x="13.463703" />
<rect
id="rect852-7"
height="65.785889"
width="130.57787"
y="205.79962"
x="60.484348" />
<rect
id="rect1544"
height="65.785889"
width="130.57787"
y="205.79962"
x="60.484348" />
<rect
x="60.484348"
y="205.79962"
width="130.57787"
height="65.785889"
id="rect1570" />
<rect
x="13.463703"
y="18.019813"
width="177.70083"
height="28.631245"
id="rect1587" />
<filter
id="filter1444"
style="color-interpolation-filters:sRGB">
<feFlood
id="feFlood1434"
result="flood"
flood-color="rgb(0,0,0)"
flood-opacity="0.498039" />
<feComposite
id="feComposite1436"
result="composite1"
operator="in"
in2="SourceGraphic"
in="flood" />
<feGaussianBlur
id="feGaussianBlur1438"
result="blur"
stdDeviation="3"
in="composite1" />
<feOffset
id="feOffset1440"
result="offset"
dy="6"
dx="6" />
<feComposite
id="feComposite1442"
result="composite2"
operator="over"
in2="offset"
in="SourceGraphic" />
</filter>
<filter
id="filter1456"
style="color-interpolation-filters:sRGB">
<feFlood
id="feFlood1446"
result="flood"
flood-color="rgb(0,0,0)"
flood-opacity="0.498039" />
<feComposite
id="feComposite1448"
result="composite1"
operator="in"
in2="SourceGraphic"
in="flood" />
<feGaussianBlur
id="feGaussianBlur1450"
result="blur"
stdDeviation="3"
in="composite1" />
<feOffset
id="feOffset1452"
result="offset"
dy="6"
dx="6" />
<feComposite
id="feComposite1454"
result="composite2"
operator="over"
in2="offset"
in="SourceGraphic" />
</filter>
<radialGradient
xlink:href="#linearGradient2515"
id="radialGradient2517"
cx="605.42725"
cy="623.97186"
fx="605.42725"
fy="623.97186"
r="410"
gradientTransform="matrix(2.4110274,1.7617596,-0.719517,0.98463462,-776.23441,-1372.7537)"
gradientUnits="userSpaceOnUse" />
</defs>
<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
id="layer3"
style="display:inline">
<path
clip-path="none"
transform="scale(0.26458333)"
d="m 192,22 c -44.32,0 -80,35.680002 -80,80 v 820 c 0,44.32002 35.68,80 80,80 h 640 c 44.31999,0 80,-35.67998 80,-80 V 213.44727 L 720.55469,22 Z"
style="fill:url(#radialGradient2517);fill-opacity:1;stroke:#999999;stroke-width:20;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1481" />
<path
id="path1490"
style="display:inline;fill:#e0e0e0;fill-opacity:1;stroke:#999999;stroke-width:5.29167;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 241.30037,56.474215 190.64713,5.8204593 V 35.307549 c 0,11.726333 9.44034,21.166666 21.16667,21.166666 z" />
<g
aria-label=".nwx"
transform="matrix(1.7893481,0,0,1.782571,-45.41919,-191.90571)"
id="text850"
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;white-space:pre;shape-inside:url(#rect852);display:inline;fill:#666666;fill-opacity:1">
<path
d="m 63.088865,241.76928 q 0,-1.36426 0.909505,-2.27376 0.930175,-0.93018 2.480467,-0.93018 1.550291,0 2.459796,0.93018 0.909504,0.9095 0.909504,2.27376 0,1.34359 -0.909504,2.27376 -0.909505,0.9095 -2.459796,0.9095 -1.550292,0 -2.480467,-0.9095 -0.909505,-0.93017 -0.909505,-2.27376 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
id="path856" />
<path
d="m 84.75161,226.78312 q -2.542479,0 -3.782712,2.14974 v 15.79231 h -5.973791 v -22.36554 h 5.601721 l 0.206705,2.56314 q 2.397785,-2.97656 6.428544,-2.97656 3.162595,0 5.105627,1.88103 1.943033,1.88102 1.963703,6.55256 v 14.34537 h -5.994461 v -14.26269 q 0,-2.14973 -0.930175,-2.91454 -0.909505,-0.76482 -2.625161,-0.76482 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
id="path858" />
<path
d="m 126.96089,222.35963 -5.68441,22.36554 h -5.00227 l -4.25814,-14.07665 -4.23746,14.07665 h -4.9816 l -5.705078,-22.36554 h 5.767088 l 2.95589,14.22134 4.05143,-14.22134 h 4.32014 l 4.09277,14.20067 2.91455,-14.20067 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
id="path860" />
<path
d="m 134.77436,222.35963 3.65869,6.65592 3.76204,-6.65592 h 6.3872 l -6.36653,10.93472 6.63525,11.43082 h -6.3872 l -4.01009,-7.04866 -3.94808,7.04866 h -6.42854 l 6.63525,-11.43082 -6.36654,-10.93472 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
id="path862" />
</g>
</g>
<g
id="g2509"
transform="matrix(1.2371037,0,0,1.2371037,206.3182,37.341857)">
<path
transform="matrix(0.16895991,0,0,0.18329422,-134.6942,-19.491243)"
id="rect1406-1"
style="display:inline;fill:#a11212;fill-opacity:1;stroke:#7e1b1b;stroke-width:9.99999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter1444)"
d="m 145.18984,56.414257 c -44.32,0 -79.999998,35.68 -79.999998,80.000003 v 199.99805 a 279.99999,279.99999 0 0 0 0,0.002 279.99999,279.99999 0 0 0 0,0.0195 v 199.98242 c 0,44.31999 35.679998,80 79.999998,80 44.32,0 80,-35.68001 80,-80 v -200 a 120,120 0 0 1 120,-120 120,120 0 0 1 120,120 v 282.83592 h 160 V 336.41621 a 279.99999,279.99999 0 0 0 -280,-280.000003 279.99999,279.99999 0 0 0 -0.81055,0 279.99999,279.99999 0 0 0 -133.46484,34.29688 c -14.4245,-20.75262 -38.42829,-34.29883 -65.72461,-34.29883 z M 376.65078,619.64082 c -0.62174,0.0972 -1.2172,0.22317 -1.7793,0.37696 0.5621,-0.15379 1.15756,-0.27973 1.7793,-0.37696 z m -1.7793,0.37696 c -0.5621,0.15378 -1.09071,0.33534 -1.58008,0.53906 0.48937,-0.20372 1.01798,-0.38528 1.58008,-0.53906 z m 338.85742,-0.37696 c 0.62174,0.0972 1.2172,0.22317 1.7793,0.37696 -0.5621,-0.15379 -1.15756,-0.27973 -1.7793,-0.37696 z m 1.7793,0.37696 c 0.5621,0.15378 1.09071,0.33534 1.58008,0.53906 -0.48938,-0.20372 -1.01798,-0.38528 -1.58008,-0.53906 z" />
<path
transform="matrix(0.16895991,0,0,0.18329422,-134.6942,-19.491243)"
id="path897"
style="display:inline;fill:#730d0d;fill-opacity:1;stroke:#590d0d;stroke-width:9.99999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter1456)"
d="m 465.18984,619.25215 v 0.16406 h -85.58398 c -5.49302,0 -9.91602,2.2329 -9.91602,5.00586 0,0.52294 0.15913,1.02696 0.45117,1.5 h -0.44531 c 0.30916,2.03607 1.24102,3.99791 2.81641,5.57227 l 165.59961,165.49414 c 3.92061,3.91808 10.23371,3.91812 14.15429,0 L 717.86757,631.49434 c 1.57569,-1.57463 2.50725,-3.53578 2.81641,-5.57227 h -0.44531 c 0.29216,-0.47304 0.45117,-0.97706 0.45117,-1.5 0,-2.77296 -4.42304,-5.00586 -9.91602,-5.00586 h -85.58398 v -0.16406 z" />
<rect
style="display:inline;fill:#c32222;fill-opacity:1;stroke:none;stroke-width:1.75982;stroke-linejoin:round;stroke-opacity:1"
id="rect914"
width="6.7583966"
height="82.482399"
x="-118.27303"
y="1.2969339"
rx="3.3791983"
ry="3.6658845" />
</g>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="256" height="256" version="1.1" viewBox="0 0 67.733 67.733" xmlns="http://www.w3.org/2000/svg">
<path d="m12.869 1.7145c-2.9081 0-5.2493 2.3412-5.2493 5.2493v53.806c0 2.9081 2.3412 5.2493 5.2493 5.2493h41.995c2.9081 0 5.2493-2.3412 5.2493-5.2493v-46.493l-12.562-12.562z" fill="#ececec" stroke="#999" stroke-linejoin="round" stroke-width="1.3123"/>
<path d="m60.113 14.277-12.562-12.562v7.3128c0 2.9081 2.3412 5.2493 5.2493 5.2493z" fill="#e0e0e0" stroke="#999" stroke-linejoin="round" stroke-width="1.3123"/>
<path d="m34.617 50.796c-0.31888 0-0.57319 0.09267-0.7628 0.27797-0.1853 0.18099-0.27797 0.41156-0.27797 0.69167 0 0.2758 0.09268 0.50636 0.27797 0.69166 0.18961 0.18099 0.44392 0.27144 0.7628 0.27144s0.57306-0.09044 0.76266-0.27144c0.18961-0.1853 0.28438-0.41587 0.28438-0.69166 0-0.28011-0.09477-0.51067-0.28438-0.69167-0.18961-0.1853-0.44378-0.27797-0.76266-0.27797zm-21.512 0.36204v9.4116h1.9456v-3.3226h1.7323c1.1118 0 1.978-0.26924 2.5985-0.8079 0.62484-0.53867 0.93722-1.2627 0.93722-2.172 0-0.90065-0.31238-1.644-0.93722-2.2301-0.62053-0.58607-1.4867-0.87903-2.5985-0.87903zm38.395 0.69154v1.7259h-1.0278v1.364h1.0278v3.6909c0 0.74121 0.1809 1.2713 0.54287 1.5902 0.36198 0.31889 0.84896 0.47828 1.4609 0.47828 0.40076 0 0.77566-0.0582 1.1247-0.17455v-1.4156c-0.17237 0.03448-0.3534 0.05178-0.543 0.05178-0.23701 0-0.41581-0.04745-0.53647-0.14226-0.12066-0.09481-0.18096-0.28867-0.18096-0.58171v-3.497h1.1893v-1.364h-1.1893v-1.7259zm-36.45 0.87916h1.7323c0.55589 0 0.95662 0.15729 1.2022 0.47188 0.24562 0.31027 0.36845 0.67008 0.36845 1.0795 0 0.40939-0.12283 0.7456-0.36845 1.0085-0.24562 0.26287-0.64636 0.39421-1.2022 0.39421h-1.7323zm9.8574 0.71755c-0.67224 0-1.1915 0.32101-1.5578 0.96311l-0.05818-0.83392h-1.7646v6.9942h1.8744v-4.583c0.21977-0.47834 0.68093-0.71756 1.3833-0.71756l0.67872 0.05178 0.01934-1.7906c-0.14651-0.05602-0.33829-0.08395-0.5753-0.08395zm4.2402 0c-1.0514 0-1.8659 0.34041-2.4433 1.0213-0.57743 0.67657-0.86609 1.5233-0.86609 2.5403v0.21979c0.01724 0.99546 0.31244 1.8228 0.88557 2.4822 0.57743 0.65933 1.3897 0.98899 2.4368 0.98899 1.0558 0 1.8702-0.33831 2.4433-1.0149 0.57312-0.67657 0.85968-1.5233 0.85968-2.5403v-0.13572c0-1.017-0.28655-1.8638-0.85968-2.5403-0.57313-0.68088-1.3919-1.0213-2.4563-1.0213zm10.93 0c-0.99974 0-1.7948 0.33819-2.3851 1.0148-0.58605 0.67657-0.87903 1.5429-0.87903 2.5987v0.27144c0.0043 0.93082 0.31236 1.7259 0.92427 2.3853 0.61191 0.65502 1.4608 0.98246 2.5467 0.98246 0.68085 0 1.2498-0.13134 1.7065-0.39421 0.45678-0.26287 0.79721-0.55386 1.0213-0.87275l-0.9244-1.0213c-0.41368 0.52143-0.97605 0.78214-1.6871 0.78214-0.47832 0-0.86398-0.13789-1.157-0.41369-0.29303-0.28011-0.47183-0.64634-0.53647-1.0988h4.4536v-0.79509c0-1.0342-0.26296-1.8659-0.78868-2.4951-0.52572-0.62917-1.2906-0.94376-2.2947-0.94376zm7.0715 0c-1.0816 0-1.8896 0.34251-2.424 1.0277-0.53434 0.68519-0.8015 1.5212-0.8015 2.508v0.18108c0 0.99115 0.26716 1.8293 0.8015 2.5145 0.53865 0.68088 1.3488 1.0213 2.4304 1.0213 0.81013 0 1.4932-0.23909 2.0491-0.71743 0.5602-0.47834 0.84453-1.0881 0.85314-1.8293h-1.7581c-0.0043 0.31889-0.11639 0.57306-0.33616 0.76267-0.21977 0.1853-0.4999 0.27797-0.84033 0.27797-0.55158 0-0.91141-0.20251-1.0795-0.6076-0.16375-0.40939-0.24555-0.88337-0.24555-1.422 0-0.65933 0.08403-1.1916 0.25208-1.5967 0.16806-0.40939 0.52357-0.614 1.0665-0.614 0.37921 0 0.67009 0.12059 0.87262 0.36192 0.20253 0.23702 0.30596 0.52578 0.31027 0.86622h1.7581c-0.0086-0.84032-0.2822-1.5061-0.82085-1.9973-0.53434-0.49127-1.2303-0.73691-2.0878-0.73691zm-13.432 0.12918v7.382c0 0.60331-0.28223 0.90492-0.84674 0.90492-0.19822 0-0.39643-0.01939-0.59465-0.05818v1.4803c0.29734 0.07326 0.60972 0.10983 0.93722 0.10983 0.75411 0 1.338-0.20894 1.7517-0.62695 0.41799-0.41801 0.62707-1.0214 0.62707-1.81v-7.382zm-4.57 1.3769c0.53003 0 0.90284 0.20462 1.1183 0.614 0.21977 0.40508 0.32962 0.88561 0.32962 1.4415 0 0.66364-0.10985 1.1937-0.32962 1.5902-0.21546 0.39646-0.58395 0.59465-1.1054 0.59465-0.53004 0-0.90494-0.19819-1.1247-0.59465-0.21546-0.40077-0.32321-0.88562-0.32321-1.4545 0-0.65072 0.10775-1.1786 0.32321-1.5836 0.21546-0.40508 0.58604-0.6076 1.1118-0.6076zm10.917 0c0.43092 0 0.7454 0.12492 0.94362 0.37486 0.20254 0.24563 0.30819 0.5559 0.3168 0.93081v0.14226h-2.5985c0.06464-0.43094 0.20477-0.78 0.42023-1.0472 0.21546-0.26718 0.52142-0.40075 0.91786-0.40075z" fill="#666" stroke-width=".1773" style="shape-inside:url(#rect2964);white-space:pre"/>
<path d="m16.933 38.766h1.5478q1.2383 0 1.7336-0.49531 0.55722-0.55722 0.55722-1.7955v-15.912q0-1.2383-0.49531-1.7336-0.49531-0.55722-1.7336-0.55722h-1.3002v-4.6435h7.9249l0.37148 4.1482q2.1051-2.6623 4.7673-3.8386 2.7242-1.2383 6.5628-1.2383 9.9062 0 9.9062 10.278v13.497q0 1.2383 0.49531 1.7955 0.55722 0.49531 1.7955 0.49531h1.7336v4.5816h-10.649v-20.308q0-3.0957-1.3002-4.4578-1.3002-1.424-4.2101-1.424-3.1576 0-5.2007 1.7955-2.0431 1.7955-2.0431 5.015v12.507q0 1.2383 0.43339 1.7955 0.49531 0.49531 1.6717 0.49531h1.6098v4.5816h-14.178z" fill="#346598" stroke-linecap="round" stroke-width=".16123"/>
</svg>

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

+8
View File
@@ -62,6 +62,14 @@ fi
echo "Content of current dir:"
ls -lah .
# Create icon
echo "Creating icon ..."
pushd "$SRC_DIR/setup/macos" || exit 1
iconutil -c icns $SRC_DIR/setup/macos/novelwriter.iconset
echo "Content of current dir:"
ls -lah .
popd || exit 1
popd || exit 1
pushd "$BUILD_DIR"/ || exit 1
-70
View File
@@ -1,70 +0,0 @@
#! /usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
function generate_linux () {
echo "Generating on Linux ..."
if ! command -v png2icns &> /dev/null
then
echo "png2icns cound not be found, it is required. Please install a package like icnsutils or libicns."
exit
fi
if ! command -v convert $> /dev/null
then
echo "convert could not be found, it is required. please install a imagemagick package."
exit
fi
mkdir -p $SCRIPT_DIR/icons
sizes=( 16 32 64 128 256 )
for base in "${sizes[@]}"; do
let basex2=${base}*2
size="${base}x${base}"
echo "Copying files for $size"
double=${basex2}x${basex2}
cp $SCRIPT_DIR/../data/hicolor/${size}/apps/novelwriter.png $SCRIPT_DIR/icons/icon_${size}.png
echo "Resizing ${size}@2x to $double from $size"
convert $SCRIPT_DIR/icons/icon_${size}.png -resize $double $SCRIPT_DIR/icons/icon_${size}@2x.png
done
png2icns $SCRIPT_DIR/novelwriter.icns $SCRIPT_DIR/icons/icon_*.png
rm -r $SCRIPT_DIR/icons
echo "Done"
}
function generate_macos () {
echo "Generating on MacOs ..."
mkdir -p $SCRIPT_DIR/icons
echo "Building Iconset ..."
sizes=( 16 32 64 128 256 )
for base in "${sizes[@]}"; do
let basex2=${base}*2
size="${base}x${base}"
echo "Copying files for $size"
double=${basex2}x${basex2}
cp $SCRIPT_DIR/../data/hicolor/${size}/apps/novelwriter.png $SCRIPT_DIR/icons/icon_${size}.png
cp $SCRIPT_DIR/../data/hicolor/${size}/apps/novelwriter.png $SCRIPT_DIR/icons/icon_${size}@2x.png
echo "Resizing ${size}@2x to $double from $size"
sips -Z $basex2 $SCRIPT_DIR/icons/icon_${size}@2x.png
done
rm -rf $SCRIPT_DIR/novelwriter.iconset
mv $SCRIPT_DIR/icons $SCRIPT_DIR/novelwriter.iconset
echo "Generating icns ..."
iconutil -c icns $SCRIPT_DIR/novelwriter.iconset
echo "Done"
}
unameOut="$(uname -s)"
case "$unameOut" in
Linux*) generate_linux;;
Darwin*) generate_macos;;
*) echo "Unsupported OS"
esac
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+2 -2
View File
@@ -26,8 +26,8 @@ echo " Building Minimal Packages"
echo "================================================================================"
echo ""
python3 pkgutils.py minimal-zip --target-win
python3 pkgutils.py minimal-zip --target-linux
python3 pkgutils.py minimal-zip --target-darwin
# python3 pkgutils.py minimal-zip --target-linux
# python3 pkgutils.py minimal-zip --target-darwin
echo ""
echo " Building Linux Packages"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 20 KiB

@@ -45,6 +45,7 @@ repdash = True
repdots = True
autoscroll = False
autoscrollpos = 30
scrollpastend = True
fmtsquoteopen =
fmtsquoteclose =
fmtdquoteopen = “
@@ -7,7 +7,7 @@
</project>
<settings>
<doBackup>yes</doBackup>
<language>None</language>
<language>en_GB</language>
<spellChecking auto="no">None</spellChecking>
<lastHandle>
<entry key="editor">None</entry>
@@ -45,6 +45,7 @@ repdash = True
repdots = True
autoscroll = True
autoscrollpos = 30
scrollpastend = True
fmtsquoteopen =
fmtsquoteclose =
fmtdquoteopen = “
+1 -1
View File
@@ -435,7 +435,7 @@ def testCoreBuildSettings_Collection(monkeypatch, mockGUI, fncPath: Path, mockRn
(buildIDTwo, "Build Two"),
(buildIDOne, "Build One"),
]
builds.setLastBuild(buildIDOne)
builds.setBuildsState(buildIDOne, [buildIDTwo, buildIDOne])
builds.setDefaultBuild(buildIDTwo)
# Check errors: No valid path
+2
View File
@@ -446,6 +446,7 @@ def testCoreTools_NewCustomA(monkeypatch, fncPath, tstPaths, mockRnd):
"projName": "Test Custom",
"projTitle": "Test Novel",
"projAuthor": "Jane Doe",
"projLang": -1,
"projPath": fncPath,
"popSample": False,
"popMinimal": False,
@@ -484,6 +485,7 @@ def testCoreTools_NewCustomB(monkeypatch, fncPath, tstPaths, mockRnd):
"projName": "Test Custom",
"projTitle": "Test Novel",
"projAuthor": "Jane Doe",
"projLang": -1,
"projPath": fncPath,
"popSample": False,
"popMinimal": False,
+17 -17
View File
@@ -155,9 +155,9 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
# Set some values
theProject = SHARED.project
theProject.tree[C.hTitlePage].setStatus(C.sFinished)
theProject.tree[C.hChapterDoc].setStatus(C.sDraft)
theProject.tree[C.hSceneDoc].setStatus(C.sDraft)
theProject.tree[C.hTitlePage].setStatus(C.sFinished) # type: ignore
theProject.tree[C.hChapterDoc].setStatus(C.sDraft) # type: ignore
theProject.tree[C.hSceneDoc].setStatus(C.sDraft) # type: ignore
nwGUI.projView.projTree.setSelectedHandle(C.hPlotRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
@@ -170,9 +170,9 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
hCharNote = "0000000000011"
hWorldNote = "0000000000012"
theProject.tree[hPlotNote].setImport(C.iMajor)
theProject.tree[hCharNote].setImport(C.iMajor)
theProject.tree[hWorldNote].setImport(C.iMain)
theProject.tree[hPlotNote].setImport(C.iMajor) # type: ignore
theProject.tree[hCharNote].setImport(C.iMajor) # type: ignore
theProject.tree[hWorldNote].setImport(C.iMain) # type: ignore
# Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_STATUS)
@@ -367,23 +367,23 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncPath, projPath, mo
tabReplace = projSettings.tabReplace
assert tabReplace.listBox.topLevelItem(0).text(0) == "<A>"
assert tabReplace.listBox.topLevelItem(0).text(1) == "B"
assert tabReplace.listBox.topLevelItem(1).text(0) == "<C>"
assert tabReplace.listBox.topLevelItem(1).text(1) == "D"
assert tabReplace.listBox.topLevelItem(0).text(0) == "<A>" # type: ignore
assert tabReplace.listBox.topLevelItem(0).text(1) == "B" # type: ignore
assert tabReplace.listBox.topLevelItem(1).text(0) == "<C>" # type: ignore
assert tabReplace.listBox.topLevelItem(1).text(1) == "D" # type: ignore
assert tabReplace.listBox.topLevelItemCount() == 2
# Nothing to save or delete
tabReplace.listBox.clearSelection()
assert tabReplace._saveEntry() is False
assert tabReplace._delEntry() is False
tabReplace._saveEntry()
tabReplace._delEntry()
assert tabReplace.listBox.topLevelItemCount() == 2
# Create a new entry
qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 3
assert tabReplace.listBox.topLevelItem(2).text(0) == "<keyword3>"
assert tabReplace.listBox.topLevelItem(2).text(1) == ""
assert tabReplace.listBox.topLevelItem(2).text(0) == "<keyword3>" # type: ignore
assert tabReplace.listBox.topLevelItem(2).text(1) == "" # type: ignore
# Edit the entry
tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(2))
@@ -394,8 +394,8 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncPath, projPath, mo
for c in "With This Stuff ":
qtbot.keyClick(tabReplace.editValue, c, delay=KEY_DELAY)
qtbot.mouseClick(tabReplace.saveButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItem(2).text(0) == "<This>"
assert tabReplace.listBox.topLevelItem(2).text(1) == "With This Stuff "
assert tabReplace.listBox.topLevelItem(2).text(0) == "<This>" # type: ignore
assert tabReplace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore
# Create a new entry again
tabReplace.listBox.clearSelection()
@@ -405,7 +405,7 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncPath, projPath, mo
# The list is sorted, so we must find it
newIdx = -1
for i in range(tabReplace.listBox.topLevelItemCount()):
if tabReplace.listBox.topLevelItem(i).text(0) == "<keyword4>":
if tabReplace.listBox.topLevelItem(i).text(0) == "<keyword4>": # type: ignore
newIdx = i
break
assert newIdx >= 0
+7 -43
View File
@@ -672,7 +672,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
@pytest.mark.gui
def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd):
def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd):
"""Test the text manipulation functions."""
buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True
@@ -680,37 +680,6 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
text = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
nwGUI.docEditor.replaceText(text)
# Clear Surrounding
# =================
# No Selection
text = "### A Scene\n\n%s" % ipsumText[0]
nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
cursor = nwGUI.docEditor.textCursor()
assert nwGUI.docEditor._clearSurrounding(cursor, 1) is False
# Clear Characters, 1 Layer
repText = text.replace("consectetur", "=consectetur=")
nwGUI.docEditor.replaceText(repText)
nwGUI.docEditor.setCursorPosition(45)
cursor = nwGUI.docEditor.textCursor()
cursor.select(QTextCursor.WordUnderCursor)
assert nwGUI.docEditor._clearSurrounding(cursor, 1) is True
assert nwGUI.docEditor.getText() == text
# Clear Characters, 2 Layers
repText = text.replace("consectetur", "==consectetur==")
nwGUI.docEditor.replaceText(repText)
nwGUI.docEditor.setCursorPosition(45)
cursor = nwGUI.docEditor.textCursor()
cursor.select(QTextCursor.WordUnderCursor)
assert nwGUI.docEditor._clearSurrounding(cursor, 2) is True
assert nwGUI.docEditor.getText() == text
# Wrap Selection
# ==============
@@ -718,11 +687,6 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
# No Selection
with monkeypatch.context() as mp:
mp.setattr(nwGUI.docEditor, "_autoSelect", lambda: QTextCursor())
assert nwGUI.docEditor._wrapSelection("=", "=") is False
# Wrap Equal
nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
@@ -752,13 +716,13 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
# =============
text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2])
nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
# No Selection
with monkeypatch.context() as mp:
mp.setattr(nwGUI.docEditor, "_autoSelect", lambda: QTextCursor())
assert nwGUI.docEditor._toggleFormat(2, "=") is False
# Block format repetition
nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(39)
assert nwGUI.docEditor._toggleFormat(1, "=") is True
assert nwGUI.docEditor.getText() == text.replace("amet", "=amet=", 1)
assert nwGUI.docEditor._toggleFormat(1, "=") is False
# Wrap Single Equal
nwGUI.docEditor.replaceText(text)
@@ -562,7 +562,6 @@ def testBuildSettings_Format(monkeypatch, qtbot: QtBot, nwGUI: GuiMain):
assert bSettings.toolStack.currentWidget() is fmtTab
# Check initial values
assert fmtTab.buildLang.currentData() == "en_US"
assert fmtTab.textFont.text() == textFont
assert fmtTab.textSize.value() == 12
assert fmtTab.lineHeight.value() == 1.2
@@ -581,7 +580,6 @@ def testBuildSettings_Format(monkeypatch, qtbot: QtBot, nwGUI: GuiMain):
assert fmtTab.rightMargin.value() == 15.0
# Change values
fmtTab.buildLang.setCurrentIndex(fmtTab.buildLang.findData("en_GB"))
fmtTab.textFont.setText("Arial")
fmtTab.textSize.setValue(11)
fmtTab.lineHeight.setValue(1.15)
@@ -596,7 +594,6 @@ def testBuildSettings_Format(monkeypatch, qtbot: QtBot, nwGUI: GuiMain):
# Save values
fmtTab.saveContent()
assert build.getStr("format.buildLang") == "en_GB"
assert build.getStr("format.textFont") == "Arial"
assert build.getInt("format.textSize") == 11
assert build.getFloat("format.lineHeight") == 1.15