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: on:
push: push:
+77
View File
@@ -1,5 +1,82 @@
# novelWriter Changelog # 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] ## Version 2.1.1 [2023-11-05]
### Release Notes ### 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) * [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) * [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 ## Special Mentions
Additional thanks to @johnblommers who was an early user and who has provided a lot of very useful 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 # 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) [![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) [![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) [![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) [![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) [![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 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 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:`F8`", "Toggle :guilabel:`Focus Mode`"
":kbd:`F9`", "Re-build the project index" ":kbd:`F9`", "Re-build the project index"
":kbd:`F11`", "Toggle full screen mode" ":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+,`", "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+Q`", "Exit novelWriter"
":kbd:`Ctrl+Shift+,`", "Open the :guilabel:`Project Settings` dialog" ":kbd:`Ctrl+Shift+,`", "Open the :guilabel:`Project Settings` dialog"
":kbd:`Ctrl+Shift+O`", "Open a project" ":kbd:`Ctrl+Shift+O`", "Open a project"
":kbd:`Ctrl+Shift+S`", "Save the current 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:`Ctrl+Shift+W`", "Close the current project"
":kbd:`Shift+F1`", "Open the local user manual (PDF) if it is available" ":kbd:`Shift+F1`", "Open the local user manual (PDF) if it is available"
":kbd:`Shift+F6`", "Open the :guilabel:`Project Details` dialog" ":kbd:`Shift+F6`", "Open the :guilabel:`Project Details` dialog"
.. _a_kb_tree: .. _a_kb_tree:
Project Tree Shortcuts Project Tree Shortcuts
@@ -46,21 +46,21 @@ Project Tree Shortcuts
.. csv-table:: .. csv-table::
:header: "Shortcut", "Description" :header: "Shortcut", "Description"
":kbd:`F2`", "Edit the label of the selected item" ":kbd:`F2`", "Edit the label of the selected item"
":kbd:`Return`", "Open the selected document in the editor" ":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+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+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+Left`", "Jump to the parent item in the tree"
":kbd:`Alt+Right`", "Jump to the first child item in the project 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+.`", "Open the context menu on the selected item"
":kbd:`Ctrl+L`", "Open the :guilabel:`Quick Links` menu" ":kbd:`Ctrl+L`", "Open the :guilabel:`Quick Links` menu"
":kbd:`Ctrl+N`", "Open the :guilabel:`Create New Item` menu" ":kbd:`Ctrl+N`", "Open the :guilabel:`Create New Item` menu"
":kbd:`Ctrl+O`", "Open selected document" ":kbd:`Ctrl+O`", "Open selected document"
":kbd:`Ctrl+R`", "Open the selected document in the viewer" ":kbd:`Ctrl+R`", "Open the selected document in the viewer"
":kbd:`Ctrl+Up`", "Move selected item one step up in the tree" ":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+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+Del`", "Move the selected item to Trash"
":kbd:`Ctrl+Shift+Del`", "Move the selected item to Trash" ":kbd:`Ctrl+Shift+Z`", "Undo the last move of a project item, if possible"
.. _a_kb_editor: .. _a_kb_editor:
@@ -134,6 +134,7 @@ Other Editor Shortcuts
":kbd:`Ctrl+Shift+A`", "Select all text in the current paragraph" ":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" ":kbd:`Ctrl+Shift+I`", "Import text to the current document from a text file"
.. _a_kb_ins: .. _a_kb_ins:
Insert Shortcuts Insert Shortcuts
+5 -23
View File
@@ -33,26 +33,8 @@ from novelwriter.error import exceptionHandler, logException
from novelwriter.config import Config from novelwriter.config import Config
from novelwriter.shared import SharedData from novelwriter.shared import SharedData
## # Package Meta
# 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__ = "novelwriter" __package__ = "novelwriter"
__copyright__ = "Copyright 20182023, Veronica Berglyd Olsen" __copyright__ = "Copyright 20182023, Veronica Berglyd Olsen"
@@ -60,9 +42,9 @@ __license__ = "GPLv3"
__author__ = "Veronica Berglyd Olsen" __author__ = "Veronica Berglyd Olsen"
__maintainer__ = "Veronica Berglyd Olsen" __maintainer__ = "Veronica Berglyd Olsen"
__email__ = "code@vkbo.net" __email__ = "code@vkbo.net"
__version__ = "2.2-alpha1" __version__ = "2.2-beta1"
__hexversion__ = "0x020200a1" __hexversion__ = "0x020200b1"
__date__ = "2023-09-01" __date__ = "2023-11-12"
__status__ = "Stable" __status__ = "Stable"
__domain__ = "novelwriter.io" __domain__ = "novelwriter.io"
@@ -2,6 +2,16 @@
"Synopsis": "Samandrag", "Synopsis": "Samandrag",
"Comment": "Kommentar", "Comment": "Kommentar",
"Notes": "Notat", "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", "0": "null",
"1": "ein", "1": "ein",
"2": "to", "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"?> <?xml version="1.0" encoding="UTF-8"?>
<svg <!-- Created with Inkscape (http://www.inkscape.org/) -->
xmlns:dc="http://purl.org/dc/elements/1.1/" <svg width="256" height="256" version="1.1" viewBox="0 0 67.733 67.733" xmlns="http://www.w3.org/2000/svg">
xmlns:cc="http://creativecommons.org/ns#" <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"/>
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" <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"/>
xmlns:svg="http://www.w3.org/2000/svg" <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"/>
xmlns="http://www.w3.org/2000/svg" <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"/>
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>
</svg> </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) <a href="https://github.com/altercation/solarized">Solarized</a> themes by Ethan Schoonover (MIT License)
</p> </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> <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 <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> <html>
<body> <body>
<h2>Release Notes for 2.1</h2> <h2>Release Notes for 2.2 Beta 1</h2>
<p><i>Released on 17 October 2023</i></p> <p><i>Released on 11 November 2023</i></p>
<p>Scroll down for <a href="#patch">Patch Notes</a></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
<p>The primary focus of this release has been a complete redesign of the Build Tool, that is, the backups.</p>
tool that assembles your project into a manuscript document. The new tool, called the "Manuscript <p>Please check the changelog for an overview of changes. The full release notes will be added to
Build Tool" allows you to define multiple build definitions for your project. The build definitions the final release.</p>
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><i>See also the <a href="https://github.com/vkbo/novelWriter/releases">Releases</a> page.</i></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> </body>
</html> </html>
+3
View File
@@ -148,6 +148,7 @@ class Config:
self.autoScroll = False # Typewriter-like scrolling self.autoScroll = False # Typewriter-like scrolling
self.autoScrollPos = 30 # Start point for 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.wordCountTimer = 5.0 # Interval for word count update in seconds
self.incNotesWCount = True # The status bar word count includes notes 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.doReplaceDots = conf.rdBool(sec, "repdots", self.doReplaceDots)
self.autoScroll = conf.rdBool(sec, "autoscroll", self.autoScroll) self.autoScroll = conf.rdBool(sec, "autoscroll", self.autoScroll)
self.autoScrollPos = conf.rdInt(sec, "autoscrollpos", self.autoScrollPos) 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.fmtSQuoteOpen = conf.rdStr(sec, "fmtsquoteopen", self.fmtSQuoteOpen)
self.fmtSQuoteClose = conf.rdStr(sec, "fmtsquoteclose", self.fmtSQuoteClose) self.fmtSQuoteClose = conf.rdStr(sec, "fmtsquoteclose", self.fmtSQuoteClose)
self.fmtDQuoteOpen = conf.rdStr(sec, "fmtdquoteopen", self.fmtDQuoteOpen) self.fmtDQuoteOpen = conf.rdStr(sec, "fmtdquoteopen", self.fmtDQuoteOpen)
@@ -701,6 +703,7 @@ class Config:
"repdots": str(self.doReplaceDots), "repdots": str(self.doReplaceDots),
"autoscroll": str(self.autoScroll), "autoscroll": str(self.autoScroll),
"autoscrollpos": str(self.autoScrollPos), "autoscrollpos": str(self.autoScrollPos),
"scrollpastend": str(self.scrollPastEnd),
"fmtsquoteopen": str(self.fmtSQuoteOpen), "fmtsquoteopen": str(self.fmtSQuoteOpen),
"fmtsquoteclose": str(self.fmtSQuoteClose), "fmtsquoteclose": str(self.fmtSQuoteClose),
"fmtdquoteopen": str(self.fmtDQuoteOpen), "fmtdquoteopen": str(self.fmtDQuoteOpen),
+1 -2
View File
@@ -327,8 +327,7 @@ class nwQuotes:
class nwUnicode: class nwUnicode:
"""Supported unicode character constants and their HTML equivalents. """Supported unicode character constants and their HTML equivalents."""
"""
# Unicode Constants # Unicode Constants
# ================= # =================
+39 -23
View File
@@ -64,7 +64,6 @@ SETTINGS_TEMPLATE = {
"text.includeKeywords": (bool, False), "text.includeKeywords": (bool, False),
"text.includeBodyText": (bool, True), "text.includeBodyText": (bool, True),
"text.addNoteHeadings": (bool, True), "text.addNoteHeadings": (bool, True),
"format.buildLang": (str, "en_GB"),
"format.textFont": (str, CONFIG.textFont), "format.textFont": (str, CONFIG.textFont),
"format.textSize": (int, 12), "format.textSize": (int, 12),
"format.lineHeight": (float, 1.15, 0.75, 3.0), "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"), "text.addNoteHeadings": QT_TRANSLATE_NOOP("Builds", "Add Titles for Notes"),
"format.grpFormat": QT_TRANSLATE_NOOP("Builds", "Text Format"), "format.grpFormat": QT_TRANSLATE_NOOP("Builds", "Text Format"),
"format.buildLang": QT_TRANSLATE_NOOP("Builds", "Language"),
"format.textFont": QT_TRANSLATE_NOOP("Builds", "Font Family"), "format.textFont": QT_TRANSLATE_NOOP("Builds", "Font Family"),
"format.textSize": QT_TRANSLATE_NOOP("Builds", "Font Size"), "format.textSize": QT_TRANSLATE_NOOP("Builds", "Font Size"),
"format.lineHeight": QT_TRANSLATE_NOOP("Builds", "Line Height"), "format.lineHeight": QT_TRANSLATE_NOOP("Builds", "Line Height"),
@@ -158,6 +156,7 @@ class BuildSettings:
self._uuid = str(uuid.uuid4()) self._uuid = str(uuid.uuid4())
self._path = Path.home() self._path = Path.home()
self._build = "" self._build = ""
self._order = 0
self._format = nwBuildFmt.ODT self._format = nwBuildFmt.ODT
self._skipRoot = set() self._skipRoot = set()
self._excluded = set() self._excluded = set()
@@ -166,20 +165,32 @@ class BuildSettings:
self._changed = False self._changed = False
return return
@classmethod
def fromDict(cls, data: dict) -> BuildSettings:
"""Create a build settings object from a dict."""
cls = BuildSettings()
cls.unpack(data)
return cls
## ##
# Properties # Properties
## ##
@property @property
def name(self) -> str: def name(self) -> str:
"""The build name.""" """Return the build name."""
return self._name return self._name
@property @property
def buildID(self) -> str: def buildID(self) -> str:
"""The build ID as a UUID.""" """Return the build ID as a UUID."""
return self._uuid return self._uuid
@property
def order(self) -> int:
"""Return the build order."""
return self._order
@property @property
def lastPath(self) -> Path: def lastPath(self) -> Path:
"""The last used build path.""" """The last used build path."""
@@ -253,6 +264,12 @@ class BuildSettings:
self._uuid = value self._uuid = value
return 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: def setLastPath(self, path: Path | str | None) -> None:
"""Set the last used build path.""" """Set the last used build path."""
if isinstance(path, str): if isinstance(path, str):
@@ -400,6 +417,7 @@ class BuildSettings:
"uuid": self._uuid, "uuid": self._uuid,
"path": str(self._path), "path": str(self._path),
"build": self._build, "build": self._build,
"order": self._order,
"format": self._format.name, "format": self._format.name,
"settings": self._settings.copy(), "settings": self._settings.copy(),
"content": { "content": {
@@ -411,14 +429,15 @@ class BuildSettings:
def unpack(self, data: dict) -> None: def unpack(self, data: dict) -> None:
"""Unpack a dictionary and populate the class.""" """Unpack a dictionary and populate the class."""
content = data.get("content", {})
settings = data.get("settings", {}) settings = data.get("settings", {})
content = data.get("content", {})
included = content.get("included", []) included = content.get("included", [])
excluded = content.get("excluded", []) excluded = content.get("excluded", [])
skipRoot = content.get("skipRoot", []) skipRoot = content.get("skipRoot", [])
self.setName(data.get("name", "")) self.setName(data.get("name", ""))
self.setBuildID(data.get("uuid", "")) self.setBuildID(data.get("uuid", ""))
self.setOrder(data.get("order", 0))
self.setLastPath(data.get("path", None)) self.setLastPath(data.get("path", None))
self.setLastBuildName(data.get("build", "")) self.setLastBuildName(data.get("build", ""))
@@ -455,9 +474,9 @@ class BuildCollection:
def __init__(self, project: NWProject) -> None: def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
self._builds = {}
self._lastBuild = "" self._lastBuild = ""
self._defaultBuild = "" self._defaultBuild = ""
self._builds: dict[str, BuildSettings] = {}
self._loadCollection() self._loadCollection()
return return
@@ -485,21 +504,19 @@ class BuildCollection:
def getBuild(self, buildID: str) -> BuildSettings | None: def getBuild(self, buildID: str) -> BuildSettings | None:
"""Get a specific build settings object.""" """Get a specific build settings object."""
if buildID not in self._builds: return self._builds.get(buildID, None)
return None
build = BuildSettings()
build.unpack(self._builds[buildID])
return build
## ##
# Setters # Setters
## ##
def setLastBuild(self, buildID: str) -> None: def setBuildsState(self, lastBuild: str, order: list[str]) -> None:
"""Set the last active build id.""" """Set the last active build id."""
if buildID != self._lastBuild: for i, key in enumerate(order):
self._lastBuild = buildID if build := self._builds.get(key):
self._saveCollection() build.setOrder(i)
self._lastBuild = lastBuild
self._saveCollection()
return return
def setDefaultBuild(self, buildID: str) -> None: def setDefaultBuild(self, buildID: str) -> None:
@@ -512,8 +529,7 @@ class BuildCollection:
def setBuild(self, build: BuildSettings) -> None: def setBuild(self, build: BuildSettings) -> None:
"""Set build settings data in the collection.""" """Set build settings data in the collection."""
if isinstance(build, BuildSettings): if isinstance(build, BuildSettings):
buildID = build.buildID self._builds[build.buildID] = build
self._builds[buildID] = build.pack()
self._saveCollection() self._saveCollection()
return return
@@ -522,15 +538,15 @@ class BuildCollection:
## ##
def removeBuild(self, buildID: str) -> None: 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._builds.pop(buildID, None)
self._saveCollection() self._saveCollection()
return return
def builds(self) -> Iterable[tuple[str, str]]: def builds(self) -> Iterable[tuple[str, str]]:
"""Iterate over all available builds.""" """Iterate over all available builds."""
for buildID in self._builds: for buildID, build in sorted(self._builds.items(), key=lambda x: x[1].order):
yield buildID, self._builds[buildID].get("name", "") yield buildID, build.name
return return
## ##
@@ -569,7 +585,7 @@ class BuildCollection:
elif key == "defaultBuild": elif key == "defaultBuild":
self._defaultBuild = str(entry) self._defaultBuild = str(entry)
elif isinstance(entry, dict): elif isinstance(entry, dict):
self._builds[key] = entry self._builds[key] = BuildSettings.fromDict(entry)
return True return True
@@ -581,11 +597,11 @@ class BuildCollection:
logger.debug("Saving builds file") logger.debug("Saving builds file")
try: try:
data = { data: dict[str, str | dict] = {
"lastBuild": self._lastBuild, "lastBuild": self._lastBuild,
"defaultBuild": self._defaultBuild, "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: with open(buildsFile, mode="w+", encoding="utf-8") as outFile:
outFile.write(jsonEncode({"novelWriter.builds": data}, nmax=4)) outFile.write(jsonEncode({"novelWriter.builds": data}, nmax=4))
except Exception: except Exception:
+2
View File
@@ -353,11 +353,13 @@ class ProjectBuilder:
projName = data.get("projName", lblNewProject) projName = data.get("projName", lblNewProject)
projTitle = data.get("projTitle", lblNewProject) projTitle = data.get("projTitle", lblNewProject)
projAuthor = data.get("projAuthor", "") projAuthor = data.get("projAuthor", "")
projLang = data.get("projLang", "en_GB")
project.data.setUuid(None) project.data.setUuid(None)
project.data.setName(projName) project.data.setName(projName)
project.data.setTitle(projTitle) project.data.setTitle(projTitle)
project.data.setAuthor(projAuthor) project.data.setAuthor(projAuthor)
project.data.setLanguage(projLang)
project.setDefaultStatusImport() project.setDefaultStatusImport()
project.session.startSession() project.session.startSession()
+1 -5
View File
@@ -248,13 +248,9 @@ class NWBuildDocument:
def _setupBuild(self, bldObj: Tokenizer) -> dict: def _setupBuild(self, bldObj: Tokenizer) -> dict:
"""Configure the build object.""" """Configure the build object."""
# Get Settings # Get Settings
buildLang = self._build.getStr("format.buildLang")
textFont = self._build.getStr("format.textFont") textFont = self._build.getStr("format.textFont")
textSize = self._build.getInt("format.textSize") textSize = self._build.getInt("format.textSize")
# The language lookup dict is reloaded if needed
self._project.setProjectLang(buildLang)
fontFamily = textFont or CONFIG.textFont fontFamily = textFont or CONFIG.textFont
bldFont = QFont(fontFamily, textSize) bldFont = QFont(fontFamily, textSize)
fontInfo = QFontInfo(bldFont) fontInfo = QFontInfo(bldFont)
@@ -287,7 +283,7 @@ class NWBuildDocument:
if isinstance(bldObj, ToOdt): if isinstance(bldObj, ToOdt):
bldObj.setColourHeaders(self._build.getBool("odt.addColours")) 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) 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)) 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 """Return the initial count of words for novel and note
documents. documents.
""" """
return tuple(self._initCounts) return self._initCounts[0], self._initCounts[1]
@property @property
def currCounts(self) -> tuple[int, int]: def currCounts(self) -> tuple[int, int]:
"""Return the current count of words for novel and note """Return the current count of words for novel and note
documents. documents.
""" """
return tuple(self._currCounts) return self._currCounts[0], self._currCounts[1]
@property @property
def lastHandle(self) -> dict[str, str | None]: def lastHandle(self) -> dict[str, str | None]:
+1 -2
View File
@@ -456,11 +456,10 @@ class ToHtml(Tokenizer):
def _formatSynopsis(self, text: str) -> str: def _formatSynopsis(self, text: str) -> str:
"""Apply HTML formatting to synopsis.""" """Apply HTML formatting to synopsis."""
sSynop = self._localLookup("Synopsis")
if self._genMode == self.M_PREVIEW: if self._genMode == self.M_PREVIEW:
sSynop = self._trSynopsis
return f"<p class='comment'><span class='synopsis'>{sSynop}:</span> {text}</p>\n" return f"<p class='comment'><span class='synopsis'>{sSynop}:</span> {text}</p>\n"
else: else:
sSynop = self._localLookup("Synopsis")
return f"<p class='synopsis'><strong>{sSynop}:</strong> {text}</p>\n" return f"<p class='synopsis'><strong>{sSynop}:</strong> {text}</p>\n"
def _formatComments(self, text: str) -> str: def _formatComments(self, text: str) -> str:
-3
View File
@@ -167,9 +167,6 @@ class Tokenizer(ABC):
self._localLookup = self._project.localLookup self._localLookup = self._project.localLookup
self.tr = partial(QCoreApplication.translate, "Tokenizer") self.tr = partial(QCoreApplication.translate, "Tokenizer")
# Cached Translations
self._trSynopsis = self.tr("Synopsis")
# Format RegEx # Format RegEx
self._rxMarkdown = [ self._rxMarkdown = [
(QRegularExpression(nwRegEx.FMT_EI), [0, self.FMT_I_B, 0, self.FMT_I_E]), (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 import logging
from PyQt5.QtGui import QFont from PyQt5.QtGui import QFont
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox, QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox,
QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox
@@ -43,12 +43,11 @@ logger = logging.getLogger(__name__)
class GuiPreferences(NPagedDialog): class GuiPreferences(NPagedDialog):
def __init__(self, mainGui): def __init__(self, parent: QWidget) -> None:
super().__init__(parent=mainGui) super().__init__(parent=parent)
logger.debug("Create: GuiPreferences") logger.debug("Create: GuiPreferences")
self.setObjectName("GuiPreferences") self.setObjectName("GuiPreferences")
self.setWindowTitle(self.tr("Preferences")) self.setWindowTitle(self.tr("Preferences"))
self.tabGeneral = GuiPreferencesGeneral(self) self.tabGeneral = GuiPreferencesGeneral(self)
@@ -93,26 +92,27 @@ class GuiPreferences(NPagedDialog):
## ##
@property @property
def updateTheme(self): def updateTheme(self) -> bool:
return self._updateTheme return self._updateTheme
@property @property
def updateSyntax(self): def updateSyntax(self) -> bool:
return self._updateSyntax return self._updateSyntax
@property @property
def needsRestart(self): def needsRestart(self) -> bool:
return self._needsRestart return self._needsRestart
@property @property
def refreshTree(self): def refreshTree(self) -> bool:
return self._refreshTree 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 """Trigger all the save functions in the tabs, and collect the
status of the saves. status of the saves.
""" """
@@ -132,9 +132,9 @@ class GuiPreferences(NPagedDialog):
return return
def _doClose(self): @pyqtSlot()
"""Close the preferences without saving the changes. def _doClose(self) -> None:
""" """Close the preferences without saving the changes."""
self._saveWindowSize() self._saveWindowSize()
self.reject() self.reject()
return return
@@ -143,9 +143,8 @@ class GuiPreferences(NPagedDialog):
# Internal Functions # Internal Functions
## ##
def _saveWindowSize(self): def _saveWindowSize(self) -> None:
"""Save the dialog window size. """Save the dialog window size."""
"""
CONFIG.setPreferencesWinSize(self.width(), self.height()) CONFIG.setPreferencesWinSize(self.width(), self.height())
return return
@@ -154,7 +153,7 @@ class GuiPreferences(NPagedDialog):
class GuiPreferencesGeneral(QWidget): class GuiPreferencesGeneral(QWidget):
def __init__(self, prefsGui): def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui) super().__init__(parent=prefsGui)
self.prefsGui = prefsGui self.prefsGui = prefsGui
@@ -285,9 +284,8 @@ class GuiPreferencesGeneral(QWidget):
return return
def saveValues(self): def saveValues(self) -> None:
"""Save the values set for this tab. """Save the values set for this tab."""
"""
guiLocale = self.guiLocale.currentData() guiLocale = self.guiLocale.currentData()
guiTheme = self.guiTheme.currentData() guiTheme = self.guiTheme.currentData()
guiSyntax = self.guiSyntax.currentData() guiSyntax = self.guiSyntax.currentData()
@@ -316,12 +314,12 @@ class GuiPreferencesGeneral(QWidget):
return return
## ##
# Slots # Private Slots
## ##
def _selectFont(self): @pyqtSlot()
"""Open the QFontDialog and set a font for the font style. def _selectFont(self) -> None:
""" """Open the QFontDialog and set a font for the font style."""
currFont = QFont() currFont = QFont()
currFont.setFamily(CONFIG.guiFont) currFont.setFamily(CONFIG.guiFont)
currFont.setPointSize(CONFIG.guiFontSize) currFont.setPointSize(CONFIG.guiFontSize)
@@ -336,7 +334,7 @@ class GuiPreferencesGeneral(QWidget):
class GuiPreferencesProjects(QWidget): class GuiPreferencesProjects(QWidget):
def __init__(self, prefsGui): def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui) super().__init__(parent=prefsGui)
# The Form # The Form
@@ -438,9 +436,8 @@ class GuiPreferencesProjects(QWidget):
return return
def saveValues(self): def saveValues(self) -> None:
"""Save the values set for this tab. """Save the values set for this tab."""
"""
# Automatic Save # Automatic Save
CONFIG.autoSaveDoc = self.autoSaveDoc.value() CONFIG.autoSaveDoc = self.autoSaveDoc.value()
CONFIG.autoSaveProj = self.autoSaveProj.value() CONFIG.autoSaveProj = self.autoSaveProj.value()
@@ -457,12 +454,12 @@ class GuiPreferencesProjects(QWidget):
return return
## ##
# Slots # Private Slots
## ##
def _backupFolder(self): @pyqtSlot()
"""Open a dialog to select the backup folder. def _backupFolder(self) -> None:
""" """Open a dialog to select the backup folder."""
currDir = self.backupPath or "" currDir = self.backupPath or ""
newDir = QFileDialog.getExistingDirectory( newDir = QFileDialog.getExistingDirectory(
self, self.tr("Backup Directory"), str(currDir), options=QFileDialog.ShowDirsOnly self, self.tr("Backup Directory"), str(currDir), options=QFileDialog.ShowDirsOnly
@@ -472,15 +469,13 @@ class GuiPreferencesProjects(QWidget):
self.mainForm.setHelpText( self.mainForm.setHelpText(
self.backupPathRow, self.tr("Path: {0}").format(self.backupPath) self.backupPathRow, self.tr("Path: {0}").format(self.backupPath)
) )
return True return
return
return False @pyqtSlot(bool)
def _toggledBackupOnClose(self, state: bool) -> None:
def _toggledBackupOnClose(self, theState): """Toggle switch that depends on the backup on close switch."""
"""Enable or disable switch that depends on the backup on close self.askBeforeBackup.setEnabled(state)
switch.
"""
self.askBeforeBackup.setEnabled(theState)
return return
# END Class GuiPreferencesProjects # END Class GuiPreferencesProjects
@@ -488,7 +483,7 @@ class GuiPreferencesProjects(QWidget):
class GuiPreferencesDocuments(QWidget): class GuiPreferencesDocuments(QWidget):
def __init__(self, prefsGui): def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui) super().__init__(parent=prefsGui)
# The Form # The Form
@@ -604,9 +599,8 @@ class GuiPreferencesDocuments(QWidget):
return return
def saveValues(self): def saveValues(self) -> None:
"""Save the values set for this tab. """Save the values set for this tab."""
"""
# Text Style # Text Style
CONFIG.setTextFont(self.textFont.text(), self.textSize.value()) CONFIG.setTextFont(self.textFont.text(), self.textSize.value())
@@ -621,12 +615,12 @@ class GuiPreferencesDocuments(QWidget):
return return
## ##
# Slots # Private Slots
## ##
@pyqtSlot()
def _selectFont(self): 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 = QFont()
currFont.setFamily(CONFIG.textFont) currFont.setFamily(CONFIG.textFont)
currFont.setPointSize(CONFIG.textSize) currFont.setPointSize(CONFIG.textSize)
@@ -642,7 +636,7 @@ class GuiPreferencesDocuments(QWidget):
class GuiPreferencesEditor(QWidget): class GuiPreferencesEditor(QWidget):
def __init__(self, prefsGui): def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui) super().__init__(parent=prefsGui)
# The Form # The Form
@@ -726,6 +720,15 @@ class GuiPreferencesEditor(QWidget):
# ================ # ================
self.mainForm.addGroupLabel(self.tr("Scroll Behaviour")) 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 # Typewriter Scrolling
self.autoScroll = NSwitch() self.autoScroll = NSwitch()
self.autoScroll.setChecked(CONFIG.autoScroll) self.autoScroll.setChecked(CONFIG.autoScroll)
@@ -750,7 +753,7 @@ class GuiPreferencesEditor(QWidget):
return return
def saveValues(self): def saveValues(self) -> None:
"""Save the values set for this tab.""" """Save the values set for this tab."""
# Spell Checking # Spell Checking
CONFIG.spellLanguage = self.spellLanguage.currentData() CONFIG.spellLanguage = self.spellLanguage.currentData()
@@ -766,6 +769,7 @@ class GuiPreferencesEditor(QWidget):
# Scroll Behaviour # Scroll Behaviour
CONFIG.autoScroll = self.autoScroll.isChecked() CONFIG.autoScroll = self.autoScroll.isChecked()
CONFIG.autoScrollPos = self.autoScrollPos.value() CONFIG.autoScrollPos = self.autoScrollPos.value()
CONFIG.scrollPastEnd = self.scrollPastEnd.isChecked()
return return
@@ -774,7 +778,7 @@ class GuiPreferencesEditor(QWidget):
class GuiPreferencesSyntax(QWidget): class GuiPreferencesSyntax(QWidget):
def __init__(self, prefsGui): def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui) super().__init__(parent=prefsGui)
self.prefsGui = prefsGui self.prefsGui = prefsGui
@@ -840,9 +844,8 @@ class GuiPreferencesSyntax(QWidget):
return return
def saveValues(self): def saveValues(self) -> None:
"""Save the values set for this tab. """Save the values set for this tab."""
"""
highlightQuotes = self.highlightQuotes.isChecked() highlightQuotes = self.highlightQuotes.isChecked()
allowOpenSQuote = self.allowOpenSQuote.isChecked() allowOpenSQuote = self.allowOpenSQuote.isChecked()
allowOpenDQuote = self.allowOpenDQuote.isChecked() allowOpenDQuote = self.allowOpenDQuote.isChecked()
@@ -862,15 +865,14 @@ class GuiPreferencesSyntax(QWidget):
return return
## ##
# Slots # Private Slots
## ##
def _toggleHighlightQuotes(self, theState): @pyqtSlot(bool)
"""Enables or disables switches controlled by the highlight def _toggleHighlightQuotes(self, state: bool) -> None:
quotes switch. """Toggle switches controlled by the highlight quotes switch."""
""" self.allowOpenSQuote.setEnabled(state)
self.allowOpenSQuote.setEnabled(theState) self.allowOpenDQuote.setEnabled(state)
self.allowOpenDQuote.setEnabled(theState)
return return
# END Class GuiPreferencesSyntax # END Class GuiPreferencesSyntax
@@ -878,7 +880,7 @@ class GuiPreferencesSyntax(QWidget):
class GuiPreferencesAutomation(QWidget): class GuiPreferencesAutomation(QWidget):
def __init__(self, prefsGui): def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui) super().__init__(parent=prefsGui)
# The Form # The Form
@@ -989,9 +991,8 @@ class GuiPreferencesAutomation(QWidget):
return return
def saveValues(self): def saveValues(self) -> None:
"""Save the values set for this tab. """Save the values set for this tab."""
"""
# Automatic Features # Automatic Features
CONFIG.autoSelect = self.autoSelect.isChecked() CONFIG.autoSelect = self.autoSelect.isChecked()
CONFIG.doReplace = self.doReplace.isChecked() CONFIG.doReplace = self.doReplace.isChecked()
@@ -1010,18 +1011,17 @@ class GuiPreferencesAutomation(QWidget):
return return
## ##
# Slots # Private Slots
## ##
def _toggleAutoReplaceMain(self, theState): @pyqtSlot(bool)
"""Enables or disables switches controlled by the main auto def _toggleAutoReplaceMain(self, state: bool) -> None:
replace switch. """Toggle switches controlled by the auto replace switch."""
""" self.doReplaceSQuote.setEnabled(state)
self.doReplaceSQuote.setEnabled(theState) self.doReplaceDQuote.setEnabled(state)
self.doReplaceDQuote.setEnabled(theState) self.doReplaceDash.setEnabled(state)
self.doReplaceDash.setEnabled(theState) self.doReplaceDots.setEnabled(state)
self.doReplaceDots.setEnabled(theState) self.fmtPadThin.setEnabled(state)
self.fmtPadThin.setEnabled(theState)
return return
# END Class GuiPreferencesAutomation # END Class GuiPreferencesAutomation
@@ -1029,7 +1029,7 @@ class GuiPreferencesAutomation(QWidget):
class GuiPreferencesQuotes(QWidget): class GuiPreferencesQuotes(QWidget):
def __init__(self, prefsGui): def __init__(self, prefsGui: GuiPreferences) -> None:
super().__init__(parent=prefsGui) super().__init__(parent=prefsGui)
# The Form # The Form
@@ -1113,9 +1113,8 @@ class GuiPreferencesQuotes(QWidget):
return return
def saveValues(self): def saveValues(self) -> None:
"""Save the values set for this tab. """Save the values set for this tab."""
"""
# Quotation Style # Quotation Style
CONFIG.fmtSQuoteOpen = self.quoteSym["SO"].text() CONFIG.fmtSQuoteOpen = self.quoteSym["SO"].text()
CONFIG.fmtSQuoteClose = self.quoteSym["SC"].text() CONFIG.fmtSQuoteClose = self.quoteSym["SC"].text()
@@ -1124,12 +1123,11 @@ class GuiPreferencesQuotes(QWidget):
return return
## ##
# Slots # Internal Functions
## ##
def _getQuote(self, qType): def _getQuote(self, qType: str) -> None:
"""Dialog for single quote open. """Dialog for single quote open."""
"""
qtBox = GuiQuoteSelect(self, currentQuote=self.quoteSym[qType].text()) qtBox = GuiQuoteSelect(self, currentQuote=self.quoteSym[qType].text())
if qtBox.exec_() == QDialog.Accepted: if qtBox.exec_() == QDialog.Accepted:
self.quoteSym[qType].setText(qtBox.selectedQuote) self.quoteSym[qType].setText(qtBox.selectedQuote)
+101 -110
View File
@@ -96,21 +96,22 @@ class GuiProjectSettings(NPagedDialog):
return return
def __del__(self): # pragma: no cover def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiProjectSettings") logger.debug("Delete: GuiProjectSettings")
return return
## ##
# Slots # Private Slots
## ##
def _doSave(self): @pyqtSlot()
"""Save settings and close dialog. def _doSave(self) -> None:
""" """Save settings and close dialog."""
project = SHARED.project project = SHARED.project
projName = self.tabMain.editName.text() projName = self.tabMain.editName.text()
bookTitle = self.tabMain.editTitle.text() bookTitle = self.tabMain.editTitle.text()
bookAuthor = self.tabMain.editAuthor.text() bookAuthor = self.tabMain.editAuthor.text()
projLang = self.tabMain.projLang.currentData()
spellLang = self.tabMain.spellLang.currentData() spellLang = self.tabMain.spellLang.currentData()
doBackup = not self.tabMain.doBackup.isChecked() doBackup = not self.tabMain.doBackup.isChecked()
@@ -119,6 +120,7 @@ class GuiProjectSettings(NPagedDialog):
project.data.setAuthor(bookAuthor) project.data.setAuthor(bookAuthor)
project.data.setDoBackup(doBackup) project.data.setDoBackup(doBackup)
project.data.setSpellLang(spellLang) project.data.setSpellLang(spellLang)
project.setProjectLang(projLang)
if self.tabStatus.colChanged: if self.tabStatus.colChanged:
newList, delList = self.tabStatus.getNewList() newList, delList = self.tabStatus.getNewList()
@@ -140,9 +142,9 @@ class GuiProjectSettings(NPagedDialog):
return return
def _doClose(self): @pyqtSlot()
"""Save settings and close the dialog. def _doClose(self) -> None:
""" """Save settings and close the dialog."""
self._saveGuiSettings() self._saveGuiSettings()
self.reject() self.reject()
return return
@@ -151,9 +153,8 @@ class GuiProjectSettings(NPagedDialog):
# Internal Functions # Internal Functions
## ##
def _focusTab(self, tab): def _focusTab(self, tab: int) -> None:
"""Change which is the focused tab. """Change which is the focused tab."""
"""
if tab == self.TAB_MAIN: if tab == self.TAB_MAIN:
self.setCurrentWidget(self.tabMain) self.setCurrentWidget(self.tabMain)
elif tab == self.TAB_STATUS: elif tab == self.TAB_STATUS:
@@ -164,9 +165,8 @@ class GuiProjectSettings(NPagedDialog):
self.setCurrentWidget(self.tabReplace) self.setCurrentWidget(self.tabReplace)
return return
def _saveGuiSettings(self): def _saveGuiSettings(self) -> None:
"""Save GUI settings. """Save GUI settings."""
"""
winWidth = CONFIG.rpxInt(self.width()) winWidth = CONFIG.rpxInt(self.width())
winHeight = CONFIG.rpxInt(self.height()) winHeight = CONFIG.rpxInt(self.height())
replaceColW = CONFIG.rpxInt(self.tabReplace.listBox.columnWidth(0)) replaceColW = CONFIG.rpxInt(self.tabReplace.listBox.columnWidth(0))
@@ -187,8 +187,8 @@ class GuiProjectSettings(NPagedDialog):
class GuiProjectEditMain(QWidget): class GuiProjectEditMain(QWidget):
def __init__(self, projGui): def __init__(self, parent: QWidget) -> None:
super().__init__(parent=projGui) super().__init__(parent=parent)
# The Form # The Form
self.mainForm = NConfigLayout() self.mainForm = NConfigLayout()
@@ -230,24 +230,41 @@ class GuiProjectEditMain(QWidget):
self.tr("Change whenever you want!") 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 = QComboBox(self)
self.spellLang.setMaximumWidth(xW) self.spellLang.setMaximumWidth(xW)
self.spellLang.addItem(self.tr("Default"), "None") self.spellLang.addItem(self.tr("Default"), "None")
if CONFIG.hasEnchant: if CONFIG.hasEnchant:
for tag, language in SHARED.spelling.listDictionaries(): for tag, language in SHARED.spelling.listDictionaries():
self.spellLang.addItem(language, tag) self.spellLang.addItem(language, tag)
self.mainForm.addRow( self.mainForm.addRow(
self.tr("Spell check language"), self.tr("Spell check language"),
self.spellLang, self.spellLang,
self.tr("Overrides main preferences.") self.tr("Overrides main preferences.")
) )
spellIdx = 0 langIdx = 0
if pData.spellLang is not None: if pData.spellLang is not None:
spellIdx = self.spellLang.findData(pData.spellLang) langIdx = self.spellLang.findData(pData.spellLang)
if spellIdx != -1: if langIdx != -1:
self.spellLang.setCurrentIndex(spellIdx) self.spellLang.setCurrentIndex(langIdx)
self.doBackup = NSwitch(self) self.doBackup = NSwitch(self)
self.doBackup.setChecked(not pData.doBackup) self.doBackup.setChecked(not pData.doBackup)
@@ -271,8 +288,8 @@ class GuiProjectEditStatus(QWidget):
COL_ROLE = Qt.ItemDataRole.UserRole + 1 COL_ROLE = Qt.ItemDataRole.UserRole + 1
NUM_ROLE = Qt.ItemDataRole.UserRole + 2 NUM_ROLE = Qt.ItemDataRole.UserRole + 2
def __init__(self, projGui, isStatus): def __init__(self, parent: QWidget, isStatus: bool) -> None:
super().__init__(parent=projGui) super().__init__(parent=parent)
if isStatus: if isStatus:
self.theStatus = SHARED.project.data.itemStatus self.theStatus = SHARED.project.data.itemStatus
@@ -372,9 +389,8 @@ class GuiProjectEditStatus(QWidget):
return return
def getNewList(self): def getNewList(self) -> tuple[list, list]:
"""Return list of entries. """Return list of entries."""
"""
if self.colChanged: if self.colChanged:
newList = [] newList = []
for n in range(self.listBox.topLevelItemCount()): for n in range(self.listBox.topLevelItemCount()):
@@ -394,9 +410,8 @@ class GuiProjectEditStatus(QWidget):
## ##
@pyqtSlot() @pyqtSlot()
def _selectColour(self): def _selectColour(self) -> None:
"""Open a dialog to select the status icon colour. """Open a dialog to select the status icon colour."""
"""
if self.selColour is not None: if self.selColour is not None:
newCol = QColorDialog.getColor( newCol = QColorDialog.getColor(
self.selColour, self, self.tr("Select Colour") self.selColour, self, self.tr("Select Colour")
@@ -410,17 +425,15 @@ class GuiProjectEditStatus(QWidget):
return return
@pyqtSlot() @pyqtSlot()
def _newItem(self): def _newItem(self) -> None:
"""Create a new status item. """Create a new status item."""
"""
self._addItem(None, self.tr("New Item"), (100, 100, 100), 0) self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
self.colChanged = True self.colChanged = True
return return
@pyqtSlot() @pyqtSlot()
def _delItem(self): def _delItem(self) -> None:
"""Delete a status item. """Delete a status item."""
"""
selItem = self._getSelectedItem() selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem): if isinstance(selItem, QTreeWidgetItem):
iRow = self.listBox.indexOfTopLevelItem(selItem) iRow = self.listBox.indexOfTopLevelItem(selItem)
@@ -433,9 +446,8 @@ class GuiProjectEditStatus(QWidget):
return return
@pyqtSlot() @pyqtSlot()
def _saveItem(self): def _saveItem(self) -> None:
"""Save changes made to a status item. """Save changes made to a status item."""
"""
selItem = self._getSelectedItem() selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem): if isinstance(selItem, QTreeWidgetItem):
selItem.setText(self.COL_LABEL, simplified(self.editName.text())) 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.selColour.red(), self.selColour.green(), self.selColour.blue()
)) ))
self.colChanged = True self.colChanged = True
return return
@pyqtSlot() @pyqtSlot()
def _selectedItem(self): def _selectedItem(self) -> None:
"""Extract the info of a selected item and populate the settings """Extract the info of a selected item and populate the settings
boxes and button. If no item is selected, clear the form. boxes and button. If no item is selected, clear the form.
""" """
@@ -456,7 +467,6 @@ class GuiProjectEditStatus(QWidget):
if isinstance(selItem, QTreeWidgetItem): if isinstance(selItem, QTreeWidgetItem):
cols = selItem.data(self.COL_LABEL, self.COL_ROLE) cols = selItem.data(self.COL_LABEL, self.COL_ROLE)
name = selItem.text(self.COL_LABEL) name = selItem.text(self.COL_LABEL)
pixmap = QPixmap(self.iPx, self.iPx) pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(*cols)) pixmap.fill(QColor(*cols))
self.selColour = QColor(*cols) self.selColour = QColor(*cols)
@@ -464,31 +474,27 @@ class GuiProjectEditStatus(QWidget):
self.colButton.setIcon(QIcon(pixmap)) self.colButton.setIcon(QIcon(pixmap))
self.editName.selectAll() self.editName.selectAll()
self.editName.setFocus() self.editName.setFocus()
self.editName.setEnabled(True) self.editName.setEnabled(True)
self.colButton.setEnabled(True) self.colButton.setEnabled(True)
self.saveButton.setEnabled(True) self.saveButton.setEnabled(True)
else: else:
pixmap = QPixmap(self.iPx, self.iPx) pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(100, 100, 100)) pixmap.fill(QColor(100, 100, 100))
self.selColour = QColor(100, 100, 100) self.selColour = QColor(100, 100, 100)
self.editName.setText("") self.editName.setText("")
self.colButton.setIcon(QIcon(pixmap)) self.colButton.setIcon(QIcon(pixmap))
self.editName.setEnabled(False) self.editName.setEnabled(False)
self.colButton.setEnabled(False) self.colButton.setEnabled(False)
self.saveButton.setEnabled(False) self.saveButton.setEnabled(False)
return return
## ##
# Internal Functions # Internal Functions
## ##
def _addItem(self, key, name, cols, count): def _addItem(self, key: str | None, name: str,
"""Add a status item to the list. cols: tuple[int, int, int], count: int) -> None:
""" """Add a status item to the list."""
pixmap = QPixmap(self.iPx, self.iPx) pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(*cols)) pixmap.fill(QColor(*cols))
@@ -504,9 +510,8 @@ class GuiProjectEditStatus(QWidget):
return return
def _moveItem(self, step): def _moveItem(self, step: int) -> None:
"""Move and item up or down step. """Move and item up or down step."""
"""
selItem = self._getSelectedItem() selItem = self._getSelectedItem()
if selItem is None: if selItem is None:
return return
@@ -527,17 +532,15 @@ class GuiProjectEditStatus(QWidget):
return return
def _getSelectedItem(self): def _getSelectedItem(self) -> QTreeWidgetItem | None:
"""Get the currently selected item. """Get the currently selected item."""
"""
selItem = self.listBox.selectedItems() selItem = self.listBox.selectedItems()
if len(selItem) > 0: if len(selItem) > 0:
return selItem[0] return selItem[0]
return None return None
def _usageString(self, nUse): def _usageString(self, nUse: int) -> str:
"""Generate usage string. """Generate usage string."""
"""
if nUse == 0: if nUse == 0:
return self.tr("Not in use") return self.tr("Not in use")
elif nUse == 1: elif nUse == 1:
@@ -553,8 +556,8 @@ class GuiProjectEditReplace(QWidget):
COL_KEY = 0 COL_KEY = 0
COL_REPL = 1 COL_REPL = 1
def __init__(self, projGui): def __init__(self, parent: QWidget) -> None:
super().__init__(parent=projGui) super().__init__(parent=parent)
self.arChanged = False self.arChanged = False
@@ -635,25 +638,23 @@ class GuiProjectEditReplace(QWidget):
return return
def getNewList(self): def getNewList(self) -> dict:
"""Extract the list from the widget. """Extract the list from the widget."""
""" new = {}
newList = {}
for n in range(self.listBox.topLevelItemCount()): for n in range(self.listBox.topLevelItemCount()):
tItem = self.listBox.topLevelItem(n) tItem = self.listBox.topLevelItem(n)
if tItem is not None: if tItem is not None:
aKey = self._stripNotAllowed(tItem.text(0)) aKey = self._stripNotAllowed(tItem.text(0))
aVal = tItem.text(1) aVal = tItem.text(1)
if len(aKey) > 0: if len(aKey) > 0:
newList[aKey] = aVal new[aKey] = aVal
return new
return newList
## ##
# Internal Functions # Internal Functions
## ##
def _selectedItem(self): def _selectedItem(self) -> bool:
"""Extract the details from the selected item and populate the """Extract the details from the selected item and populate the
edit form. edit form.
""" """
@@ -670,63 +671,53 @@ class GuiProjectEditReplace(QWidget):
self.editKey.setFocus() self.editKey.setFocus()
return True return True
def _saveEntry(self): def _saveEntry(self) -> None:
"""Save the form data into the list widget. """Save the form data into the list widget."""
"""
selItem = self._getSelectedItem() selItem = self._getSelectedItem()
if selItem is None: if selItem:
return False newKey = self.editKey.text()
newVal = self.editValue.text()
newKey = self.editKey.text() saveKey = self._stripNotAllowed(newKey)
newVal = self.editValue.text() if len(saveKey) > 0 and len(newVal) > 0:
saveKey = self._stripNotAllowed(newKey) selItem.setText(self.COL_KEY, "<%s>" % saveKey)
selItem.setText(self.COL_REPL, newVal)
if len(saveKey) > 0 and len(newVal) > 0: self.editKey.clear()
selItem.setText(self.COL_KEY, "<%s>" % saveKey) self.editValue.clear()
selItem.setText(self.COL_REPL, newVal) self.editKey.setEnabled(False)
self.editKey.clear() self.editValue.setEnabled(False)
self.editValue.clear() self.listBox.clearSelection()
self.editKey.setEnabled(False) self.arChanged = True
self.editValue.setEnabled(False)
self.listBox.clearSelection()
self.arChanged = True
return return
def _addEntry(self): def _addEntry(self) -> None:
"""Add a new list entry. """Add a new list entry."""
"""
saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1) saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
newVal = "" newVal = ""
newItem = QTreeWidgetItem([saveKey, newVal]) newItem = QTreeWidgetItem([saveKey, newVal])
self.listBox.addTopLevelItem(newItem) self.listBox.addTopLevelItem(newItem)
return True return
def _delEntry(self): def _delEntry(self) -> None:
"""Delete the selected entry. """Delete the selected entry."""
"""
selItem = self._getSelectedItem() selItem = self._getSelectedItem()
if selItem is None: if selItem:
return False self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem)) self.arChanged = True
self.arChanged = True return
return True
def _getSelectedItem(self): def _getSelectedItem(self) -> QTreeWidgetItem | None:
"""Extract the currently selected item. """Extract the currently selected item."""
"""
selItem = self.listBox.selectedItems() selItem = self.listBox.selectedItems()
if len(selItem) == 0: if len(selItem) == 0:
return None return None
return selItem[0] return selItem[0]
def _stripNotAllowed(self, theKey): def _stripNotAllowed(self, key: str) -> str:
"""Clean up the replace key string. """Clean up the replace key string."""
""" result = ""
retKey = "" for c in key:
for c in theKey:
if c.isalnum(): if c.isalnum():
retKey += c result += c
return retKey return result
# END Class GuiProjectEditReplace # 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 Rewritten: 2020-06-15 [0.9] GuiDocEditSearch
Created: 2020-06-27 [0.10] GuiDocEditFooter Created: 2020-06-27 [0.10] GuiDocEditFooter
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter 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 This file is a part of novelWriter
Copyright 20182023, Veronica Berglyd Olsen Copyright 20182023, Veronica Berglyd Olsen
@@ -67,6 +69,16 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__) 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): class GuiDocEditor(QPlainTextEdit):
"""Gui Widget: Main Document Editor""" """Gui Widget: Main Document Editor"""
@@ -152,7 +164,6 @@ class GuiDocEditor(QPlainTextEdit):
self.setMinimumWidth(CONFIG.pxInt(300)) self.setMinimumWidth(CONFIG.pxInt(300))
self.setAutoFillBackground(True) self.setAutoFillBackground(True)
self.setFrameStyle(QFrame.NoFrame) self.setFrameStyle(QFrame.NoFrame)
self.setCenterOnScroll(True)
# Custom Shortcuts # Custom Shortcuts
self.keyContext = QShortcut(self) self.keyContext = QShortcut(self)
@@ -330,7 +341,8 @@ class GuiDocEditor(QPlainTextEdit):
self._qDocument.setDefaultTextOption(options) self._qDocument.setDefaultTextOption(options)
# Scroll bars # Scrolling
self.setCenterOnScroll(CONFIG.scrollPastEnd)
if CONFIG.hideVScroll: if CONFIG.hideVScroll:
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
else: else:
@@ -996,7 +1008,9 @@ class GuiDocEditor(QPlainTextEdit):
return return
text = block.text() 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() cursor = self.textCursor()
bPos = cursor.positionInBlock() bPos = cursor.positionInBlock()
if bPos > 0: if bPos > 0:
@@ -1004,8 +1018,10 @@ class GuiDocEditor(QPlainTextEdit):
point = self.cursorRect().bottomRight() point = self.cursorRect().bottomRight()
self._completer.move(self.viewport().mapToGlobal(point)) self._completer.move(self.viewport().mapToGlobal(point))
self._completer.setVisible(show) self._completer.setVisible(show)
else:
self._completer.setVisible(False)
elif self._doReplace and added == 1: if self._doReplace and added == 1:
self._docAutoReplace(text) self._docAutoReplace(text)
return return
@@ -1410,17 +1426,28 @@ class GuiDocEditor(QPlainTextEdit):
If more than one block is selected, the formatting is applied to If more than one block is selected, the formatting is applied to
the first block. the first block.
""" """
cursor = self._autoSelect() cursor = self.textCursor()
if not cursor.hasSelection(): posO = cursor.position()
logger.warning("No selection made, nothing to do") if cursor.hasSelection():
return False 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() posS = cursor.selectionStart()
posE = cursor.selectionEnd() 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) blockS = self._qDocument.findBlock(posS)
blockE = self._qDocument.findBlock(posE) blockE = self._qDocument.findBlock(posE)
if blockS != blockE: if blockS != blockE:
posE = blockS.position() + blockS.length() - 1 posE = blockS.position() + blockS.length() - 1
cursor.clearSelection() cursor.clearSelection()
@@ -1443,34 +1470,26 @@ class GuiDocEditor(QPlainTextEdit):
break break
if fLen == min(numA, numB): 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: else:
self._wrapSelection(fChar*fLen) self._wrapSelection(fChar*fLen, pos=posO, select=select)
return True return True
def _clearSurrounding(self, cursor: QTextCursor, nChars: int) -> bool: def _wrapSelection(self, before: str, after: str | None = None, pos: int | None = None,
"""Clear n characters before and after the cursor.""" select: _SelectAction = _SelectAction.NO_DECISION) -> bool:
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:
"""Wrap the selected text in whatever is in tBefore and tAfter. """Wrap the selected text in whatever is in tBefore and tAfter.
If there is no selection, the autoSelect setting decides the If there is no selection, the autoSelect setting decides the
action. AutoSelect will select the word under the cursor before action. AutoSelect will select the word under the cursor before
@@ -1479,10 +1498,17 @@ class GuiDocEditor(QPlainTextEdit):
if after is None: if after is None:
after = before after = before
cursor = self._autoSelect() cursor = self.textCursor()
if not cursor.hasSelection(): posO = pos if isinstance(pos, int) else cursor.position()
logger.warning("No selection made, nothing to do") if select == _SelectAction.NO_DECISION:
return False 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() posS = cursor.selectionStart()
posE = cursor.selectionEnd() posE = cursor.selectionEnd()
@@ -1500,8 +1526,14 @@ class GuiDocEditor(QPlainTextEdit):
cursor.insertText(before) cursor.insertText(before)
cursor.endEditBlock() cursor.endEditBlock()
cursor.setPosition(posE + len(before), QTextCursor.MoveAnchor) if select == _SelectAction.MOVE_AFTER:
cursor.setPosition(posS + len(before), QTextCursor.KeepAnchor) 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) self.setTextCursor(cursor)
return True return True
@@ -1908,27 +1940,38 @@ class GuiDocEditor(QPlainTextEdit):
def _autoSelect(self) -> QTextCursor: def _autoSelect(self) -> QTextCursor:
"""Return a cursor which may or may not have a selection based """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() cursor = self.textCursor()
if CONFIG.autoSelect and not cursor.hasSelection(): if CONFIG.autoSelect and not cursor.hasSelection():
cursor.select(QTextCursor.WordUnderCursor) cPos = cursor.position()
posS = cursor.selectionStart() bPos = cursor.block().position()
posE = cursor.selectionEnd() bLen = cursor.block().length()
# Underscore counts as a part of the word, so check that the # Scan backwards
# selection isn't wrapped in italics markers. sPos = cPos
reSelect = False for i in range(cPos - bPos):
if self._qDocument.characterAt(posS) == "_": sPos = cPos - i - 1
posS += 1 if not self._qDocument.characterAt(sPos).isalnum():
reSelect = True sPos += 1
if self._qDocument.characterAt(posE) == "_": break
posE -= 1
reSelect = True # Scan forwards
if reSelect: ePos = cPos
cursor.clearSelection() for i in range(bPos + bLen - cPos):
cursor.setPosition(posS, QTextCursor.MoveAnchor) ePos = cPos + i
cursor.setPosition(posE-1, QTextCursor.KeepAnchor) 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) self.setTextCursor(cursor)
+36 -20
View File
@@ -54,6 +54,7 @@ class GuiMainMenu(QMenuBar):
requestDocInsert = pyqtSignal(nwDocInsert) requestDocInsert = pyqtSignal(nwDocInsert)
requestDocInsertText = pyqtSignal(str) requestDocInsertText = pyqtSignal(str)
requestDocKeyWordInsert = pyqtSignal(str) requestDocKeyWordInsert = pyqtSignal(str)
requestFocusChange = pyqtSignal(nwWidget)
def __init__(self, mainGui: GuiMain) -> None: def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui) super().__init__(parent=mainGui)
@@ -173,7 +174,7 @@ class GuiMainMenu(QMenuBar):
# Project > Delete # Project > Delete
self.aDeleteItem = self.projMenu.addAction(self.tr("Delete Item")) 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)) self.aDeleteItem.triggered.connect(lambda: self.mainGui.projView.requestDeleteItem(None))
# Project > Empty Trash # Project > Empty Trash
@@ -246,12 +247,16 @@ class GuiMainMenu(QMenuBar):
# Edit > Undo # Edit > Undo
self.aEditUndo = self.editMenu.addAction(self.tr("Undo")) self.aEditUndo = self.editMenu.addAction(self.tr("Undo"))
self.aEditUndo.setShortcut("Ctrl+Z") 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 # Edit > Redo
self.aEditRedo = self.editMenu.addAction(self.tr("Redo")) self.aEditRedo = self.editMenu.addAction(self.tr("Redo"))
self.aEditRedo.setShortcut("Ctrl+Y") 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 # Edit > Separator
self.editMenu.addSeparator() self.editMenu.addSeparator()
@@ -259,17 +264,23 @@ class GuiMainMenu(QMenuBar):
# Edit > Cut # Edit > Cut
self.aEditCut = self.editMenu.addAction(self.tr("Cut")) self.aEditCut = self.editMenu.addAction(self.tr("Cut"))
self.aEditCut.setShortcut("Ctrl+X") 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 # Edit > Copy
self.aEditCopy = self.editMenu.addAction(self.tr("Copy")) self.aEditCopy = self.editMenu.addAction(self.tr("Copy"))
self.aEditCopy.setShortcut("Ctrl+C") 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 # Edit > Paste
self.aEditPaste = self.editMenu.addAction(self.tr("Paste")) self.aEditPaste = self.editMenu.addAction(self.tr("Paste"))
self.aEditPaste.setShortcut("Ctrl+V") 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 # Edit > Separator
self.editMenu.addSeparator() self.editMenu.addSeparator()
@@ -277,12 +288,16 @@ class GuiMainMenu(QMenuBar):
# Edit > Select All # Edit > Select All
self.aSelectAll = self.editMenu.addAction(self.tr("Select All")) self.aSelectAll = self.editMenu.addAction(self.tr("Select All"))
self.aSelectAll.setShortcut("Ctrl+A") 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 # Edit > Select Paragraph
self.aSelectPar = self.editMenu.addAction(self.tr("Select Paragraph")) self.aSelectPar = self.editMenu.addAction(self.tr("Select Paragraph"))
self.aSelectPar.setShortcut("Ctrl+Shift+A") 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 return
@@ -293,23 +308,24 @@ class GuiMainMenu(QMenuBar):
# View > TreeView # View > TreeView
self.aFocusTree = self.viewMenu.addAction(self.tr("Go to Project Tree")) 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.setShortcut("Ctrl+T")
self.aFocusTree.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.TREE)) 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 = self.viewMenu.addAction(self.tr("Go to Document Editor"))
self.aFocusEditor.setShortcut("Ctrl+Alt+2" if CONFIG.osWindows else "Alt+2") self.aFocusEditor.setShortcut("Ctrl+E")
self.aFocusEditor.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.EDITOR)) self.aFocusEditor.triggered.connect(
lambda: self.requestFocusChange.emit(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))
# View > Outline # View > Outline
self.aFocusOutline = self.viewMenu.addAction(self.tr("Go to 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.setShortcut("Ctrl+Shift+T")
self.aFocusOutline.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.OUTLINE)) self.aFocusOutline.triggered.connect(
lambda: self.requestFocusChange.emit(nwWidget.OUTLINE)
)
# View > Separator # View > Separator
self.viewMenu.addSeparator() self.viewMenu.addSeparator()
+1 -1
View File
@@ -189,7 +189,7 @@ class GuiProjectView(QWidget):
self.projTree.buildTree() self.projTree.buildTree()
return return
def setFocus(self) -> None: def setTreeFocus(self) -> None:
"""Forward the set focus call to the tree widget.""" """Forward the set focus call to the tree widget."""
self.projTree.setFocus() self.projTree.setFocus()
return return
+8 -8
View File
@@ -57,32 +57,32 @@ class GuiSideBar(QWidget):
# Buttons # Buttons
self.tbProject = QToolButton(self) 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.setIconSize(iconSize)
self.tbProject.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT)) self.tbProject.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))
self.tbNovel = QToolButton(self) 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.setIconSize(iconSize)
self.tbNovel.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL)) self.tbNovel.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))
self.tbOutline = QToolButton(self) 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.setIconSize(iconSize)
self.tbOutline.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE)) self.tbOutline.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))
self.tbBuild = QToolButton(self) 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.setIconSize(iconSize)
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog) self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
self.tbDetails = QToolButton(self) 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.setIconSize(iconSize)
self.tbDetails.clicked.connect(self.mainGui.showProjectDetailsDialog) self.tbDetails.clicked.connect(self.mainGui.showProjectDetailsDialog)
self.tbStats = QToolButton(self) 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.setIconSize(iconSize)
self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog) self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
@@ -111,7 +111,7 @@ class GuiSideBar(QWidget):
self.outerBox.addWidget(self.tbDetails) self.outerBox.addWidget(self.tbDetails)
self.outerBox.addWidget(self.tbStats) self.outerBox.addWidget(self.tbStats)
self.outerBox.addWidget(self.tbSettings) 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.outerBox.setSpacing(CONFIG.pxInt(4))
self.setLayout(self.outerBox) self.setLayout(self.outerBox)
@@ -131,7 +131,7 @@ class GuiSideBar(QWidget):
buttonStyle = ( buttonStyle = (
"QToolButton {{padding: {0}px; border: none; background: transparent;}} " "QToolButton {{padding: {0}px; border: none; background: transparent;}} "
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}" "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;}}" buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
self.tbProject.setIcon(SHARED.theme.getIcon("view_editor")) 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 datetime import datetime
from PyQt5.QtCore import Qt, QTimer, pyqtSlot 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 ( from PyQt5.QtWidgets import (
QDialog, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QShortcut, QDialog, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QShortcut,
QSplitter, QStackedWidget, QVBoxLayout, QWidget, qApp QSplitter, QStackedWidget, QVBoxLayout, QWidget, qApp
@@ -247,6 +247,7 @@ class GuiMain(QMainWindow):
self.mainMenu.requestDocInsert.connect(self._passDocumentInsert) self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert) self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert)
self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord) self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord)
self.mainMenu.requestFocusChange.connect(self.switchFocus)
self.sideBar.viewChangeRequested.connect(self._changeView) self.sideBar.viewChangeRequested.connect(self._changeView)
@@ -300,17 +301,17 @@ class GuiMain(QMainWindow):
# Shortcuts and Actions # Shortcuts and Actions
self._connectMenuActions() self._connectMenuActions()
keyReturn = QShortcut(self) self.keyReturn = QShortcut(self)
keyReturn.setKey(QKeySequence(Qt.Key_Return)) self.keyReturn.setKey(Qt.Key.Key_Return)
keyReturn.activated.connect(self._keyPressReturn) self.keyReturn.activated.connect(self._keyPressReturn)
keyEnter = QShortcut(self) self.keyEnter = QShortcut(self)
keyEnter.setKey(QKeySequence(Qt.Key_Enter)) self.keyEnter.setKey(Qt.Key.Key_Enter)
keyEnter.activated.connect(self._keyPressReturn) self.keyEnter.activated.connect(self._keyPressReturn)
keyEscape = QShortcut(self) self.keyEscape = QShortcut(self)
keyEscape.setKey(QKeySequence(Qt.Key_Escape)) self.keyEscape.setKey(Qt.Key.Key_Escape)
keyEscape.activated.connect(self._keyPressEscape) self.keyEscape.activated.connect(self._keyPressEscape)
# Check that config loaded fine # Check that config loaded fine
self.reportConfErr() self.reportConfErr()
@@ -1093,25 +1094,6 @@ class GuiMain(QMainWindow):
return True 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: def closeDocViewer(self, byUser: bool = True) -> bool:
"""Close the document view panel.""" """Close the document view panel."""
self.docViewer.clearViewer() self.docViewer.clearViewer()
@@ -1189,6 +1171,33 @@ class GuiMain(QMainWindow):
return 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 # Private Slots
## ##
@@ -1224,18 +1233,14 @@ class GuiMain(QMainWindow):
if view == nwView.EDITOR: if view == nwView.EDITOR:
# Only change the main stack, but not the project stack # Only change the main stack, but not the project stack
self.mainStack.setCurrentWidget(self.splitMain) self.mainStack.setCurrentWidget(self.splitMain)
elif view == nwView.PROJECT: elif view == nwView.PROJECT:
self.mainStack.setCurrentWidget(self.splitMain) self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.projView) self.projStack.setCurrentWidget(self.projView)
elif view == nwView.NOVEL: elif view == nwView.NOVEL:
self.mainStack.setCurrentWidget(self.splitMain) self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.novelView) self.projStack.setCurrentWidget(self.novelView)
elif view == nwView.OUTLINE: elif view == nwView.OUTLINE:
self.mainStack.setCurrentWidget(self.outlineView) self.mainStack.setCurrentWidget(self.outlineView)
return return
@pyqtSlot(nwDocAction) @pyqtSlot(nwDocAction)
@@ -1482,6 +1487,12 @@ class GuiMain(QMainWindow):
projData["numChapters"] = newProj.field("numChapters") projData["numChapters"] = newProj.field("numChapters")
projData["numScenes"] = newProj.field("numScenes") 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 return projData
def _getTagSource(self, tag: str) -> tuple[str | None, str | None]: 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.setIconSize(QSize(iPx, iPx))
self.buildList.doubleClicked.connect(self._editSelectedBuild) self.buildList.doubleClicked.connect(self._editSelectedBuild)
self.buildList.currentItemChanged.connect(self._updateBuildDetails) self.buildList.currentItemChanged.connect(self._updateBuildDetails)
self.buildList.setSelectionMode(QAbstractItemView.SingleSelection)
self.buildList.setDragDropMode(QAbstractItemView.InternalMove)
self.buildDetails = _DetailsWidget(self) self.buildDetails = _DetailsWidget(self)
self.buildDetails.setColumnWidth( self.buildDetails.setColumnWidth(
@@ -417,9 +419,15 @@ class GuiManuscript(QDialog):
"""Save the user GUI settings.""" """Save the user GUI settings."""
logger.debug("Saving GuiManuscript 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() current = self.buildList.currentItem()
if isinstance(current, QListWidgetItem): lastBuild = current.data(self.D_KEY) if isinstance(current, QListWidgetItem) else ""
self._builds.setLastBuild(current.data(self.D_KEY))
self._builds.setBuildsState(lastBuild, buildOrder)
winWidth = CONFIG.rpxInt(self.width()) winWidth = CONFIG.rpxInt(self.width())
winHeight = CONFIG.rpxInt(self.height()) winHeight = CONFIG.rpxInt(self.height())
-14
View File
@@ -974,15 +974,6 @@ class _FormatTab(QWidget):
self.formFormat = NConfigLayout() self.formFormat = NConfigLayout()
self.formFormat.addGroupLabel(self._build.getLabel("format.grpFormat")) 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 # Font Family
self.textFont = QLineEdit() self.textFont = QLineEdit()
self.textFont.setReadOnly(True) self.textFont.setReadOnly(True)
@@ -1103,10 +1094,6 @@ class _FormatTab(QWidget):
def loadContent(self) -> None: def loadContent(self) -> None:
"""Populate the widgets.""" """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") textFont = self._build.getStr("format.textFont")
if not textFont: if not textFont:
textFont = str(CONFIG.textFont) textFont = str(CONFIG.textFont)
@@ -1146,7 +1133,6 @@ class _FormatTab(QWidget):
def saveContent(self) -> None: def saveContent(self) -> None:
"""Save choices back into build object.""" """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.textFont", self.textFont.text())
self._build.setValue("format.textSize", self.textSize.value()) self._build.setValue("format.textSize", self.textSize.value())
self._build.setValue("format.lineHeight", self.lineHeight.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.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QFileDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QComboBox, QFileDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel,
QPushButton, QRadioButton, QSpinBox, QVBoxLayout, QWizard, QWizardPage QLineEdit, QPushButton, QRadioButton, QSpinBox, QVBoxLayout, QWizard,
QWizardPage
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -127,15 +128,28 @@ class ProjWizardIntroPage(QWizardPage):
self.projAuthor.setFixedWidth(xW) self.projAuthor.setFixedWidth(xW)
self.projAuthor.setPlaceholderText(self.tr("Optional")) 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 = QFormLayout()
self.mainForm.addRow(self.tr("Project Name"), self.projName) self.mainForm.addRow(self.tr("Project Name"), self.projName)
self.mainForm.addRow(self.tr("Novel Title"), self.projTitle) self.mainForm.addRow(self.tr("Novel Title"), self.projTitle)
self.mainForm.addRow(self.tr("Author(s)"), self.projAuthor) self.mainForm.addRow(self.tr("Author(s)"), self.projAuthor)
self.mainForm.addRow(self.tr("Language"), self.projLang)
self.mainForm.setVerticalSpacing(fS) self.mainForm.setVerticalSpacing(fS)
self.registerField("projName*", self.projName) self.registerField("projName*", self.projName)
self.registerField("projTitle", self.projTitle) self.registerField("projTitle", self.projTitle)
self.registerField("projAuthor", self.projAuthor) self.registerField("projAuthor", self.projAuthor)
self.registerField("projLang", self.projLang)
# Assemble # Assemble
self.outerBox = QVBoxLayout() self.outerBox = QVBoxLayout()
+5 -5
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?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"> <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="1609" autoCount="255" editTime="81165"> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1611" autoCount="255" editTime="81193">
<name>Sample Project</name> <name>Sample Project</name>
<title>Sample Project</title> <title>Sample Project</title>
<author>Jane Smith</author> <author>Jane Smith</author>
@@ -36,7 +36,7 @@
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry> <entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
</importance> </importance>
</settings> </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"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="sc24b8f" import="ia857f0">Novel</name> <name status="sc24b8f" import="ia857f0">Novel</name>
@@ -46,7 +46,7 @@
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name> <name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
</item> </item>
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sf12341" import="ia857f0" active="yes">Page</name>
</item> </item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"?> <?xml version="1.0" encoding="UTF-8"?>
<svg <!-- Created with Inkscape (http://www.inkscape.org/) -->
xmlns:dc="http://purl.org/dc/elements/1.1/" <svg width="256" height="256" version="1.1" viewBox="0 0 67.733 67.733" xmlns="http://www.w3.org/2000/svg">
xmlns:cc="http://creativecommons.org/ns#" <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"/>
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" <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"/>
xmlns:svg="http://www.w3.org/2000/svg" <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"/>
xmlns="http://www.w3.org/2000/svg" <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"/>
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>
</svg> </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 project structure is stored in a single project XML file, and other meta data is primarily saved as
JSON files. 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 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). 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"?> <?xml version="1.0" encoding="UTF-8"?>
<svg <!-- Created with Inkscape (http://www.inkscape.org/) -->
xmlns:dc="http://purl.org/dc/elements/1.1/" <svg width="256" height="256" version="1.1" viewBox="0 0 67.733 67.733" xmlns="http://www.w3.org/2000/svg">
xmlns:cc="http://creativecommons.org/ns#" <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"/>
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" <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"/>
xmlns:svg="http://www.w3.org/2000/svg" <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"/>
xmlns="http://www.w3.org/2000/svg" <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"/>
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>
</svg> </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:" echo "Content of current dir:"
ls -lah . 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 popd || exit 1
pushd "$BUILD_DIR"/ || 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 "================================================================================"
echo "" echo ""
python3 pkgutils.py minimal-zip --target-win python3 pkgutils.py minimal-zip --target-win
python3 pkgutils.py minimal-zip --target-linux # python3 pkgutils.py minimal-zip --target-linux
python3 pkgutils.py minimal-zip --target-darwin # python3 pkgutils.py minimal-zip --target-darwin
echo "" echo ""
echo " Building Linux Packages" 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 repdots = True
autoscroll = False autoscroll = False
autoscrollpos = 30 autoscrollpos = 30
scrollpastend = True
fmtsquoteopen = fmtsquoteopen =
fmtsquoteclose = fmtsquoteclose =
fmtdquoteopen = “ fmtdquoteopen = “
@@ -7,7 +7,7 @@
</project> </project>
<settings> <settings>
<doBackup>yes</doBackup> <doBackup>yes</doBackup>
<language>None</language> <language>en_GB</language>
<spellChecking auto="no">None</spellChecking> <spellChecking auto="no">None</spellChecking>
<lastHandle> <lastHandle>
<entry key="editor">None</entry> <entry key="editor">None</entry>
@@ -45,6 +45,7 @@ repdash = True
repdots = True repdots = True
autoscroll = True autoscroll = True
autoscrollpos = 30 autoscrollpos = 30
scrollpastend = True
fmtsquoteopen = fmtsquoteopen =
fmtsquoteclose = fmtsquoteclose =
fmtdquoteopen = “ fmtdquoteopen = “
+1 -1
View File
@@ -435,7 +435,7 @@ def testCoreBuildSettings_Collection(monkeypatch, mockGUI, fncPath: Path, mockRn
(buildIDTwo, "Build Two"), (buildIDTwo, "Build Two"),
(buildIDOne, "Build One"), (buildIDOne, "Build One"),
] ]
builds.setLastBuild(buildIDOne) builds.setBuildsState(buildIDOne, [buildIDTwo, buildIDOne])
builds.setDefaultBuild(buildIDTwo) builds.setDefaultBuild(buildIDTwo)
# Check errors: No valid path # Check errors: No valid path
+2
View File
@@ -446,6 +446,7 @@ def testCoreTools_NewCustomA(monkeypatch, fncPath, tstPaths, mockRnd):
"projName": "Test Custom", "projName": "Test Custom",
"projTitle": "Test Novel", "projTitle": "Test Novel",
"projAuthor": "Jane Doe", "projAuthor": "Jane Doe",
"projLang": -1,
"projPath": fncPath, "projPath": fncPath,
"popSample": False, "popSample": False,
"popMinimal": False, "popMinimal": False,
@@ -484,6 +485,7 @@ def testCoreTools_NewCustomB(monkeypatch, fncPath, tstPaths, mockRnd):
"projName": "Test Custom", "projName": "Test Custom",
"projTitle": "Test Novel", "projTitle": "Test Novel",
"projAuthor": "Jane Doe", "projAuthor": "Jane Doe",
"projLang": -1,
"projPath": fncPath, "projPath": fncPath,
"popSample": False, "popSample": False,
"popMinimal": False, "popMinimal": False,
+17 -17
View File
@@ -155,9 +155,9 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
# Set some values # Set some values
theProject = SHARED.project theProject = SHARED.project
theProject.tree[C.hTitlePage].setStatus(C.sFinished) theProject.tree[C.hTitlePage].setStatus(C.sFinished) # type: ignore
theProject.tree[C.hChapterDoc].setStatus(C.sDraft) theProject.tree[C.hChapterDoc].setStatus(C.sDraft) # type: ignore
theProject.tree[C.hSceneDoc].setStatus(C.sDraft) theProject.tree[C.hSceneDoc].setStatus(C.sDraft) # type: ignore
nwGUI.projView.projTree.setSelectedHandle(C.hPlotRoot) nwGUI.projView.projTree.setSelectedHandle(C.hPlotRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
@@ -170,9 +170,9 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPat
hCharNote = "0000000000011" hCharNote = "0000000000011"
hWorldNote = "0000000000012" hWorldNote = "0000000000012"
theProject.tree[hPlotNote].setImport(C.iMajor) theProject.tree[hPlotNote].setImport(C.iMajor) # type: ignore
theProject.tree[hCharNote].setImport(C.iMajor) theProject.tree[hCharNote].setImport(C.iMajor) # type: ignore
theProject.tree[hWorldNote].setImport(C.iMain) theProject.tree[hWorldNote].setImport(C.iMain) # type: ignore
# Create Dialog # Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_STATUS) projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_STATUS)
@@ -367,23 +367,23 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncPath, projPath, mo
tabReplace = projSettings.tabReplace tabReplace = projSettings.tabReplace
assert tabReplace.listBox.topLevelItem(0).text(0) == "<A>" assert tabReplace.listBox.topLevelItem(0).text(0) == "<A>" # type: ignore
assert tabReplace.listBox.topLevelItem(0).text(1) == "B" assert tabReplace.listBox.topLevelItem(0).text(1) == "B" # type: ignore
assert tabReplace.listBox.topLevelItem(1).text(0) == "<C>" assert tabReplace.listBox.topLevelItem(1).text(0) == "<C>" # type: ignore
assert tabReplace.listBox.topLevelItem(1).text(1) == "D" assert tabReplace.listBox.topLevelItem(1).text(1) == "D" # type: ignore
assert tabReplace.listBox.topLevelItemCount() == 2 assert tabReplace.listBox.topLevelItemCount() == 2
# Nothing to save or delete # Nothing to save or delete
tabReplace.listBox.clearSelection() tabReplace.listBox.clearSelection()
assert tabReplace._saveEntry() is False tabReplace._saveEntry()
assert tabReplace._delEntry() is False tabReplace._delEntry()
assert tabReplace.listBox.topLevelItemCount() == 2 assert tabReplace.listBox.topLevelItemCount() == 2
# Create a new entry # Create a new entry
qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton) qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 3 assert tabReplace.listBox.topLevelItemCount() == 3
assert tabReplace.listBox.topLevelItem(2).text(0) == "<keyword3>" assert tabReplace.listBox.topLevelItem(2).text(0) == "<keyword3>" # type: ignore
assert tabReplace.listBox.topLevelItem(2).text(1) == "" assert tabReplace.listBox.topLevelItem(2).text(1) == "" # type: ignore
# Edit the entry # Edit the entry
tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(2)) 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 ": for c in "With This Stuff ":
qtbot.keyClick(tabReplace.editValue, c, delay=KEY_DELAY) qtbot.keyClick(tabReplace.editValue, c, delay=KEY_DELAY)
qtbot.mouseClick(tabReplace.saveButton, Qt.LeftButton) qtbot.mouseClick(tabReplace.saveButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItem(2).text(0) == "<This>" assert tabReplace.listBox.topLevelItem(2).text(0) == "<This>" # type: ignore
assert tabReplace.listBox.topLevelItem(2).text(1) == "With This Stuff " assert tabReplace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore
# Create a new entry again # Create a new entry again
tabReplace.listBox.clearSelection() 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 # The list is sorted, so we must find it
newIdx = -1 newIdx = -1
for i in range(tabReplace.listBox.topLevelItemCount()): 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 newIdx = i
break break
assert newIdx >= 0 assert newIdx >= 0
+7 -43
View File
@@ -672,7 +672,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd
@pytest.mark.gui @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.""" """Test the text manipulation functions."""
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True 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) text = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
nwGUI.docEditor.replaceText(text) 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 # Wrap Selection
# ============== # ==============
@@ -718,11 +687,6 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumTex
nwGUI.docEditor.replaceText(text) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) 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 # Wrap Equal
nwGUI.docEditor.replaceText(text) nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45) 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]) text = "### A Scene\n\n%s" % "\n\n".join(ipsumText[0:2])
nwGUI.docEditor.replaceText(text)
nwGUI.docEditor.setCursorPosition(45)
# No Selection # Block format repetition
with monkeypatch.context() as mp: nwGUI.docEditor.replaceText(text)
mp.setattr(nwGUI.docEditor, "_autoSelect", lambda: QTextCursor()) nwGUI.docEditor.setCursorPosition(39)
assert nwGUI.docEditor._toggleFormat(2, "=") is False 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 # Wrap Single Equal
nwGUI.docEditor.replaceText(text) nwGUI.docEditor.replaceText(text)
@@ -562,7 +562,6 @@ def testBuildSettings_Format(monkeypatch, qtbot: QtBot, nwGUI: GuiMain):
assert bSettings.toolStack.currentWidget() is fmtTab assert bSettings.toolStack.currentWidget() is fmtTab
# Check initial values # Check initial values
assert fmtTab.buildLang.currentData() == "en_US"
assert fmtTab.textFont.text() == textFont assert fmtTab.textFont.text() == textFont
assert fmtTab.textSize.value() == 12 assert fmtTab.textSize.value() == 12
assert fmtTab.lineHeight.value() == 1.2 assert fmtTab.lineHeight.value() == 1.2
@@ -581,7 +580,6 @@ def testBuildSettings_Format(monkeypatch, qtbot: QtBot, nwGUI: GuiMain):
assert fmtTab.rightMargin.value() == 15.0 assert fmtTab.rightMargin.value() == 15.0
# Change values # Change values
fmtTab.buildLang.setCurrentIndex(fmtTab.buildLang.findData("en_GB"))
fmtTab.textFont.setText("Arial") fmtTab.textFont.setText("Arial")
fmtTab.textSize.setValue(11) fmtTab.textSize.setValue(11)
fmtTab.lineHeight.setValue(1.15) fmtTab.lineHeight.setValue(1.15)
@@ -596,7 +594,6 @@ def testBuildSettings_Format(monkeypatch, qtbot: QtBot, nwGUI: GuiMain):
# Save values # Save values
fmtTab.saveContent() fmtTab.saveContent()
assert build.getStr("format.buildLang") == "en_GB"
assert build.getStr("format.textFont") == "Arial" assert build.getStr("format.textFont") == "Arial"
assert build.getInt("format.textSize") == 11 assert build.getInt("format.textSize") == 11
assert build.getFloat("format.lineHeight") == 1.15 assert build.getFloat("format.lineHeight") == 1.15