Compare commits

...

250 Commits
v2.4 ... v2.5b1

Author SHA1 Message Date
Veronica Berglyd Olsen
55714bbcad Release 2.5 Beta 1 (#1895) 2024-05-26 16:49:07 +02:00
Veronica Berglyd Olsen
6f144c20cd Bump to 2.5 beta 1 and update changelog 2024-05-26 16:42:49 +02:00
Veronica Berglyd Olsen
f23bd3818f Add a QTextDocument format class for previews (#1892) 2024-05-26 00:57:15 +02:00
Veronica Berglyd Olsen
d3ea763d6b Add test coverage of new ToQTextDocument class 2024-05-26 00:51:27 +02:00
Veronica Berglyd Olsen
91222f631f Update manuscript tests 2024-05-25 23:02:34 +02:00
Veronica Berglyd Olsen
aceb344856 Fix a potential segfault issue in main gui and add auto-build on open manuscript tool 2024-05-25 22:43:09 +02:00
Veronica Berglyd Olsen
f11ea8d07e Make various fixes and tweaks, and update tests 2024-05-24 21:39:16 +02:00
Veronica Berglyd Olsen
a4e3bf848b Clean up no longer needed code, and fix text justify setting 2024-05-24 20:38:34 +02:00
Veronica Berglyd Olsen
e74c1057a4 Remove HTML preview mode and manuscript cache 2024-05-24 20:12:13 +02:00
Veronica Berglyd Olsen
6cc046c65c Use preview document class for manuscript preview 2024-05-24 18:52:18 +02:00
Veronica Berglyd Olsen
c97a070e98 Fix module header info 2024-05-23 22:04:19 +02:00
Veronica Berglyd Olsen
b4c0bf3322 Complete all current document viewer features in the qdoc class 2024-05-23 21:29:47 +02:00
Veronica Berglyd Olsen
e998cb634e Add working meta data blocks 2024-05-23 20:48:42 +02:00
Veronica Berglyd Olsen
c8017a1f42 Add text paragraph formatting 2024-05-23 19:43:32 +02:00
Veronica Berglyd Olsen
6d84942746 Update qdocument class to work with merged code from main 2024-05-23 19:02:27 +02:00
Veronica Berglyd Olsen
818f145b62 Merge branch 'main' into feature/preview_generator 2024-05-23 18:50:38 +02:00
Veronica Berglyd Olsen
f92c22ce7e Fix build formatting and font handling (#1891) 2024-05-23 18:49:32 +02:00
Veronica Berglyd Olsen
69b362b9d2 Add native font dialog option and fix tests 2024-05-23 18:44:45 +02:00
Veronica Berglyd Olsen
1fe7ca3db8 Update font description and add native font dialog setting to config 2024-05-22 23:31:28 +02:00
Veronica Berglyd Olsen
cc32674b0f Update tests 2024-05-22 23:13:50 +02:00
Veronica Berglyd Olsen
cf16b57f66 Clean up font handling in HTML and ODT 2024-05-22 23:13:16 +02:00
Veronica Berglyd Olsen
411163f750 Update tests 2024-05-22 17:28:57 +02:00
Veronica Berglyd Olsen
7f3da655c0 Update ODT handling of font 2024-05-22 17:28:29 +02:00
Veronica Berglyd Olsen
76e786d961 Update HTML handling of font 2024-05-22 16:59:44 +02:00
Veronica Berglyd Olsen
7e1f1a725c Switch Tokenizer to use QFont instead of font family and size 2024-05-22 16:57:50 +02:00
Veronica Berglyd Olsen
c63a0c2f58 Add new font and quote button icons 2024-05-22 16:55:36 +02:00
Veronica Berglyd Olsen
04a7e092a9 Clean up Markdown build code 2024-05-22 16:55:05 +02:00
Veronica Berglyd Olsen
309065bcbf Add general keep line breaks setting to build 2024-05-22 09:14:34 +02:00
Veronica Berglyd Olsen
20ae3febef Merge branch 'main' into feature/preview_generator 2024-05-21 23:16:27 +02:00
Veronica Berglyd Olsen
e95d33031c Improve the text tokenizer (#1885) 2024-05-21 23:11:44 +02:00
Veronica Berglyd Olsen
3fd62d0b02 Remove context menu test from main menu tests 2024-05-21 23:06:25 +02:00
Veronica Berglyd Olsen
dc0ddcfa9f Move text blocks first also in stats counter as an optimisation 2024-05-21 22:59:02 +02:00
Veronica Berglyd Olsen
7a13b14c15 Drop empty tokens from the tokenizer data 2024-05-21 22:45:01 +02:00
Veronica Berglyd Olsen
adf0550571 Update tests 2024-05-21 22:32:20 +02:00
Veronica Berglyd Olsen
ba6259f08a Update converter classes to handle only single paragraphs 2024-05-21 22:32:07 +02:00
Veronica Berglyd Olsen
d8bb2ad3ae Merge lines of the same paragraph already in the tokenizer 2024-05-21 21:58:34 +02:00
Veronica Berglyd Olsen
afe4a29fd1 Strip consecutive empty paragraphs when tokenizing 2024-05-21 20:53:45 +02:00
Veronica Berglyd Olsen
1203dd639b Add a QTextDocument generator class 2024-05-21 18:12:08 +02:00
Veronica Berglyd Olsen
aadad6aa8c Define header sizes as constants 2024-05-21 18:02:01 +02:00
Veronica Berglyd Olsen
8ac0440bb3 Rename a few files 2024-05-21 16:33:53 +02:00
Veronica Berglyd Olsen
d2fca3b980 Allow full font style settings for GUI and editor (#1881) 2024-05-20 18:37:42 +02:00
Veronica Berglyd Olsen
d6b4019614 Clean up app construction in main entry point 2024-05-20 18:33:00 +02:00
Veronica Berglyd Olsen
a6eb951361 Add font initialisation, and update tests 2024-05-20 17:36:40 +02:00
Veronica Berglyd Olsen
dce5bdd31d Update tests 2024-05-20 17:14:03 +02:00
Veronica Berglyd Olsen
0b999bd726 Fix font handling in manuscript tool 2024-05-20 17:13:10 +02:00
Veronica Berglyd Olsen
774437f7bb Save all font info for text font 2024-05-20 16:32:10 +02:00
Veronica Berglyd Olsen
c0673b8128 Save all font info for GUI font 2024-05-20 16:12:27 +02:00
Veronica Berglyd Olsen
0bc8a63ccf Add error text colour setting to theme (#1880) 2024-05-20 13:36:37 +02:00
Veronica Berglyd Olsen
192f4507e3 Update documentation 2024-05-20 13:32:13 +02:00
Veronica Berglyd Olsen
2d22adcd2a Update the dictionaries tool to use the new error colour 2024-05-20 13:32:06 +02:00
Veronica Berglyd Olsen
4c4b92fdd4 Add error text colour to theme definition 2024-05-20 13:31:38 +02:00
Veronica Berglyd Olsen
2a727d1c39 Merge branch 'main' into fix/dict_tool_error_colour 2024-05-20 13:09:44 +02:00
Veronica Berglyd Olsen
35693f5433 Fix remaining merge conflict in sample project 2024-05-20 13:09:26 +02:00
Veronica Berglyd Olsen
bf459b4090 Add error text value in themes 2024-05-20 13:08:59 +02:00
Veronica Berglyd Olsen
728f1ce1e3 Merge release 2.4.3 into main 2024-05-20 13:07:14 +02:00
Veronica Berglyd Olsen
7b8021de02 Release 2.4.3 (#1878) 2024-05-20 12:32:32 +02:00
Veronica Berglyd Olsen
60593cde21 Bump version to 2.4.3 and update changelog 2024-05-20 12:29:03 +02:00
Veronica Berglyd Olsen
f9292d61da Fix how document font is set (#1877) 2024-05-20 12:16:41 +02:00
Veronica Berglyd Olsen
9388ce6a46 Fix support for 90% font 2024-05-20 12:00:19 +02:00
Veronica Berglyd Olsen
cb19475409 Add font init to viewer, and update manuscript tool preview 2024-05-20 11:36:30 +02:00
Veronica Berglyd Olsen
09b51badd4 Add an explicit font setting function for text editor that covers all font settings 2024-05-20 11:17:23 +02:00
Veronica Berglyd Olsen
fddd5fda47 Fix crash in dictionaries install tool (#1876) 2024-05-19 21:52:54 +02:00
Veronica Berglyd Olsen
5b0a6f05ed Fix enchant folder missing bug for dictionaries tool (#1874) 2024-05-19 21:47:53 +02:00
Veronica Berglyd Olsen
51f91a27e2 Merge release 2.4.2 into main 2024-05-18 15:57:49 +02:00
Veronica Berglyd Olsen
09018d1214 Release 2.4.2 (#1872) 2024-05-18 15:22:19 +02:00
Veronica Berglyd Olsen
de12b22c97 Bump version and update changelog 2024-05-18 15:19:30 +02:00
Veronica Berglyd Olsen
d1ae7f5fe2 Update Dutch and Chinese translations 2024-05-18 15:08:39 +02:00
Veronica Berglyd Olsen
201d32f6ae Merge branch 'release' 2024-05-14 09:37:59 +02:00
Veronica Berglyd Olsen
6a441b1c6b Fix syntax highlighter init (#1866) 2024-05-14 09:37:00 +02:00
Veronica Berglyd Olsen
0afa15f2db Call highlighter init on editor init (#1865) 2024-05-14 09:29:21 +02:00
Veronica Berglyd Olsen
85eb44da02 Bump main branch to 2.5 alpha 4 2024-05-13 18:48:38 +02:00
Veronica Berglyd Olsen
4856c7a61b Improve dialogue highlighting (#1864) 2024-05-13 18:41:13 +02:00
Veronica Berglyd Olsen
1b4cc933e7 Add test coverage 2024-05-13 18:31:31 +02:00
Veronica Berglyd Olsen
d64acb038b Allow multiple widgets in config form 2024-05-13 18:31:14 +02:00
Veronica Berglyd Olsen
b5def640ed Add alternative dialog style 2024-05-13 18:11:44 +02:00
Veronica Berglyd Olsen
7e35b4c675 Update syntax themes and add alternative dialog colour 2024-05-13 17:57:19 +02:00
Veronica Berglyd Olsen
e5e3066b3d Update preferences 2024-05-13 17:51:16 +02:00
Veronica Berglyd Olsen
e1af21bf88 Add new dialogue highlighting settings 2024-05-13 17:50:18 +02:00
Veronica Berglyd Olsen
8c9d08e46b Merge branch 'release' 2024-05-13 12:33:53 +02:00
Veronica Berglyd Olsen
e4acfb4831 Fix manuscript tool font issue (#1863) 2024-05-13 12:33:22 +02:00
Veronica Berglyd Olsen
a8101c2231 Fix text font for header on build tool 2024-05-13 12:29:18 +02:00
Veronica Berglyd Olsen
a3ce426613 Clean up code style (#1859) 2024-05-10 16:39:26 +02:00
Veronica Berglyd Olsen
161d02cd98 Use python -m to run pytest 2024-05-10 16:36:14 +02:00
Veronica Berglyd Olsen
101400bea3 Clean up some linting errors 2024-05-10 16:22:55 +02:00
Veronica Berglyd Olsen
1ff9fea378 Remove banner comments 2024-05-10 16:14:47 +02:00
Veronica Berglyd Olsen
3f4538a60e Remove all END Test comments 2024-05-10 16:06:23 +02:00
Veronica Berglyd Olsen
142d7c4f3a Remove all END Enum comments 2024-05-10 16:01:44 +02:00
Veronica Berglyd Olsen
2afe0ebbe9 Remove all END Class comments 2024-05-10 16:00:56 +02:00
Veronica Berglyd Olsen
bc71a62199 Improve first line indent (#1857) 2024-05-10 15:49:59 +02:00
Veronica Berglyd Olsen
1e85ce4a84 Update tests 2024-05-10 15:26:46 +02:00
Veronica Berglyd Olsen
39951a5099 Add more settings to first line indent and move to Format section 2024-05-10 15:14:09 +02:00
Veronica Berglyd Olsen
9cbb6f3125 Merge release 2.4.1 into main 2024-05-06 22:27:29 +02:00
Veronica Berglyd Olsen
2dbd3e2534 Release 2.4.1 (#1853) 2024-05-06 21:44:15 +02:00
Veronica Berglyd Olsen
f871a51e5b Bump version number to 2.4.1 and update changelog 2024-05-06 21:40:50 +02:00
Veronica Berglyd Olsen
6ab3c68ebb Update translation files for 2.4.1 (#1850) 2024-05-06 21:29:35 +02:00
Veronica Berglyd Olsen
7243ccd44c Update translation files 2024-05-04 21:00:51 +02:00
Veronica Berglyd Olsen
2a96fa1a2d Update the docs for 2.4.1 2024-05-04 20:54:56 +02:00
Veronica Berglyd Olsen
c05620dddb Refactor Main GUI inheritance (#1849) 2024-05-02 22:36:44 +02:00
Veronica Berglyd Olsen
917977ae13 Force document save when manually calling save document 2024-05-02 22:30:58 +02:00
Veronica Berglyd Olsen
2b8338f537 Clean up Main GUI inheritance 2024-05-02 22:13:35 +02:00
Veronica Berglyd Olsen
e75b5e012c Refactor how editor document is saved, and always save cursor position 2024-05-02 21:45:50 +02:00
Veronica Berglyd Olsen
78e194d8c1 Improve handling of tool dialogs on project close 2024-05-02 21:29:07 +02:00
Veronica Berglyd Olsen
c594e07c8c Merge branch 'release' 2024-05-02 21:16:42 +02:00
Veronica Berglyd Olsen
a3ed079828 Make sure dialogs close when project closes (#1848) 2024-05-02 21:08:46 +02:00
Veronica Berglyd Olsen
976ce16ac2 Update imports 2024-05-02 21:04:39 +02:00
Veronica Berglyd Olsen
5faeb12a74 Close non-modal dialogs when project closes 2024-05-02 21:02:23 +02:00
Veronica Berglyd Olsen
acc463eb76 Prune some unneeded translation strings 2024-05-02 19:38:13 +02:00
Veronica Berglyd Olsen
3b7459ffbc Add usability updates to the build tool (#1846) 2024-05-01 22:18:15 +02:00
Veronica Berglyd Olsen
cfb19a697a Restore build preview scroll bar after update (#1837) 2024-05-01 22:10:54 +02:00
Veronica Berglyd Olsen
ceec43db67 Make sure open document is saved before we update preview or build manuscript (#1835) 2024-05-01 22:09:43 +02:00
Veronica Berglyd Olsen
229fa39436 Fix build settings bug (#1845) 2024-05-01 20:33:22 +02:00
Veronica Berglyd Olsen
2496eb22cb Make sure build map is cleared when list is updated 2024-05-01 20:32:50 +02:00
Veronica Berglyd Olsen
d0a45bceca Make sure build map is cleared when list is updated 2024-05-01 20:30:32 +02:00
Veronica Berglyd Olsen
7180fae453 Improve dialog handling (#1844) 2024-05-01 20:24:31 +02:00
Veronica Berglyd Olsen
e1a1b5980e Make sure build settings are closed before deletion 2024-05-01 20:23:59 +02:00
Veronica Berglyd Olsen
53d410a6ee Make sure build settings are closed before deletion 2024-05-01 20:18:08 +02:00
Veronica Berglyd Olsen
a56b38694c Create subclasses for non-blocking dialogs 2024-05-01 20:08:35 +02:00
Veronica Berglyd Olsen
600b60b441 Remove main gui inheritance in manuscript tools 2024-05-01 19:43:51 +02:00
Veronica Berglyd Olsen
aa7208b602 Merge branch 'release' 2024-05-01 19:38:38 +02:00
Veronica Berglyd Olsen
0092b3ece6 Fix two minor issues (#1843) 2024-05-01 19:30:13 +02:00
Veronica Berglyd Olsen
d01c1e867d Pin MacOS tests to macos-13 image 2024-05-01 19:26:59 +02:00
Veronica Berglyd Olsen
dd3c8dfac8 Use GUI font for the header and footer in the editor and viewer (#1842) 2024-05-01 19:22:22 +02:00
Veronica Berglyd Olsen
7c9c5a9a76 Fix garbage collection blocker in build settings 2024-05-01 19:12:45 +02:00
Veronica Berglyd Olsen
8f9a55c5ca Add tools settings to pyproject 2024-05-01 19:12:27 +02:00
Veronica Berglyd Olsen
ea0b61aa6d Update assets credits 2024-04-29 22:48:25 +02:00
Veronica Berglyd Olsen
b94e7159b8 Add more themes (#1840) 2024-04-29 21:37:35 +02:00
Veronica Berglyd Olsen
2eabca8267 Fix theme test 2024-04-29 21:34:28 +02:00
Veronica Berglyd Olsen
afbe434b59 Add Snazzy Light syntax theme 2024-04-29 21:26:37 +02:00
Veronica Berglyd Olsen
2a50799873 Add dracula syntax and GUI theme 2024-04-29 21:08:25 +02:00
Veronica Berglyd Olsen
77727ad500 Update tango theme 2024-04-29 21:08:11 +02:00
Veronica Berglyd Olsen
e89f1fe5a8 Fix some minor issues on other themes 2024-04-29 21:07:58 +02:00
Veronica Berglyd Olsen
503e7eceb5 Make default syntax themes consistent 2024-04-29 21:07:10 +02:00
Veronica Berglyd Olsen
c15eba8f62 Bump main branch to 2.5 Alpha 3 2024-04-28 23:19:36 +02:00
Veronica Berglyd Olsen
e2f83d5106 Update syntax themes (#1836) 2024-04-28 23:10:32 +02:00
Veronica Berglyd Olsen
937b5cca12 Rename old value colour to tag and add new value colour to all syntax themes 2024-04-28 23:07:20 +02:00
Veronica Berglyd Olsen
1d438d5d76 Separate tags and value colours 2024-04-28 22:43:46 +02:00
Veronica Berglyd Olsen
22a34be21a Fix note colour in viewer and fix tests 2024-04-28 20:08:35 +02:00
Veronica Berglyd Olsen
17a492444e Redesign the default dark colour theme 2024-04-28 19:54:08 +02:00
Veronica Berglyd Olsen
9a714a903d Add a note colour to syntax themes 2024-04-28 19:53:58 +02:00
Veronica Berglyd Olsen
69a99fc97f Add a syntax colour for special notes 2024-04-28 19:53:09 +02:00
Veronica Berglyd Olsen
8beb74689c Add footnotes (#1832) 2024-04-28 19:40:38 +02:00
Veronica Berglyd Olsen
6be7da0305 Clean up a few minor bits in the index tests 2024-04-28 18:43:22 +02:00
Veronica Berglyd Olsen
8a08fe213c Fix more deprecation warnings in xml module 2024-04-28 18:29:15 +02:00
Veronica Berglyd Olsen
205b17aea0 Fix deprecation warning in xml module 2024-04-28 18:26:38 +02:00
Veronica Berglyd Olsen
864172ea50 Add an elide function for shortening text 2024-04-28 18:22:31 +02:00
Veronica Berglyd Olsen
e01e14075c Update editor and highlight, and update tests 2024-04-28 18:05:53 +02:00
Veronica Berglyd Olsen
e9596e1bbc Fix footnote formatting for ODT and update document builder tests 2024-04-27 19:05:13 +02:00
Veronica Berglyd Olsen
db9305c230 Add a footnote to the Lorem Ipsum test project 2024-04-27 16:50:37 +02:00
Veronica Berglyd Olsen
4572d101cf Change to run MacOS test on 13 2024-04-26 22:29:26 +02:00
Veronica Berglyd Olsen
41506911c1 Fix regex flag in highlighter 2024-04-26 22:25:58 +02:00
Veronica Berglyd Olsen
b722224863 Improve comment processing 2024-04-26 22:25:08 +02:00
Veronica Berglyd Olsen
b585b8ecd1 Make the footnote registry per-file 2024-04-26 20:12:57 +02:00
Veronica Berglyd Olsen
52e8ae9cb6 Merge branch 'main' into features/footnotes 2024-04-25 23:35:40 +02:00
Veronica Berglyd Olsen
6d6d0c2d12 Require minimum Python 3.9 (#1833) 2024-04-25 23:35:26 +02:00
Veronica Berglyd Olsen
383640cb7e Drop Python 3.8 support 2024-04-25 23:30:26 +02:00
Veronica Berglyd Olsen
e5ef27dfbf Revert change in test 2024-04-25 23:25:23 +02:00
Veronica Berglyd Olsen
96741fc08b Fix remaining broken tests 2024-04-25 22:58:00 +02:00
Veronica Berglyd Olsen
dc41f495be Fix ODT tests 2024-04-24 18:39:06 +02:00
Veronica Berglyd Olsen
b278d64d0c Fix uninitialised shared class in tests 2024-04-24 18:38:50 +02:00
Veronica Berglyd Olsen
b5e32844f5 Merge branch 'main' into features/footnotes 2024-04-24 18:09:26 +02:00
Veronica Berglyd Olsen
4179d2e515 Add isort and clean up flake8 (#1831) 2024-04-24 18:08:42 +02:00
Veronica Berglyd Olsen
815ba9d553 Update linting and formatting tools 2024-04-24 17:46:00 +02:00
Veronica Berglyd Olsen
0d294f49f9 Merge branch 'main' into features/footnotes 2024-04-21 23:34:56 +02:00
Veronica Berglyd Olsen
a195cfc792 Merge branch 'release' 2024-04-21 13:56:08 +02:00
Veronica Berglyd Olsen
d3c9168ebf Make MacOS build script executable 2024-04-21 13:17:55 +02:00
Veronica Berglyd Olsen
4d153b8d99 Backport ARM build for MacOS from 2.5 branch 2024-04-21 13:14:38 +02:00
Veronica Berglyd Olsen
bf8d224064 Update build scripts (#1829) 2024-04-21 12:59:42 +02:00
Veronica Berglyd Olsen
b7ad2c969a Fix test 2024-04-20 21:49:22 +02:00
Veronica Berglyd Olsen
d78643160c Update MacOS package names and fic another launch script bug 2024-04-20 21:43:31 +02:00
Veronica Berglyd Olsen
221b38c766 Fix escapes in MacOS launch script 2024-04-20 21:36:09 +02:00
Veronica Berglyd Olsen
e8bd9b21b5 Explicitly set python exec and ignore errors from run from source blocks in main 2024-04-20 21:31:04 +02:00
Veronica Berglyd Olsen
c2f0d04a35 Merge branch 'main' into builds/macos_m1 2024-04-20 21:04:09 +02:00
Veronica Berglyd Olsen
54ebcd91cb Fix wrong hex version 2024-04-20 20:13:06 +02:00
Veronica Berglyd Olsen
07411327b6 Add some debug output 2024-04-20 19:50:45 +02:00
Veronica Berglyd Olsen
094ad1a17b Require input arguments 2024-04-20 19:44:50 +02:00
Veronica Berglyd Olsen
c362848b86 Fix bash script bug 2024-04-20 19:41:33 +02:00
Veronica Berglyd Olsen
47a8dfbd93 Go back to macos-12 runner 2024-04-20 19:35:39 +02:00
Veronica Berglyd Olsen
73b8443d7b Let's try this one more time ... 2024-04-20 18:35:31 +02:00
Veronica Berglyd Olsen
48a7929254 Escape forwarding of arguments in MacoS build script 2024-04-20 18:28:57 +02:00
Veronica Berglyd Olsen
2c1abf1519 Change package name 2024-04-20 18:18:11 +02:00
Veronica Berglyd Olsen
e27e066ccc Drop support for deprecated Linux AppImage 2024-04-20 18:12:51 +02:00
Veronica Berglyd Olsen
621e933f63 Make some tweaks to build scripts 2024-04-20 18:02:10 +02:00
Veronica Berglyd Olsen
885974a84e Keep artifacts for 14 days 2024-04-20 17:41:36 +02:00
Veronica Berglyd Olsen
b1d1af2153 Add M1 build for MacOS 2024-04-20 17:41:10 +02:00
Veronica Berglyd Olsen
cb94be5b3c Post-release updates 2024-04-20 17:30:35 +02:00
Veronica Berglyd Olsen
5a202e96fd Post-release updates 2024-04-20 17:27:12 +02:00
Veronica Berglyd Olsen
b80edee225 Merge Release 2.4 into Main 2024-04-20 17:26:12 +02:00
Veronica Berglyd Olsen
48e653a546 Clean up footnote key generation 2024-04-18 18:23:25 +02:00
Veronica Berglyd Olsen
a2d8aefb21 Reduce footnote index storage to keys only 2024-04-18 18:07:17 +02:00
Veronica Berglyd Olsen
ae893be79b Merge branch 'main' into features/footnotes 2024-04-18 00:01:48 +02:00
Veronica Berglyd Olsen
791080107f Update readme 2024-04-17 00:23:41 +02:00
Veronica Berglyd Olsen
7d22ed1249 Update packaging (#1826) 2024-04-17 00:21:42 +02:00
Veronica Berglyd Olsen
8150edbc43 Clean up and refactor package script 2024-04-17 00:12:06 +02:00
Veronica Berglyd Olsen
5acbfeffa5 Reduce zip build job to Windows only 2024-04-17 00:00:28 +02:00
Veronica Berglyd Olsen
3b763e3460 Remove windows install scripts 2024-04-16 23:54:54 +02:00
Veronica Berglyd Olsen
6dd5f4295d Drop build support for Ubuntu 20.04 2024-04-16 23:46:10 +02:00
Veronica Berglyd Olsen
ac7b0bc9a2 Drop code for pre Qt 5.15 versions 2024-04-16 23:45:59 +02:00
Veronica Berglyd Olsen
1733938eb4 Bump Qt version requirement to 5.15 2024-04-16 23:40:48 +02:00
Veronica Berglyd Olsen
e5c9ed4b80 Fix build errors for old Ubuntu LTSes 2024-04-16 23:34:25 +02:00
Veronica Berglyd Olsen
f45170850c Fix Ubuntu builds for 20.04 and 22.04, and bump to 2.5 alpha 2 2024-04-16 23:25:30 +02:00
Veronica Berglyd Olsen
bdce0e12c7 Simplify footnote handling in writer classes 2024-04-16 21:54:31 +02:00
Veronica Berglyd Olsen
57f9a0a844 Refactor and improve highlighter class 2024-04-16 21:52:37 +02:00
Veronica Berglyd Olsen
988d969de7 Drop the non-compliant print-orientation attribute from the ODT class 2024-04-16 21:51:54 +02:00
Veronica Berglyd Olsen
17960d286f Add a working ODT footnotes implementation 2024-04-16 00:39:48 +02:00
Veronica Berglyd Olsen
26ac68e98e Block footnotes in footnotes, and only output referenced footnotes 2024-04-16 00:37:26 +02:00
Veronica Berglyd Olsen
0729dd798b Add formatted comments for ODT 2024-04-15 18:49:49 +02:00
Veronica Berglyd Olsen
c65dee1df9 Skip footnote section if there are none 2024-04-15 18:30:07 +02:00
Veronica Berglyd Olsen
92888338f4 Add footnotes to output translation file 2024-04-15 18:25:13 +02:00
Veronica Berglyd Olsen
44192c88f5 Add footnote handling in html and markdown output 2024-04-15 18:22:55 +02:00
Veronica Berglyd Olsen
8d24243d4f Complete footnotes processing in tokenizer 2024-04-15 18:22:29 +02:00
Veronica Berglyd Olsen
7c4d4dec5c Use format list also for marker keys 2024-04-15 01:11:32 +02:00
Veronica Berglyd Olsen
9823677983 Add text marker processing in tokenizer class 2024-04-15 00:42:01 +02:00
Veronica Berglyd Olsen
ec6a5712e5 Update tests 2024-04-14 23:59:57 +02:00
Veronica Berglyd Olsen
70033cb0f3 Add insert footnote feature in editor 2024-04-14 23:33:35 +02:00
Veronica Berglyd Olsen
da102d3d5a Merge branch 'main' into features/footnotes 2024-04-14 22:17:23 +02:00
Veronica Berglyd Olsen
5568b43b22 Add key generator to text index 2024-04-14 22:17:00 +02:00
Veronica Berglyd Olsen
5da30e6608 Update the status shape feature (#1819) 2024-04-13 19:26:32 +02:00
Veronica Berglyd Olsen
0fb0af5b32 Merge branch 'main' into updates/shapes 2024-04-13 19:22:05 +02:00
Veronica Berglyd Olsen
f5fcc57a06 Merge branch 'release' 2024-04-13 19:19:53 +02:00
Veronica Berglyd Olsen
4a1a3013f4 Add missing i18n wrapper to shape labels 2024-04-13 18:34:09 +02:00
Veronica Berglyd Olsen
cf57a3ead0 Update file format spec 2024-04-13 01:34:12 +02:00
Veronica Berglyd Olsen
39b2ff4a72 Bump main branch to version 2.5a1 (correctly this time) 2024-04-13 01:10:39 +02:00
Veronica Berglyd Olsen
eced026aeb Add shape setting to status and importance icons (#1810) 2024-04-13 01:07:43 +02:00
Veronica Berglyd Olsen
730d09a76c Merge branch 'main' into feature/status_shapes 2024-04-13 01:04:24 +02:00
Veronica Berglyd Olsen
b521b39498 Bump main branch to version 2.5a1 2024-04-13 01:03:58 +02:00
Veronica Berglyd Olsen
492bc044fc Make project setting lists behave the same way 2024-04-11 21:53:37 +02:00
Veronica Berglyd Olsen
84ff2f8371 Merge branch 'main' into feature/status_shapes 2024-04-11 21:46:48 +02:00
Veronica Berglyd Olsen
54f26ea06e Update test 2024-04-11 21:42:33 +02:00
Veronica Berglyd Olsen
e10f3a19d3 Add size policy types 2024-04-11 21:36:48 +02:00
Veronica Berglyd Olsen
21a5dbbb52 Redesign status item controls, and clean up auto-replace methods 2024-04-11 21:35:25 +02:00
Veronica Berglyd Olsen
f67d2c5a4c Add pre-calculation of status icons 2024-04-11 19:52:54 +02:00
Veronica Berglyd Olsen
6cf83bb381 Add more status icon shapes 2024-04-11 11:17:03 +02:00
Veronica Berglyd Olsen
f4decae77f Simplify status class counter reset 2024-04-10 23:44:43 +02:00
Veronica Berglyd Olsen
e47f0c5166 Update GUI test 2024-04-10 23:35:14 +02:00
Veronica Berglyd Olsen
778b65b48c Add shape selector to project settings 2024-04-10 23:24:00 +02:00
Veronica Berglyd Olsen
d1dbb0b378 Add test coverage of status class 2024-04-10 22:19:15 +02:00
Veronica Berglyd Olsen
adfd003532 Update core tests 2024-04-10 21:39:23 +02:00
Veronica Berglyd Olsen
f17302439e Simplify further how status labels are added and updated 2024-04-10 21:38:43 +02:00
Veronica Berglyd Olsen
63d2f5c57d Simplify the status class 2024-04-10 17:57:39 +02:00
Veronica Berglyd Olsen
478db63b30 Update tests 2024-04-10 01:09:11 +02:00
Veronica Berglyd Olsen
127588699e Switch status entries from dict to dataclass 2024-04-10 01:08:57 +02:00
Veronica Berglyd Olsen
29c10542b1 Merge branch 'main' into feature/status_shapes 2024-04-09 23:22:45 +02:00
Veronica Berglyd Olsen
5c9fd471a7 Update codecov token 2024-04-09 23:00:17 +02:00
Veronica Berglyd Olsen
dd15c93f51 Comment out debug print 2024-04-09 22:41:37 +02:00
Veronica Berglyd Olsen
4003ececb2 Update tests 2024-04-09 22:40:14 +02:00
Veronica Berglyd Olsen
ff3cae9db4 Add status icon shape to project XML 2024-04-09 22:39:46 +02:00
Veronica Berglyd Olsen
af44646a77 Add a shape generator to the status class 2024-04-09 21:42:59 +02:00
Veronica Berglyd Olsen
ba42285172 Remove summaries from text index 2024-04-08 00:56:36 +02:00
Veronica Berglyd Olsen
dff2d5996b Add new index classes to store special text comments 2024-04-07 23:56:40 +02:00
Veronica Berglyd Olsen
08b17daec9 Add parsing for terms in comments 2024-04-07 18:06:21 +02:00
223 changed files with 8829 additions and 8373 deletions

View File

@@ -1,5 +0,0 @@
[flake8]
ignore = E133,E221,E226,E228,E241,W503,ANN101,ANN102,ANN401
per-file-ignores=tests/*:ANN
max-line-length = 99
exclude = docs/*

View File

@@ -9,7 +9,7 @@ jobs:
- name: Python Setup
uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: "3.12"
architecture: x64
- name: Install Packages (apt)
@@ -35,15 +35,14 @@ jobs:
novelwriter/assets/manual.pdf
novelwriter/assets/i18n/*.qm
if-no-files-found: error
retention-days: 1
retention-days: 14
buildLinux:
needs: buildAssets
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11"]
linux-tag: ["manylinux_2_24_x86_64", "manylinux_2_28_x86_64"]
env:
PYTHON_VERSION: "3.12"
LINUX_TAG: "manylinux_2_28_x86_64"
steps:
- name: Python Setup
uses: actions/setup-python@v5
@@ -64,19 +63,24 @@ jobs:
path: novelwriter/assets
- name: Build AppImage
run: python pkgutils.py build-appimage --linux-tag ${{ matrix.linux-tag }} --python-version ${{ matrix.python-version }}
run: python pkgutils.py build-appimage --linux-tag $LINUX_TAG --python-version $PYTHON_VERSION
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: AppImage-${{ matrix.python-version }}-${{ matrix.linux-tag }}
name: Linux-AppImage
path: dist_appimage
if-no-files-found: error
retention-days: 1
retention-days: 14
buildMac:
buildMac-AMD64:
needs: buildAssets
# Stay on macos-12 due to https://github.com/create-dmg/create-dmg/issues/143
runs-on: macos-12
env:
PYTHON_VERSION: "3.12"
PACKAGE_ARCH: x86_64
MINICONDA_ARCH: x86_64
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -88,12 +92,40 @@ jobs:
path: novelwriter/assets
- name: Build App Bundle
run: ./setup/macos/build.sh
run: ./setup/macos/build.sh $PYTHON_VERSION $PACKAGE_ARCH $MINICONDA_ARCH
- name: Upload DMG
uses: actions/upload-artifact@v4
with:
name: MacOS-DMG
name: MacOS-AMD64-DMG
path: dist_macos
if-no-files-found: error
retention-days: 1
retention-days: 14
buildMac-M1:
needs: buildAssets
runs-on: macos-14
env:
PYTHON_VERSION: "3.12"
PACKAGE_ARCH: aarch64
MINICONDA_ARCH: arm64
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download Artifacts
uses: actions/download-artifact@v4
with:
name: nw-assets
path: novelwriter/assets
- name: Build App Bundle
run: ./setup/macos/build.sh $PYTHON_VERSION $PACKAGE_ARCH $MINICONDA_ARCH
- name: Upload DMG
uses: actions/upload-artifact@v4
with:
name: MacOS-M1-DMG
path: dist_macos
if-no-files-found: error
retention-days: 14

View File

@@ -14,7 +14,7 @@ jobs:
testLinux:
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12"]
runs-on: ubuntu-latest
steps:
- name: Python Setup
@@ -36,7 +36,7 @@ jobs:
- name: Run Tests
run: |
export QT_QPA_PLATFORM=offscreen
pytest -v --cov=novelwriter --timeout=60
python -m pytest -v --cov=novelwriter --timeout=60
- name: Upload to Codecov
uses: codecov/codecov-action@v4
env:

View File

@@ -12,7 +12,7 @@ on:
jobs:
testMac:
runs-on: macos-latest
runs-on: macos-13
steps:
- name: Python Setup
uses: actions/setup-python@v5
@@ -30,7 +30,7 @@ jobs:
- name: Run Tests
run: |
export QT_QPA_PLATFORM=offscreen
pytest -v --cov=novelwriter --timeout=60
python -m pytest -v --cov=novelwriter --timeout=60
- name: Upload to Codecov
uses: codecov/codecov-action@v4
env:

View File

@@ -26,7 +26,7 @@ jobs:
pip install -U -r requirements.txt -r tests/requirements.txt
- name: Run Tests
run: |
pytest -v --cov=novelwriter --timeout=60
python -m pytest -v --cov=novelwriter --timeout=60
- name: Upload to Codecov
uses: codecov/codecov-action@v4
env:

View File

@@ -1,5 +1,183 @@
# novelWriter Changelog
## Version 2.5 Beta 1 [2024-05-26]
### 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.
### Detailed Changelog
**General Changes**
* novelWriter now requires Python 3.9 and Qt 5.15 as a minimum. That means Ubuntu 20.04 and older
are no longer supported for the Debian packages. Users on Ubuntu 20.04 and equivalent Linux
distros must use the AppImage. PRs #1826 and #1833.
**Major Features**
* Status and Importance icons can now be assigned a shape in addition to a colour. Issue #1786.
PRs #1810 and #1819.
* Footnotes have been added. They use a modified comment format for specifying the footnote text,
and a shortcode for inserting the footnote into the text. They are linked via a key. If the
footnote is inserted via the **Insert** menu, a unique key is generated. Issue #342. PR #1832.
* Comments in general now support formatting, so format tags work just like in regular text. This
was needed for footnotes, but has been expanded to include all comments. PR #1832.
* Two new themes have been added. Dracula is available as a set for both the GUI and the editor.
Snazzy Light is available for syntax only, but works well with the default light GUI theme.
PR #1840.
* Character dialogue highlighting has been completely redesigned. The highlighting rule now
specifically relates to dialogue, and not just quoted text in general. To that effect, more rules
for detecting what is and isn't dialogue has been added. This is designed to support difference
between dialogue conventions in different languages. Issues #1770 and #1771. PR #1864.
* The font settings in Preferences now considers the full range of font options, in particular font
style and weight. The changes include the font size in these settings, so the additional setting
for font size has been removed. Use the font dialog to pick size as well. Due to this removal,
the font size has been reset to default and must be set again by the user. Issue #1733. PR #1881.
* The document viewer and the manuscript preview now uses a completely new class to generate the
formatted text. The old method used an outdated HTML format as the go-between, which restricted
what formatting could be applied. The main noticeable change to users at the moment should be the
size of headings, that now match the size in the editor. Issue #1882. PR #1892.
**Improvements**
* The default dark theme has been redesigned to match the GUI theme a little better, and is now
also consistent with the default light themes. PRs #1836 and #1840.
* Special comments now have a different text colour than plain comments. PR #1836.
* The various Tomorrow themes are now a little more consistent in what colours are used for which
components. More variation has also been added. PR #1840.
* The Tango theme has been updated. PR #1840.
* The last cursor position is now always saved when a document is closed in the editor, even if the
content has not been changed. PR #1849.
* The settings for first line indent have now been extended to allow setting how deep the indents
are and whether or not to indent the first paragraph after a heading or break. The settings have
also moved from being Open Document specific to the general formatting tab for manuscripts.
PR #1857.
* Preserving in-paragraph line breaks is now a general Manuscript setting that applies to all
output formats. PR #1891.
* The Manuscript builds now also uses the full font settings of the selected output font, not just
the font family and size option. Since the size option has been merged into the general font
choice, the separate font size setting has been dropped and users must set it again. PR #1891.
* It is now possible to disable the usage of the font selection dialog from your operating system.
At least on Linux Gnome desktops, the system's font dialog lacks a lot of the selection options
available in the font dialog provided with Qt. PR #1891.
**Bugfixes**
* Fixed an issue where Manuscript dialogs would be left open if the project was closed. PR #1844.
* First line indents are no longer reset after a comment. Issue #1852. PR #1857.
**Packaging**
* Minimal zip file release packages can no longer be built from the package utility script. The zip
file needed for building the Windows installer is still available. PR #1826.
* Added build packages for AARCH64 on MacOS. It seems some dependencies still prevents it from
being installed cleanly without requiring some virtualisation feature, but it works. PR #1829.
* AppImages are now released with Python 3.12. PR #1829.
**Other Changes**
* A new GUI theme setting for error text has been added. It is used when error messages need to be
displayed on the GUI. PR #1880.
**Code Improvements**
* The behaviour of dialogs across various platforms has been unified and improved a little to
ensure more consistent behaviour. It is mostly related to modality and z-order. There are still
limitations due to platform restrictions, like non-modal dialogs being stuck on top on Linux
Wayland. PR #1844.
* Some GUI components have been improved and refactored to rely more on signals and slots rather
than reaching into each other's classes and calling methods. This is a little more robust.
PR #1849.
* The text tokenizer used to parse the formatting codes in novelWriter has been refactored and
improved. In particular, multi-line paragraphs are now processed during the second pass of
tokenization rather than when the final format is applied. This allows a much more compact format
of the token data, and avoid potential inconsistencies between the various output formats.
PR #1885.
----
## Version 2.4.3 [2024-05-20]
### Release Notes
This is a patch release that fixes issues with the document font in the editor, viewer and
manuscript preview on some Linux distros, and also fixes a potential crash on Windows when using
the spell check dictionary install tool.
### Detailed Changelog
**Bugfixes**
* Fix a crash in the dictionaries install tool on Windows if the config folder reported by the
third party Enchant spell checker tool didn't already exist prior to adding new dictionaries.
The folder is now created when the tool is opened if it doesn't exist. Issue #1874. PR #1876.
* Fix issues setting a different text font for the editor and viewer, and related issues with the
preview in the Manuscript Build tool, on certain platforms. Changing the font and setting
non-standard font sizes produced unexpected results when reloading. The issue seems to be related
to Qt 5.15.3, but that is not fully confirmed. However, the only place so far where the issue is
observed is on Mint 21.3. Issues #1862 and #1875. PR #1877.
----
## Version 2.4.2 [2024-05-18]
### Release Notes
This is a patch release that fixes two minor font issues and updates the Dutch and Chinese
translations.
### Detailed Changelog
**Bugfixes**
* Change the preview widget in the Manuscript Build tool to use the document font only for the
document itself, not the yellow heading stating the age of the previewed text. PR #1863.
* Fixes the syntax highlighter so that it is re-initialised when the document editor is
re-initialised, so that changes to document font size regenerates the heading font sizes as well.
Issue #1865. PR #1866.
**Internationalisation**
* Dutch translation updated by Martijn van der Kleijn (@mvdkleijn). PR #1872.
* Chinese translation updated by @longqzh. PR #1872.
----
## Version 2.4.1 [2024-05-06]
### Release Notes
This is a patch release that fixes a number of minor issues with the Manuscript Build tool. The
only critical fix is related to a potential crash when deleting a build entry when its settings
dialog is still open.
### Detailed Changelog
**Bugfixes**
* Fixed an issue that could crash novelWriter if a build settings entry was deleted from the
Manuscript tool while its settings window was open. PR #1845.
**Improvements**
* Changed the font used for the document editor and viewer header and footer from the one used for
the document text to the one used for the user interface. Issue #1842. PR #1843.
* Added a save document step in the editor before running a manuscript build or a preview job. This
ensures that the text in the editor is included in the manuscript. Issue #1835. PR #1846.
* Restore preview panel scroll bar position after updating the preview on the Manuscript tool.
Issue #1837. PR #1846.
* Close all non-modal dialogs when a project is closed. PR #1848.
**Code Improvements**
* Fixed an issue with the Build Settings dialog that blocks the garbage collector from deleting the
dialog after it's been closed. PR #1843.
* Remove some deprecated translation entries from the translation files. PR #1850.
----
## Version 2.4 [2024-04-20]
### Release Notes

View File

@@ -55,6 +55,9 @@ Some of the assets bundled with novelWriter were adapted from the following sour
* [Tomorrow](https://github.com/chriskempson/base16) syntax themes by Chris Kempson (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)
* [Cyberpunk Night](https://github.com/alemvigh) theme by Anders Lemvigh (CC BY-SA 4.0)
* [Dracula](https://draculatheme.com) theme by Zeno Rocha (MIT License)
* [Snazzy Light](https://github.com/loilo/vscode-snazzy-light) theme by Florian Reuschel (MIT License)
## Fonts

View File

@@ -31,9 +31,9 @@ documentation.
## Implementation
novelWriter is written with Python 3 (3.8+) using Qt5 and PyQt5 (5.10+), and is released on Linux,
Windows and macOS. It can in principle run on any Operating System that also supports Qt, PyQt and
Python.
novelWriter is written with Python 3 (3.9+) using Qt5 and PyQt5 (5.15 only), and is released on
Linux, Windows and macOS. It can in principle run on any Operating System that also supports Qt,
PyQt and Python.
## Project Contributions

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -16,13 +16,21 @@ This is a list of hopefully helpful little tips on how to get the most out of no
Managing the Project
====================
.. dropdown:: Create a Project from a Template
:animate: fade-in-slide-down
On the Welcome dialog's **Create New Project** form, you can select to "Prefill Project" from
the content of a different project. This feature is most useful if you copy a project you have
dedicated to be a template project. If you have a structure and settings you want to use for
every new project, this is the best solution.
.. dropdown:: Merge Multiple Documents Into One
:animate: fade-in-slide-down
If you need to merge a selection of documents in your project into a single document, you can
achieve this by first making a new folder for just that purpose, and drag all the files you want
merged into this folder. Then you can right click the folder, select :guilabel:`Transform` and
:guilabel:`Merge Documents in Folder`.
achieve this by first making a new folder for just that purpose, and drag all the documents you
want merged into this folder. Then you can right click the folder, select :guilabel:`Transform`
and :guilabel:`Merge Documents in Folder`.
In the dialog that pops up, the documents will be in the same order as in the folder, but you
can rearrange them here of you wish. See :ref:`a_ui_tree_split_merge` for more details.
@@ -58,8 +66,6 @@ Organising Your Text
such text. Separating chapter and scene files also allows you to make scene files child
documents of the chapter.
.. versionadded:: 2.0
.. dropdown:: Distinguishing Soft and Hard Scene Breaks
:animate: fade-in-slide-down

View File

@@ -131,9 +131,11 @@ A GUI theme ``.conf`` file consists of the following settings:
linkvisited = 100, 100, 100
[GUI]
statusnone = 100, 100, 100
statussaved = 100, 100, 100
statusunsaved = 100, 100, 100
helptext = 0, 0, 0
errortext = 255, 0, 0
statusnone = 120, 120, 120
statussaved = 2, 133, 37
statusunsaved = 200, 15, 39
In the Main section you must at least define the ``name`` and ``icontheme`` settings. The
``icontheme`` settings should correspond to one of the internal icon themes, either
@@ -143,7 +145,11 @@ setting must match the icon theme's folder name.
The Palette values correspond to the Qt enum values for ``QPalette::ColorRole``, see the
`Qt documentation <https://doc.qt.io/qt-5.15/qpalette.html#ColorRole-enum>`_ for more details. The
colour values are RGB numbers on the format ``r, g, b`` where each is an integer from ``0`` to
``255``. Omitted values are not loaded and will use default values.
``255``. Omitted values are not loaded and will use default values. If the ``helptext`` colour is
not defined, it is computed as a colour between the ``window`` and ``windowtext`` colour.
.. versionadded:: 2.5
The ``errortext`` theme colour entry was added.
Custom Syntax Theme
@@ -168,12 +174,13 @@ A syntax theme ``.conf`` file consists of the following settings:
headertext = 0, 0, 0
headertag = 0, 0, 0
emphasis = 0, 0, 0
straightquotes = 0, 0, 0
doublequotes = 0, 0, 0
singlequotes = 0, 0, 0
dialog = 0, 0, 0
altdialog = 0, 0, 0
note = 0, 0, 0
hidden = 0, 0, 0
shortcode = 0, 0, 0
keyword = 0, 0, 0
tag = 0, 0, 0
value = 0, 0, 0
optional = 0, 0, 0
spellcheckline = 0, 0, 0
@@ -197,3 +204,7 @@ Omitted syntax colours default to black, except ``background`` which defaults to
.. versionadded:: 2.4
The ``texthighlight`` syntax colour entry was added.
.. versionadded:: 2.5
The ``dialog``, ``altdialog``, ``note`` and ``tag`` syntax colour entries were added.
``straightquotes``, ``doublequotes`` and ``singlequotes`` were removed.

View File

@@ -1,8 +1,17 @@
.. _a_proj:
**************
Novel Projects
**************
*****************
Managing Projects
*****************
Your text in novelWriter is organised into projects. Each project is meant to contain one novel
and associated notes. If you have multiple novels in a series, with the same characters and shared
notes, it is also possible to keep all of them in the same project.
.. _a_proj_new:
Creating Project
================
New projects can be created from the **Project** menu by selecting **Create or Open Project**. This
will open the **Welcome** dialog, where you can select the :guilabel:`New` button that will assist
@@ -27,10 +36,19 @@ and page counts, is available through the **Novel Details** dialog. Statistics a
is also available in the **Manuscript Build** tool.
Template Projects
-----------------
From the Welcome dialog you can also create a new from another existing project. If you have a
specific structure you want to use for all your new projects, you can create a dedicated project to
be used as a template, and select to copy an existing project from the :guilabel:"Prefill Project"
option from the **New Project** form.
.. _a_proj_roots:
Project Roots
=============
Project Structure
=================
Projects are structured into a set of top level folders called "Root Folders". They are visible in
the project tree at the left side of the main window.

View File

@@ -39,11 +39,9 @@ The following Python packages are needed to run all features of novelWriter:
* ``PyQt5`` – needed for connecting with the Qt5 libraries.
* ``PyEnchant`` – needed for spell checking (optional).
PyQt/Qt should be at least 5.10, but ideally 5.13 or higher for all features to work. For instance,
searching using regular expressions with full Unicode support requires 5.13.
If you want spell checking, you must install the ``PyEnchant`` package. The spell check library
must be at least 3.0 to work with Windows. On Linux, 2.0 also works fine.
PyQt/Qt must be at least 5.15.0. If you want spell checking, you must install the ``PyEnchant``
package. The spell check library must be at least 3.0 to work with Windows. On Linux, 2.0 also
works fine.
If you install from PyPi, these dependencies should be installed automatically. If you install from
source, dependencies can still be installed from PyPi with:
@@ -55,7 +53,7 @@ source, dependencies can still be installed from PyPi with:
.. note::
On Linux distros, the Qt library is usually split up into multiple packages. In some cases,
secondary dependencies may not be installed automatically. For novelWriter, the library files
for renderring the SVG icons may be left out and needs to be installed manually. This is the
for rendering the SVG icons may be left out and needs to be installed manually. This is the
case on for instance Arch Linux.

View File

@@ -791,22 +791,22 @@
<context>
<name>GuiDocEditFooter</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3007" />
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<source>Line: {0} ({1})</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<source>Words: {0} ({1})</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<location filename="../novelwriter/gui/doceditor.py" line="3010" />
<source>Words: {0} selected</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3021" />
<location filename="../novelwriter/gui/doceditor.py" line="3022" />
<source>Status</source>
<translation type="unfinished" />
</message>
@@ -814,27 +814,27 @@
<context>
<name>GuiDocEditHeader</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2805" />
<location filename="../novelwriter/gui/doceditor.py" line="2806" />
<source>Toggle Tool Bar</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2810" />
<location filename="../novelwriter/gui/doceditor.py" line="2811" />
<source>Outline</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2815" />
<location filename="../novelwriter/gui/doceditor.py" line="2816" />
<source>Search</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2820" />
<location filename="../novelwriter/gui/doceditor.py" line="2821" />
<source>Toggle Focus Mode</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2825" />
<location filename="../novelwriter/gui/doceditor.py" line="2826" />
<source>Close</source>
<translation type="unfinished" />
</message>
@@ -842,62 +842,62 @@
<context>
<name>GuiDocEditSearch</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2413" />
<location filename="../novelwriter/gui/doceditor.py" line="2414" />
<source>Search for</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2418" />
<location filename="../novelwriter/gui/doceditor.py" line="2419" />
<source>Replace with</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2426" />
<location filename="../novelwriter/gui/doceditor.py" line="2427" />
<source>Search</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2434" />
<location filename="../novelwriter/gui/doceditor.py" line="2435" />
<source>Case Sensitive</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2440" />
<location filename="../novelwriter/gui/doceditor.py" line="2441" />
<source>Whole Words Only</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2446" />
<location filename="../novelwriter/gui/doceditor.py" line="2447" />
<source>RegEx Mode</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2452" />
<location filename="../novelwriter/gui/doceditor.py" line="2453" />
<source>Loop Search</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2458" />
<location filename="../novelwriter/gui/doceditor.py" line="2459" />
<source>Search Next File</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2466" />
<location filename="../novelwriter/gui/doceditor.py" line="2467" />
<source>Preserve Case</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2474" />
<location filename="../novelwriter/gui/doceditor.py" line="2475" />
<source>Close Search</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2485" />
<location filename="../novelwriter/gui/doceditor.py" line="2486" />
<source>Find in current document</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2489" />
<location filename="../novelwriter/gui/doceditor.py" line="2490" />
<source>Find and replace in current document</source>
<translation type="unfinished" />
</message>
@@ -905,122 +905,122 @@
<context>
<name>GuiDocEditor</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="441" />
<location filename="../novelwriter/gui/doceditor.py" line="442" />
<source>Opened Document: {0}</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="484" />
<location filename="../novelwriter/gui/doceditor.py" line="485" />
<source>This document has been changed outside of novelWriter while it was open. Overwrite the file on disk?</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="493" />
<location filename="../novelwriter/gui/doceditor.py" line="494" />
<source>Could not save document.</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="518" />
<location filename="../novelwriter/gui/doceditor.py" line="519" />
<source>Saved Document: {0}</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="687" />
<location filename="../novelwriter/gui/doceditor.py" line="688" />
<source>Spell checking requires the package PyEnchant. It does not appear to be installed.</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="713" />
<location filename="../novelwriter/gui/doceditor.py" line="714" />
<source>Spell check complete</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="833" />
<location filename="../novelwriter/gui/doceditor.py" line="834" />
<source>Document Details</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="835" />
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<source>Created: {0}</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<location filename="../novelwriter/gui/doceditor.py" line="837" />
<source>Updated: {0}</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="838" />
<location filename="../novelwriter/gui/doceditor.py" line="839" />
<source>File Location: {0}</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1112" />
<location filename="../novelwriter/gui/doceditor.py" line="1113" />
<source>Set as Document Name</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1118" />
<location filename="../novelwriter/gui/doceditor.py" line="1119" />
<source>Follow Tag</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1122" />
<location filename="../novelwriter/gui/doceditor.py" line="1123" />
<source>Create Note for Tag</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1128" />
<location filename="../novelwriter/gui/doceditor.py" line="1129" />
<source>Cut</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1130" />
<location filename="../novelwriter/gui/doceditor.py" line="1131" />
<source>Copy</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1133" />
<location filename="../novelwriter/gui/doceditor.py" line="1134" />
<source>Paste</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1138" />
<location filename="../novelwriter/gui/doceditor.py" line="1139" />
<source>Select All</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1140" />
<location filename="../novelwriter/gui/doceditor.py" line="1141" />
<source>Select Word</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1144" />
<location filename="../novelwriter/gui/doceditor.py" line="1145" />
<source>Select Paragraph</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1160" />
<location filename="../novelwriter/gui/doceditor.py" line="1161" />
<source>Spelling Suggestion(s)</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1167" />
<location filename="../novelwriter/gui/doceditor.py" line="1168" />
<source>No Suggestions</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1170" />
<location filename="../novelwriter/gui/doceditor.py" line="1171" />
<source>Add Word to Dictionary</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1596" />
<location filename="../novelwriter/gui/doceditor.py" line="1597" />
<source>Please select some text before calling replace quotes.</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1903" />
<location filename="../novelwriter/gui/doceditor.py" line="1904" />
<source>Do you want to create a new project note for the tag '{0}'?</source>
<translation type="unfinished" />
</message>
@@ -1104,52 +1104,52 @@
<context>
<name>GuiDocToolBar</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2270" />
<location filename="../novelwriter/gui/doceditor.py" line="2271" />
<source>Markdown Bold</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2276" />
<location filename="../novelwriter/gui/doceditor.py" line="2277" />
<source>Markdown Italic</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2282" />
<location filename="../novelwriter/gui/doceditor.py" line="2283" />
<source>Markdown Strikethrough</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2288" />
<location filename="../novelwriter/gui/doceditor.py" line="2289" />
<source>Shortcode Bold</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2294" />
<location filename="../novelwriter/gui/doceditor.py" line="2295" />
<source>Shortcode Italic</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2300" />
<location filename="../novelwriter/gui/doceditor.py" line="2301" />
<source>Shortcode Strikethrough</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2306" />
<location filename="../novelwriter/gui/doceditor.py" line="2307" />
<source>Shortcode Underline</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2312" />
<location filename="../novelwriter/gui/doceditor.py" line="2313" />
<source>Shortcode Highlight</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2318" />
<location filename="../novelwriter/gui/doceditor.py" line="2319" />
<source>Shortcode Superscript</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2324" />
<location filename="../novelwriter/gui/doceditor.py" line="2325" />
<source>Shortcode Subscript</source>
<translation type="unfinished" />
</message>
@@ -2198,57 +2198,57 @@
<context>
<name>GuiManuscriptBuild</name>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="72" />
<location filename="../novelwriter/tools/manusbuild.py" line="70" />
<source>Build Manuscript</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="93" />
<location filename="../novelwriter/tools/manusbuild.py" line="91" />
<source>Output Format</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="119" />
<location filename="../novelwriter/tools/manusbuild.py" line="117" />
<source>Table of Contents</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="147" />
<location filename="../novelwriter/tools/manusbuild.py" line="145" />
<source>Path</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="157" />
<location filename="../novelwriter/tools/manusbuild.py" line="155" />
<source>File Name</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="160" />
<location filename="../novelwriter/tools/manusbuild.py" line="158" />
<source>Reset file name to default</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<location filename="../novelwriter/tools/manusbuild.py" line="182" />
<source>Open Folder</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="186" />
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<source>&amp;Build</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="281" />
<location filename="../novelwriter/tools/manusbuild.py" line="279" />
<source>Select Folder</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="319" />
<location filename="../novelwriter/tools/manusbuild.py" line="317" />
<source>Output folder does not exist.</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="327" />
<location filename="../novelwriter/tools/manusbuild.py" line="325" />
<source>The file already exists. Do you want to overwrite it?</source>
<translation type="unfinished" />
</message>
@@ -2415,7 +2415,7 @@
<context>
<name>GuiOutlineToolBar</name>
<message>
<location filename="../novelwriter/gui/outline.py" line="223" />
<location filename="../novelwriter/gui/outline.py" line="222" />
<source>Outline of</source>
<translation type="unfinished" />
</message>
@@ -3860,79 +3860,20 @@
<translation type="unfinished" />
</message>
</context>
<context>
<name>QWizard</name>
<message>
<location filename="qtbase.py" line="54" />
<source>Go Back</source>
<translation type="unfinished" />
</message>
<message>
<location filename="qtbase.py" line="55" />
<source>&lt; &amp;Back</source>
<translation type="unfinished" />
</message>
<message>
<location filename="qtbase.py" line="56" />
<source>Continue</source>
<translation type="unfinished" />
</message>
<message>
<location filename="qtbase.py" line="57" />
<source>&amp;Next</source>
<translation type="unfinished" />
</message>
<message>
<location filename="qtbase.py" line="58" />
<source>&amp;Next &gt;</source>
<translation type="unfinished" />
</message>
<message>
<location filename="qtbase.py" line="59" />
<source>Commit</source>
<translation type="unfinished" />
</message>
<message>
<location filename="qtbase.py" line="60" />
<source>Done</source>
<translation type="unfinished" />
</message>
<message>
<location filename="qtbase.py" line="61" />
<source>&amp;Finish</source>
<translation type="unfinished" />
</message>
<message>
<location filename="qtbase.py" line="63" />
<location filename="qtbase.py" line="62" />
<source>Cancel</source>
<translation type="unfinished" />
</message>
<message>
<location filename="qtbase.py" line="64" />
<source>Help</source>
<translation type="unfinished" />
</message>
<message>
<location filename="qtbase.py" line="65" />
<source>&amp;Help</source>
<translation type="unfinished" />
</message>
</context>
<context>
<name>SharedData</name>
<message>
<location filename="../novelwriter/shared.py" line="249" />
<location filename="../novelwriter/shared.py" line="250" />
<source>novelWriter Project File or Zip File</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/shared.py" line="250" />
<location filename="../novelwriter/shared.py" line="251" />
<source>novelWriter Project File</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/shared.py" line="254" />
<location filename="../novelwriter/shared.py" line="255" />
<source>Open Project</source>
<translation type="unfinished" />
</message>
@@ -4041,32 +3982,32 @@
<context>
<name>_DetailsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Setting</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Value</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="589" />
<location filename="../novelwriter/tools/manuscript.py" line="600" />
<source>Name</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="595" />
<location filename="../novelwriter/tools/manuscript.py" line="606" />
<source>Selection</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="611" />
<location filename="../novelwriter/tools/manuscript.py" line="622" />
<source>Title</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="612" />
<location filename="../novelwriter/tools/manuscript.py" line="623" />
<source>Hidden</source>
<translation type="unfinished" />
</message>
@@ -4112,22 +4053,22 @@
<context>
<name>_GuiAlert</name>
<message>
<location filename="../novelwriter/shared.py" line="410" />
<location filename="../novelwriter/shared.py" line="422" />
<source>Information</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/shared.py" line="413" />
<location filename="../novelwriter/shared.py" line="425" />
<source>Warning</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/shared.py" line="416" />
<location filename="../novelwriter/shared.py" line="428" />
<source>Error</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/shared.py" line="419" />
<location filename="../novelwriter/shared.py" line="431" />
<source>Question</source>
<translation type="unfinished" />
</message>
@@ -4438,27 +4379,27 @@
<context>
<name>_PreviewWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="757" />
<location filename="../novelwriter/tools/manuscript.py" line="769" />
<source>Press the "Preview" button to generate ...</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="855" />
<location filename="../novelwriter/tools/manuscript.py" line="867" />
<source>Processing ...</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="877" />
<location filename="../novelwriter/tools/manuscript.py" line="888" />
<source>Done</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="927" />
<location filename="../novelwriter/tools/manuscript.py" line="938" />
<source>Unknown</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="928" />
<location filename="../novelwriter/tools/manuscript.py" line="939" />
<source>Built</source>
<translation type="unfinished" />
</message>
@@ -4557,59 +4498,59 @@
<context>
<name>_StatsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1090" />
<location filename="../novelwriter/tools/manuscript.py" line="1050" />
<location filename="../novelwriter/tools/manuscript.py" line="1102" />
<location filename="../novelwriter/tools/manuscript.py" line="1062" />
<source>Words</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1117" />
<location filename="../novelwriter/tools/manuscript.py" line="1053" />
<location filename="../novelwriter/tools/manuscript.py" line="1129" />
<location filename="../novelwriter/tools/manuscript.py" line="1065" />
<source>Characters</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1091" />
<location filename="../novelwriter/tools/manuscript.py" line="1103" />
<source>Words in Headings</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1092" />
<location filename="../novelwriter/tools/manuscript.py" line="1104" />
<source>Words in Text</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1094" />
<location filename="../novelwriter/tools/manuscript.py" line="1106" />
<source>Headings</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1095" />
<location filename="../novelwriter/tools/manuscript.py" line="1107" />
<source>Paragraphs</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1118" />
<location filename="../novelwriter/tools/manuscript.py" line="1130" />
<source>Characters in Headings</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1119" />
<location filename="../novelwriter/tools/manuscript.py" line="1131" />
<source>Characters in Text</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1120" />
<location filename="../novelwriter/tools/manuscript.py" line="1132" />
<source>Characters, No Spaces</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1121" />
<location filename="../novelwriter/tools/manuscript.py" line="1133" />
<source>Characters in Headings, No Spaces</source>
<translation type="unfinished" />
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1122" />
<location filename="../novelwriter/tools/manuscript.py" line="1134" />
<source>Characters in Text, No Spaces</source>
<translation type="unfinished" />
</message>

View File

@@ -791,22 +791,22 @@
<context>
<name>GuiDocEditFooter</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3007" />
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<source>Line: {0} ({1})</source>
<translation>Zeile: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<source>Words: {0} ({1})</source>
<translation>Wörter: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<location filename="../novelwriter/gui/doceditor.py" line="3010" />
<source>Words: {0} selected</source>
<translation>Wörter: {0} markiert</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3021" />
<location filename="../novelwriter/gui/doceditor.py" line="3022" />
<source>Status</source>
<translation>Status</translation>
</message>
@@ -814,27 +814,27 @@
<context>
<name>GuiDocEditHeader</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2805" />
<location filename="../novelwriter/gui/doceditor.py" line="2806" />
<source>Toggle Tool Bar</source>
<translation>Werkzeugleiste ein/aus</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2810" />
<location filename="../novelwriter/gui/doceditor.py" line="2811" />
<source>Outline</source>
<translation>Gliederung</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2815" />
<location filename="../novelwriter/gui/doceditor.py" line="2816" />
<source>Search</source>
<translation>Suche</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2820" />
<location filename="../novelwriter/gui/doceditor.py" line="2821" />
<source>Toggle Focus Mode</source>
<translation>Ablenkungsfrei ein/aus</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2825" />
<location filename="../novelwriter/gui/doceditor.py" line="2826" />
<source>Close</source>
<translation>Schließen</translation>
</message>
@@ -842,62 +842,62 @@
<context>
<name>GuiDocEditSearch</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2413" />
<location filename="../novelwriter/gui/doceditor.py" line="2414" />
<source>Search for</source>
<translation>Suchen nach</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2418" />
<location filename="../novelwriter/gui/doceditor.py" line="2419" />
<source>Replace with</source>
<translation>Ersetzen durch</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2426" />
<location filename="../novelwriter/gui/doceditor.py" line="2427" />
<source>Search</source>
<translation>Suchen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2434" />
<location filename="../novelwriter/gui/doceditor.py" line="2435" />
<source>Case Sensitive</source>
<translation>Groß-/Kleinschreibung beachten</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2440" />
<location filename="../novelwriter/gui/doceditor.py" line="2441" />
<source>Whole Words Only</source>
<translation>Nur ganze Wörter</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2446" />
<location filename="../novelwriter/gui/doceditor.py" line="2447" />
<source>RegEx Mode</source>
<translation>RegEx-Modus</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2452" />
<location filename="../novelwriter/gui/doceditor.py" line="2453" />
<source>Loop Search</source>
<translation>Suche am Anfang fortsetzen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2458" />
<location filename="../novelwriter/gui/doceditor.py" line="2459" />
<source>Search Next File</source>
<translation>Nächstes Dokument durchsuchen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2466" />
<location filename="../novelwriter/gui/doceditor.py" line="2467" />
<source>Preserve Case</source>
<translation>Groß-/Kleinschreibung beibehalten</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2474" />
<location filename="../novelwriter/gui/doceditor.py" line="2475" />
<source>Close Search</source>
<translation>Suche schließen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2485" />
<location filename="../novelwriter/gui/doceditor.py" line="2486" />
<source>Find in current document</source>
<translation>Im geöffneten Dokument suchen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2489" />
<location filename="../novelwriter/gui/doceditor.py" line="2490" />
<source>Find and replace in current document</source>
<translation>Im geöffneten Dokument suchen und ersetzen</translation>
</message>
@@ -905,122 +905,122 @@
<context>
<name>GuiDocEditor</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="441" />
<location filename="../novelwriter/gui/doceditor.py" line="442" />
<source>Opened Document: {0}</source>
<translation>Dokument geöffnet: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="484" />
<location filename="../novelwriter/gui/doceditor.py" line="485" />
<source>This document has been changed outside of novelWriter while it was open. Overwrite the file on disk?</source>
<translation>Dieses Dokument wurde außerhalb von novelWriter geändert, während es hier geöffnet war. Möchten Sie die externen Änderungen überschreiben?</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="493" />
<location filename="../novelwriter/gui/doceditor.py" line="494" />
<source>Could not save document.</source>
<translation>Dokument konnte nicht gespeichert werden.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="518" />
<location filename="../novelwriter/gui/doceditor.py" line="519" />
<source>Saved Document: {0}</source>
<translation>Dokument gespeichert: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="687" />
<location filename="../novelwriter/gui/doceditor.py" line="688" />
<source>Spell checking requires the package PyEnchant. It does not appear to be installed.</source>
<translation>Die Rechtschreibprüfung erfordert das Paket PyEnchant. Es scheint nicht installiert zu sein.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="713" />
<location filename="../novelwriter/gui/doceditor.py" line="714" />
<source>Spell check complete</source>
<translation>Rechtschreibprüfung abgeschlossen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="833" />
<location filename="../novelwriter/gui/doceditor.py" line="834" />
<source>Document Details</source>
<translation>Details</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="835" />
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<source>Created: {0}</source>
<translation>Erstellt: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<location filename="../novelwriter/gui/doceditor.py" line="837" />
<source>Updated: {0}</source>
<translation>Aktualisiert: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="838" />
<location filename="../novelwriter/gui/doceditor.py" line="839" />
<source>File Location: {0}</source>
<translation>Dateispeicherort: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1112" />
<location filename="../novelwriter/gui/doceditor.py" line="1113" />
<source>Set as Document Name</source>
<translation>Als Dokumentname verwenden</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1118" />
<location filename="../novelwriter/gui/doceditor.py" line="1119" />
<source>Follow Tag</source>
<translation>Schlagwort öffnen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1122" />
<location filename="../novelwriter/gui/doceditor.py" line="1123" />
<source>Create Note for Tag</source>
<translation>Notiz für Schlagwort erstellen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1128" />
<location filename="../novelwriter/gui/doceditor.py" line="1129" />
<source>Cut</source>
<translation>Ausschneiden</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1130" />
<location filename="../novelwriter/gui/doceditor.py" line="1131" />
<source>Copy</source>
<translation>Kopieren</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1133" />
<location filename="../novelwriter/gui/doceditor.py" line="1134" />
<source>Paste</source>
<translation>Einfügen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1138" />
<location filename="../novelwriter/gui/doceditor.py" line="1139" />
<source>Select All</source>
<translation>Alles markieren</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1140" />
<location filename="../novelwriter/gui/doceditor.py" line="1141" />
<source>Select Word</source>
<translation>Wort markieren</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1144" />
<location filename="../novelwriter/gui/doceditor.py" line="1145" />
<source>Select Paragraph</source>
<translation>Absatz markieren</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1160" />
<location filename="../novelwriter/gui/doceditor.py" line="1161" />
<source>Spelling Suggestion(s)</source>
<translation>Korrekturvorschläge</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1167" />
<location filename="../novelwriter/gui/doceditor.py" line="1168" />
<source>No Suggestions</source>
<translation>Keine Vorschläge</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1170" />
<location filename="../novelwriter/gui/doceditor.py" line="1171" />
<source>Add Word to Dictionary</source>
<translation>Zum Wörterbuch hinzufügen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1596" />
<location filename="../novelwriter/gui/doceditor.py" line="1597" />
<source>Please select some text before calling replace quotes.</source>
<translation>Bitte markieren Sie den Text, in dem die Anführungszeichen ersetzt werden sollen.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1903" />
<location filename="../novelwriter/gui/doceditor.py" line="1904" />
<source>Do you want to create a new project note for the tag '{0}'?</source>
<translation>Möchten Sie für das Schlagwort „{0}“ eine neue Projektnotiz erstellen?</translation>
</message>
@@ -1104,52 +1104,52 @@
<context>
<name>GuiDocToolBar</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2270" />
<location filename="../novelwriter/gui/doceditor.py" line="2271" />
<source>Markdown Bold</source>
<translation>Fett mit Markdown</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2276" />
<location filename="../novelwriter/gui/doceditor.py" line="2277" />
<source>Markdown Italic</source>
<translation>Kursiv mit Markdown</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2282" />
<location filename="../novelwriter/gui/doceditor.py" line="2283" />
<source>Markdown Strikethrough</source>
<translation>Durchgestrichen mit Markdown</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2288" />
<location filename="../novelwriter/gui/doceditor.py" line="2289" />
<source>Shortcode Bold</source>
<translation>Fett mit Shortcode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2294" />
<location filename="../novelwriter/gui/doceditor.py" line="2295" />
<source>Shortcode Italic</source>
<translation>Kursiv mit Shortcode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2300" />
<location filename="../novelwriter/gui/doceditor.py" line="2301" />
<source>Shortcode Strikethrough</source>
<translation>Durchgestrichen mit Shortcode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2306" />
<location filename="../novelwriter/gui/doceditor.py" line="2307" />
<source>Shortcode Underline</source>
<translation>Unterstrichen mit Shortcode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2312" />
<location filename="../novelwriter/gui/doceditor.py" line="2313" />
<source>Shortcode Highlight</source>
<translation>Hervorheben mit Shortcode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2318" />
<location filename="../novelwriter/gui/doceditor.py" line="2319" />
<source>Shortcode Superscript</source>
<translation>Hochgestellt mit Shortcode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2324" />
<location filename="../novelwriter/gui/doceditor.py" line="2325" />
<source>Shortcode Subscript</source>
<translation>Tiefgestellt mit Shortcode</translation>
</message>
@@ -2198,57 +2198,57 @@
<context>
<name>GuiManuscriptBuild</name>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="72" />
<location filename="../novelwriter/tools/manusbuild.py" line="70" />
<source>Build Manuscript</source>
<translation>Manuskript erstellen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="93" />
<location filename="../novelwriter/tools/manusbuild.py" line="91" />
<source>Output Format</source>
<translation>Ausgabeformat</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="119" />
<location filename="../novelwriter/tools/manusbuild.py" line="117" />
<source>Table of Contents</source>
<translation>Inhaltsverzeichnis</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="147" />
<location filename="../novelwriter/tools/manusbuild.py" line="145" />
<source>Path</source>
<translation>Pfad</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="157" />
<location filename="../novelwriter/tools/manusbuild.py" line="155" />
<source>File Name</source>
<translation>Dateiname</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="160" />
<location filename="../novelwriter/tools/manusbuild.py" line="158" />
<source>Reset file name to default</source>
<translation>Dateiname auf Standard zurücksetzen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<location filename="../novelwriter/tools/manusbuild.py" line="182" />
<source>Open Folder</source>
<translation>Ordner öffnen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="186" />
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<source>&amp;Build</source>
<translation>&amp;Erstellen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="281" />
<location filename="../novelwriter/tools/manusbuild.py" line="279" />
<source>Select Folder</source>
<translation>Verzeichnis wählen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="319" />
<location filename="../novelwriter/tools/manusbuild.py" line="317" />
<source>Output folder does not exist.</source>
<translation>Das Ausgabeverzeichnis existiert nicht.</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="327" />
<location filename="../novelwriter/tools/manusbuild.py" line="325" />
<source>The file already exists. Do you want to overwrite it?</source>
<translation>Die Datei ist bereits vorhanden. Möchten Sie sie überschreiben?</translation>
</message>
@@ -2415,7 +2415,7 @@
<context>
<name>GuiOutlineToolBar</name>
<message>
<location filename="../novelwriter/gui/outline.py" line="223" />
<location filename="../novelwriter/gui/outline.py" line="222" />
<source>Outline of</source>
<translation>Gliederung für</translation>
</message>
@@ -3860,79 +3860,20 @@
<translation>Standard wiederherstellen</translation>
</message>
</context>
<context>
<name>QWizard</name>
<message>
<location filename="qtbase.py" line="54" />
<source>Go Back</source>
<translation>Zurück</translation>
</message>
<message>
<location filename="qtbase.py" line="55" />
<source>&lt; &amp;Back</source>
<translation>&lt; &amp;Zurück</translation>
</message>
<message>
<location filename="qtbase.py" line="56" />
<source>Continue</source>
<translation>Fortsetzen</translation>
</message>
<message>
<location filename="qtbase.py" line="57" />
<source>&amp;Next</source>
<translation>&amp;Weiter</translation>
</message>
<message>
<location filename="qtbase.py" line="58" />
<source>&amp;Next &gt;</source>
<translation>&amp;Weiter &gt;</translation>
</message>
<message>
<location filename="qtbase.py" line="59" />
<source>Commit</source>
<translation>Commit</translation>
</message>
<message>
<location filename="qtbase.py" line="60" />
<source>Done</source>
<translation>Fertig</translation>
</message>
<message>
<location filename="qtbase.py" line="61" />
<source>&amp;Finish</source>
<translation>&amp;Fertig</translation>
</message>
<message>
<location filename="qtbase.py" line="63" />
<location filename="qtbase.py" line="62" />
<source>Cancel</source>
<translation>&amp;Abbrechen</translation>
</message>
<message>
<location filename="qtbase.py" line="64" />
<source>Help</source>
<translation>Hilfe</translation>
</message>
<message>
<location filename="qtbase.py" line="65" />
<source>&amp;Help</source>
<translation>&amp;Hilfe</translation>
</message>
</context>
<context>
<name>SharedData</name>
<message>
<location filename="../novelwriter/shared.py" line="249" />
<location filename="../novelwriter/shared.py" line="250" />
<source>novelWriter Project File or Zip File</source>
<translation>novelWriter-Projektdatei oder Zip-Datei</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="250" />
<location filename="../novelwriter/shared.py" line="251" />
<source>novelWriter Project File</source>
<translation>novelWriter-Projektdatei</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="254" />
<location filename="../novelwriter/shared.py" line="255" />
<source>Open Project</source>
<translation>Projekt öffnen</translation>
</message>
@@ -4041,32 +3982,32 @@
<context>
<name>_DetailsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Setting</source>
<translation>Einstellung</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Value</source>
<translation>Wert</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="589" />
<location filename="../novelwriter/tools/manuscript.py" line="600" />
<source>Name</source>
<translation>Name</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="595" />
<location filename="../novelwriter/tools/manuscript.py" line="606" />
<source>Selection</source>
<translation>Auswahl</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="611" />
<location filename="../novelwriter/tools/manuscript.py" line="622" />
<source>Title</source>
<translation>Titel</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="612" />
<location filename="../novelwriter/tools/manuscript.py" line="623" />
<source>Hidden</source>
<translation>Ausgeblendet</translation>
</message>
@@ -4112,22 +4053,22 @@
<context>
<name>_GuiAlert</name>
<message>
<location filename="../novelwriter/shared.py" line="410" />
<location filename="../novelwriter/shared.py" line="422" />
<source>Information</source>
<translation>Information</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="413" />
<location filename="../novelwriter/shared.py" line="425" />
<source>Warning</source>
<translation>Warnung</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="416" />
<location filename="../novelwriter/shared.py" line="428" />
<source>Error</source>
<translation>Fehler</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="419" />
<location filename="../novelwriter/shared.py" line="431" />
<source>Question</source>
<translation>Frage</translation>
</message>
@@ -4438,27 +4379,27 @@
<context>
<name>_PreviewWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="757" />
<location filename="../novelwriter/tools/manuscript.py" line="769" />
<source>Press the "Preview" button to generate ...</source>
<translation>Zum Generieren den "Vorschau"-Button anklicken ...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="855" />
<location filename="../novelwriter/tools/manuscript.py" line="867" />
<source>Processing ...</source>
<translation>In Bearbeitung ...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="877" />
<location filename="../novelwriter/tools/manuscript.py" line="888" />
<source>Done</source>
<translation>Fertig</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="927" />
<location filename="../novelwriter/tools/manuscript.py" line="938" />
<source>Unknown</source>
<translation>Unbekannt</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="928" />
<location filename="../novelwriter/tools/manuscript.py" line="939" />
<source>Built</source>
<translation>Erstellt</translation>
</message>
@@ -4557,59 +4498,59 @@
<context>
<name>_StatsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1090" />
<location filename="../novelwriter/tools/manuscript.py" line="1050" />
<location filename="../novelwriter/tools/manuscript.py" line="1102" />
<location filename="../novelwriter/tools/manuscript.py" line="1062" />
<source>Words</source>
<translation>Wörter</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1117" />
<location filename="../novelwriter/tools/manuscript.py" line="1053" />
<location filename="../novelwriter/tools/manuscript.py" line="1129" />
<location filename="../novelwriter/tools/manuscript.py" line="1065" />
<source>Characters</source>
<translation>Zeichen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1091" />
<location filename="../novelwriter/tools/manuscript.py" line="1103" />
<source>Words in Headings</source>
<translation>Wörter in Überschriften</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1092" />
<location filename="../novelwriter/tools/manuscript.py" line="1104" />
<source>Words in Text</source>
<translation>Wörter im Text</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1094" />
<location filename="../novelwriter/tools/manuscript.py" line="1106" />
<source>Headings</source>
<translation>Überschriften</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1095" />
<location filename="../novelwriter/tools/manuscript.py" line="1107" />
<source>Paragraphs</source>
<translation>Absätze</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1118" />
<location filename="../novelwriter/tools/manuscript.py" line="1130" />
<source>Characters in Headings</source>
<translation>Zeichen in Überschriften</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1119" />
<location filename="../novelwriter/tools/manuscript.py" line="1131" />
<source>Characters in Text</source>
<translation>Zeichen im Text</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1120" />
<location filename="../novelwriter/tools/manuscript.py" line="1132" />
<source>Characters, No Spaces</source>
<translation>Zeichen ohne Leerzeichen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1121" />
<location filename="../novelwriter/tools/manuscript.py" line="1133" />
<source>Characters in Headings, No Spaces</source>
<translation>Zeichen in Überschriften ohne Leerzeichen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1122" />
<location filename="../novelwriter/tools/manuscript.py" line="1134" />
<source>Characters in Text, No Spaces</source>
<translation>Zeichen im Text ohne Leerzeichen</translation>
</message>

View File

@@ -791,22 +791,22 @@
<context>
<name>GuiDocEditFooter</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3007" />
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<source>Line: {0} ({1})</source>
<translation>Line: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<source>Words: {0} ({1})</source>
<translation>Words: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<location filename="../novelwriter/gui/doceditor.py" line="3010" />
<source>Words: {0} selected</source>
<translation>Words: {0} selected</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3021" />
<location filename="../novelwriter/gui/doceditor.py" line="3022" />
<source>Status</source>
<translation>Status</translation>
</message>
@@ -814,27 +814,27 @@
<context>
<name>GuiDocEditHeader</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2805" />
<location filename="../novelwriter/gui/doceditor.py" line="2806" />
<source>Toggle Tool Bar</source>
<translation>Toggle Tool Bar</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2810" />
<location filename="../novelwriter/gui/doceditor.py" line="2811" />
<source>Outline</source>
<translation>Outline</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2815" />
<location filename="../novelwriter/gui/doceditor.py" line="2816" />
<source>Search</source>
<translation>Search</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2820" />
<location filename="../novelwriter/gui/doceditor.py" line="2821" />
<source>Toggle Focus Mode</source>
<translation>Toggle Focus Mode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2825" />
<location filename="../novelwriter/gui/doceditor.py" line="2826" />
<source>Close</source>
<translation>Close</translation>
</message>
@@ -842,62 +842,62 @@
<context>
<name>GuiDocEditSearch</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2413" />
<location filename="../novelwriter/gui/doceditor.py" line="2414" />
<source>Search for</source>
<translation>Search for</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2418" />
<location filename="../novelwriter/gui/doceditor.py" line="2419" />
<source>Replace with</source>
<translation>Replace with</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2426" />
<location filename="../novelwriter/gui/doceditor.py" line="2427" />
<source>Search</source>
<translation>Search</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2434" />
<location filename="../novelwriter/gui/doceditor.py" line="2435" />
<source>Case Sensitive</source>
<translation>Case Sensitive</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2440" />
<location filename="../novelwriter/gui/doceditor.py" line="2441" />
<source>Whole Words Only</source>
<translation>Whole Words Only</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2446" />
<location filename="../novelwriter/gui/doceditor.py" line="2447" />
<source>RegEx Mode</source>
<translation>RegEx Mode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2452" />
<location filename="../novelwriter/gui/doceditor.py" line="2453" />
<source>Loop Search</source>
<translation>Loop Search</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2458" />
<location filename="../novelwriter/gui/doceditor.py" line="2459" />
<source>Search Next File</source>
<translation>Search Next File</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2466" />
<location filename="../novelwriter/gui/doceditor.py" line="2467" />
<source>Preserve Case</source>
<translation>Preserve Case</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2474" />
<location filename="../novelwriter/gui/doceditor.py" line="2475" />
<source>Close Search</source>
<translation>Close Search</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2485" />
<location filename="../novelwriter/gui/doceditor.py" line="2486" />
<source>Find in current document</source>
<translation>Find in current document</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2489" />
<location filename="../novelwriter/gui/doceditor.py" line="2490" />
<source>Find and replace in current document</source>
<translation>Find and replace in current document</translation>
</message>
@@ -905,122 +905,122 @@
<context>
<name>GuiDocEditor</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="441" />
<location filename="../novelwriter/gui/doceditor.py" line="442" />
<source>Opened Document: {0}</source>
<translation>Opened Document: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="484" />
<location filename="../novelwriter/gui/doceditor.py" line="485" />
<source>This document has been changed outside of novelWriter while it was open. Overwrite the file on disk?</source>
<translation>This document has been changed outside of novelWriter while it was open. Overwrite the file on disk?</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="493" />
<location filename="../novelwriter/gui/doceditor.py" line="494" />
<source>Could not save document.</source>
<translation>Could not save document.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="518" />
<location filename="../novelwriter/gui/doceditor.py" line="519" />
<source>Saved Document: {0}</source>
<translation>Saved Document: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="687" />
<location filename="../novelwriter/gui/doceditor.py" line="688" />
<source>Spell checking requires the package PyEnchant. It does not appear to be installed.</source>
<translation>Spell checking requires the package PyEnchant. It does not appear to be installed.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="713" />
<location filename="../novelwriter/gui/doceditor.py" line="714" />
<source>Spell check complete</source>
<translation>Spell check complete</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="833" />
<location filename="../novelwriter/gui/doceditor.py" line="834" />
<source>Document Details</source>
<translation>Document Details</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="835" />
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<source>Created: {0}</source>
<translation>Created: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<location filename="../novelwriter/gui/doceditor.py" line="837" />
<source>Updated: {0}</source>
<translation>Updated: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="838" />
<location filename="../novelwriter/gui/doceditor.py" line="839" />
<source>File Location: {0}</source>
<translation>File Location: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1112" />
<location filename="../novelwriter/gui/doceditor.py" line="1113" />
<source>Set as Document Name</source>
<translation>Set as Document Name</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1118" />
<location filename="../novelwriter/gui/doceditor.py" line="1119" />
<source>Follow Tag</source>
<translation>Follow Tag</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1122" />
<location filename="../novelwriter/gui/doceditor.py" line="1123" />
<source>Create Note for Tag</source>
<translation>Create Note for Tag</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1128" />
<location filename="../novelwriter/gui/doceditor.py" line="1129" />
<source>Cut</source>
<translation>Cut</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1130" />
<location filename="../novelwriter/gui/doceditor.py" line="1131" />
<source>Copy</source>
<translation>Copy</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1133" />
<location filename="../novelwriter/gui/doceditor.py" line="1134" />
<source>Paste</source>
<translation>Paste</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1138" />
<location filename="../novelwriter/gui/doceditor.py" line="1139" />
<source>Select All</source>
<translation>Select All</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1140" />
<location filename="../novelwriter/gui/doceditor.py" line="1141" />
<source>Select Word</source>
<translation>Select Word</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1144" />
<location filename="../novelwriter/gui/doceditor.py" line="1145" />
<source>Select Paragraph</source>
<translation>Select Paragraph</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1160" />
<location filename="../novelwriter/gui/doceditor.py" line="1161" />
<source>Spelling Suggestion(s)</source>
<translation>Spelling Suggestion(s)</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1167" />
<location filename="../novelwriter/gui/doceditor.py" line="1168" />
<source>No Suggestions</source>
<translation>No Suggestions</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1170" />
<location filename="../novelwriter/gui/doceditor.py" line="1171" />
<source>Add Word to Dictionary</source>
<translation>Add Word to Dictionary</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1596" />
<location filename="../novelwriter/gui/doceditor.py" line="1597" />
<source>Please select some text before calling replace quotes.</source>
<translation>Please select some text before calling replace quotes.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1903" />
<location filename="../novelwriter/gui/doceditor.py" line="1904" />
<source>Do you want to create a new project note for the tag '{0}'?</source>
<translation>Do you want to create a new project note for the tag '{0}'?</translation>
</message>
@@ -1104,52 +1104,52 @@
<context>
<name>GuiDocToolBar</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2270" />
<location filename="../novelwriter/gui/doceditor.py" line="2271" />
<source>Markdown Bold</source>
<translation>Markdown Bold</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2276" />
<location filename="../novelwriter/gui/doceditor.py" line="2277" />
<source>Markdown Italic</source>
<translation>Markdown Italic</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2282" />
<location filename="../novelwriter/gui/doceditor.py" line="2283" />
<source>Markdown Strikethrough</source>
<translation>Markdown Strikethrough</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2288" />
<location filename="../novelwriter/gui/doceditor.py" line="2289" />
<source>Shortcode Bold</source>
<translation>Shortcode Bold</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2294" />
<location filename="../novelwriter/gui/doceditor.py" line="2295" />
<source>Shortcode Italic</source>
<translation>Shortcode Italic</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2300" />
<location filename="../novelwriter/gui/doceditor.py" line="2301" />
<source>Shortcode Strikethrough</source>
<translation>Shortcode Strikethrough</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2306" />
<location filename="../novelwriter/gui/doceditor.py" line="2307" />
<source>Shortcode Underline</source>
<translation>Shortcode Underline</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2312" />
<location filename="../novelwriter/gui/doceditor.py" line="2313" />
<source>Shortcode Highlight</source>
<translation>Shortcode Highlight</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2318" />
<location filename="../novelwriter/gui/doceditor.py" line="2319" />
<source>Shortcode Superscript</source>
<translation>Shortcode Superscript</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2324" />
<location filename="../novelwriter/gui/doceditor.py" line="2325" />
<source>Shortcode Subscript</source>
<translation>Shortcode Subscript</translation>
</message>
@@ -2198,57 +2198,57 @@
<context>
<name>GuiManuscriptBuild</name>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="72" />
<location filename="../novelwriter/tools/manusbuild.py" line="70" />
<source>Build Manuscript</source>
<translation>Build Manuscript</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="93" />
<location filename="../novelwriter/tools/manusbuild.py" line="91" />
<source>Output Format</source>
<translation>Output Format</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="119" />
<location filename="../novelwriter/tools/manusbuild.py" line="117" />
<source>Table of Contents</source>
<translation>Table of Contents</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="147" />
<location filename="../novelwriter/tools/manusbuild.py" line="145" />
<source>Path</source>
<translation>Path</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="157" />
<location filename="../novelwriter/tools/manusbuild.py" line="155" />
<source>File Name</source>
<translation>File Name</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="160" />
<location filename="../novelwriter/tools/manusbuild.py" line="158" />
<source>Reset file name to default</source>
<translation>Reset file name to default</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<location filename="../novelwriter/tools/manusbuild.py" line="182" />
<source>Open Folder</source>
<translation>Open Folder</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="186" />
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<source>&amp;Build</source>
<translation>&amp;Build</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="281" />
<location filename="../novelwriter/tools/manusbuild.py" line="279" />
<source>Select Folder</source>
<translation>Select Folder</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="319" />
<location filename="../novelwriter/tools/manusbuild.py" line="317" />
<source>Output folder does not exist.</source>
<translation>Output folder does not exist.</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="327" />
<location filename="../novelwriter/tools/manusbuild.py" line="325" />
<source>The file already exists. Do you want to overwrite it?</source>
<translation>The file already exists. Do you want to overwrite it?</translation>
</message>
@@ -2415,7 +2415,7 @@
<context>
<name>GuiOutlineToolBar</name>
<message>
<location filename="../novelwriter/gui/outline.py" line="223" />
<location filename="../novelwriter/gui/outline.py" line="222" />
<source>Outline of</source>
<translation>Outline of</translation>
</message>
@@ -3860,79 +3860,20 @@
<translation>Restore Defaults</translation>
</message>
</context>
<context>
<name>QWizard</name>
<message>
<location filename="qtbase.py" line="54" />
<source>Go Back</source>
<translation>Go Back</translation>
</message>
<message>
<location filename="qtbase.py" line="55" />
<source>&lt; &amp;Back</source>
<translation>&lt; &amp;Back</translation>
</message>
<message>
<location filename="qtbase.py" line="56" />
<source>Continue</source>
<translation>Continue</translation>
</message>
<message>
<location filename="qtbase.py" line="57" />
<source>&amp;Next</source>
<translation>&amp;Next</translation>
</message>
<message>
<location filename="qtbase.py" line="58" />
<source>&amp;Next &gt;</source>
<translation>&amp;Next &gt;</translation>
</message>
<message>
<location filename="qtbase.py" line="59" />
<source>Commit</source>
<translation>Commit</translation>
</message>
<message>
<location filename="qtbase.py" line="60" />
<source>Done</source>
<translation>Done</translation>
</message>
<message>
<location filename="qtbase.py" line="61" />
<source>&amp;Finish</source>
<translation>&amp;Finish</translation>
</message>
<message>
<location filename="qtbase.py" line="63" />
<location filename="qtbase.py" line="62" />
<source>Cancel</source>
<translation>Cancel</translation>
</message>
<message>
<location filename="qtbase.py" line="64" />
<source>Help</source>
<translation>Help</translation>
</message>
<message>
<location filename="qtbase.py" line="65" />
<source>&amp;Help</source>
<translation>&amp;Help</translation>
</message>
</context>
<context>
<name>SharedData</name>
<message>
<location filename="../novelwriter/shared.py" line="249" />
<location filename="../novelwriter/shared.py" line="250" />
<source>novelWriter Project File or Zip File</source>
<translation>novelWriter Project File or Zip File</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="250" />
<location filename="../novelwriter/shared.py" line="251" />
<source>novelWriter Project File</source>
<translation>novelWriter Project File</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="254" />
<location filename="../novelwriter/shared.py" line="255" />
<source>Open Project</source>
<translation>Open Project</translation>
</message>
@@ -4041,32 +3982,32 @@
<context>
<name>_DetailsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Setting</source>
<translation>Setting</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Value</source>
<translation>Value</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="589" />
<location filename="../novelwriter/tools/manuscript.py" line="600" />
<source>Name</source>
<translation>Name</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="595" />
<location filename="../novelwriter/tools/manuscript.py" line="606" />
<source>Selection</source>
<translation>Selection</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="611" />
<location filename="../novelwriter/tools/manuscript.py" line="622" />
<source>Title</source>
<translation>Title</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="612" />
<location filename="../novelwriter/tools/manuscript.py" line="623" />
<source>Hidden</source>
<translation>Hidden</translation>
</message>
@@ -4112,22 +4053,22 @@
<context>
<name>_GuiAlert</name>
<message>
<location filename="../novelwriter/shared.py" line="410" />
<location filename="../novelwriter/shared.py" line="422" />
<source>Information</source>
<translation>Information</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="413" />
<location filename="../novelwriter/shared.py" line="425" />
<source>Warning</source>
<translation>Warning</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="416" />
<location filename="../novelwriter/shared.py" line="428" />
<source>Error</source>
<translation>Error</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="419" />
<location filename="../novelwriter/shared.py" line="431" />
<source>Question</source>
<translation>Question</translation>
</message>
@@ -4438,27 +4379,27 @@
<context>
<name>_PreviewWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="757" />
<location filename="../novelwriter/tools/manuscript.py" line="769" />
<source>Press the "Preview" button to generate ...</source>
<translation>Press the "Preview" button to generate ...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="855" />
<location filename="../novelwriter/tools/manuscript.py" line="867" />
<source>Processing ...</source>
<translation>Processing ...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="877" />
<location filename="../novelwriter/tools/manuscript.py" line="888" />
<source>Done</source>
<translation>Done</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="927" />
<location filename="../novelwriter/tools/manuscript.py" line="938" />
<source>Unknown</source>
<translation>Unknown</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="928" />
<location filename="../novelwriter/tools/manuscript.py" line="939" />
<source>Built</source>
<translation>Built</translation>
</message>
@@ -4557,59 +4498,59 @@
<context>
<name>_StatsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1090" />
<location filename="../novelwriter/tools/manuscript.py" line="1050" />
<location filename="../novelwriter/tools/manuscript.py" line="1102" />
<location filename="../novelwriter/tools/manuscript.py" line="1062" />
<source>Words</source>
<translation>Words</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1117" />
<location filename="../novelwriter/tools/manuscript.py" line="1053" />
<location filename="../novelwriter/tools/manuscript.py" line="1129" />
<location filename="../novelwriter/tools/manuscript.py" line="1065" />
<source>Characters</source>
<translation>Characters</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1091" />
<location filename="../novelwriter/tools/manuscript.py" line="1103" />
<source>Words in Headings</source>
<translation>Words in Headings</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1092" />
<location filename="../novelwriter/tools/manuscript.py" line="1104" />
<source>Words in Text</source>
<translation>Words in Text</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1094" />
<location filename="../novelwriter/tools/manuscript.py" line="1106" />
<source>Headings</source>
<translation>Headings</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1095" />
<location filename="../novelwriter/tools/manuscript.py" line="1107" />
<source>Paragraphs</source>
<translation>Paragraphs</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1118" />
<location filename="../novelwriter/tools/manuscript.py" line="1130" />
<source>Characters in Headings</source>
<translation>Characters in Headings</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1119" />
<location filename="../novelwriter/tools/manuscript.py" line="1131" />
<source>Characters in Text</source>
<translation>Characters in Text</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1120" />
<location filename="../novelwriter/tools/manuscript.py" line="1132" />
<source>Characters, No Spaces</source>
<translation>Characters, No Spaces</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1121" />
<location filename="../novelwriter/tools/manuscript.py" line="1133" />
<source>Characters in Headings, No Spaces</source>
<translation>Characters in Headings, No Spaces</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1122" />
<location filename="../novelwriter/tools/manuscript.py" line="1134" />
<source>Characters in Text, No Spaces</source>
<translation>Characters in Text, No Spaces</translation>
</message>

View File

@@ -791,22 +791,22 @@
<context>
<name>GuiDocEditFooter</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3007" />
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<source>Line: {0} ({1})</source>
<translation>Línea: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<source>Words: {0} ({1})</source>
<translation>Palabras: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<location filename="../novelwriter/gui/doceditor.py" line="3010" />
<source>Words: {0} selected</source>
<translation>Palabras: {0} seleccionadas</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3021" />
<location filename="../novelwriter/gui/doceditor.py" line="3022" />
<source>Status</source>
<translation>Estado</translation>
</message>
@@ -814,27 +814,27 @@
<context>
<name>GuiDocEditHeader</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2805" />
<location filename="../novelwriter/gui/doceditor.py" line="2806" />
<source>Toggle Tool Bar</source>
<translation>Alternar Barra de Herramientas</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2810" />
<location filename="../novelwriter/gui/doceditor.py" line="2811" />
<source>Outline</source>
<translation>Estructura</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2815" />
<location filename="../novelwriter/gui/doceditor.py" line="2816" />
<source>Search</source>
<translation>Buscar</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2820" />
<location filename="../novelwriter/gui/doceditor.py" line="2821" />
<source>Toggle Focus Mode</source>
<translation>Alternar el Modo Enfocado</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2825" />
<location filename="../novelwriter/gui/doceditor.py" line="2826" />
<source>Close</source>
<translation>Cerrar</translation>
</message>
@@ -842,62 +842,62 @@
<context>
<name>GuiDocEditSearch</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2413" />
<location filename="../novelwriter/gui/doceditor.py" line="2414" />
<source>Search for</source>
<translation>Buscar</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2418" />
<location filename="../novelwriter/gui/doceditor.py" line="2419" />
<source>Replace with</source>
<translation>Reemplazar con</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2426" />
<location filename="../novelwriter/gui/doceditor.py" line="2427" />
<source>Search</source>
<translation>Buscar</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2434" />
<location filename="../novelwriter/gui/doceditor.py" line="2435" />
<source>Case Sensitive</source>
<translation>Sensibilidad a Mayúsculas y Minúsculas</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2440" />
<location filename="../novelwriter/gui/doceditor.py" line="2441" />
<source>Whole Words Only</source>
<translation>Sólo Palabras Enteras</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2446" />
<location filename="../novelwriter/gui/doceditor.py" line="2447" />
<source>RegEx Mode</source>
<translation>Modo ExReg</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2452" />
<location filename="../novelwriter/gui/doceditor.py" line="2453" />
<source>Loop Search</source>
<translation>Reiniciar la Búsqueda</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2458" />
<location filename="../novelwriter/gui/doceditor.py" line="2459" />
<source>Search Next File</source>
<translation>Buscar en el Siguiente Archivo</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2466" />
<location filename="../novelwriter/gui/doceditor.py" line="2467" />
<source>Preserve Case</source>
<translation>Conservar Mayúsculas y Minúsculas</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2474" />
<location filename="../novelwriter/gui/doceditor.py" line="2475" />
<source>Close Search</source>
<translation>Cerrar la Búsqueda</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2485" />
<location filename="../novelwriter/gui/doceditor.py" line="2486" />
<source>Find in current document</source>
<translation>Buscar en el documento actual</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2489" />
<location filename="../novelwriter/gui/doceditor.py" line="2490" />
<source>Find and replace in current document</source>
<translation>Buscar y reemplazar en el documento actual</translation>
</message>
@@ -905,122 +905,122 @@
<context>
<name>GuiDocEditor</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="441" />
<location filename="../novelwriter/gui/doceditor.py" line="442" />
<source>Opened Document: {0}</source>
<translation>Se Abrió el Documento: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="484" />
<location filename="../novelwriter/gui/doceditor.py" line="485" />
<source>This document has been changed outside of novelWriter while it was open. Overwrite the file on disk?</source>
<translation>Este documento ha cambiado por fuera de novelWriter estando abierto. ¿Sobreescribir en el disco?</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="493" />
<location filename="../novelwriter/gui/doceditor.py" line="494" />
<source>Could not save document.</source>
<translation>No se puedo guardar el documento.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="518" />
<location filename="../novelwriter/gui/doceditor.py" line="519" />
<source>Saved Document: {0}</source>
<translation>Documento Guardado: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="687" />
<location filename="../novelwriter/gui/doceditor.py" line="688" />
<source>Spell checking requires the package PyEnchant. It does not appear to be installed.</source>
<translation>Para la corrección ortográfica se requiere del paquete PyEnchant. Parece que no se encuentra instalado.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="713" />
<location filename="../novelwriter/gui/doceditor.py" line="714" />
<source>Spell check complete</source>
<translation>Se completó la comprobación ortográfica</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="833" />
<location filename="../novelwriter/gui/doceditor.py" line="834" />
<source>Document Details</source>
<translation>Detalles del Documento</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="835" />
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<source>Created: {0}</source>
<translation>Creación: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<location filename="../novelwriter/gui/doceditor.py" line="837" />
<source>Updated: {0}</source>
<translation>Actualizado en: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="838" />
<location filename="../novelwriter/gui/doceditor.py" line="839" />
<source>File Location: {0}</source>
<translation>Ubicación del Archivo: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1112" />
<location filename="../novelwriter/gui/doceditor.py" line="1113" />
<source>Set as Document Name</source>
<translation>Elegir como Nombre del Documento</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1118" />
<location filename="../novelwriter/gui/doceditor.py" line="1119" />
<source>Follow Tag</source>
<translation>Continuar a Etiqueta</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1122" />
<location filename="../novelwriter/gui/doceditor.py" line="1123" />
<source>Create Note for Tag</source>
<translation>Crear Nota para la Etiqueta</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1128" />
<location filename="../novelwriter/gui/doceditor.py" line="1129" />
<source>Cut</source>
<translation>Cortar</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1130" />
<location filename="../novelwriter/gui/doceditor.py" line="1131" />
<source>Copy</source>
<translation>Copiar</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1133" />
<location filename="../novelwriter/gui/doceditor.py" line="1134" />
<source>Paste</source>
<translation>Pegar</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1138" />
<location filename="../novelwriter/gui/doceditor.py" line="1139" />
<source>Select All</source>
<translation>Seleccionar Todo</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1140" />
<location filename="../novelwriter/gui/doceditor.py" line="1141" />
<source>Select Word</source>
<translation>Seleccionar Palabra</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1144" />
<location filename="../novelwriter/gui/doceditor.py" line="1145" />
<source>Select Paragraph</source>
<translation>Seleccionar Párrafo</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1160" />
<location filename="../novelwriter/gui/doceditor.py" line="1161" />
<source>Spelling Suggestion(s)</source>
<translation>Sugerencia(s) de Ortografía</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1167" />
<location filename="../novelwriter/gui/doceditor.py" line="1168" />
<source>No Suggestions</source>
<translation>No Hay Sugerencias</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1170" />
<location filename="../novelwriter/gui/doceditor.py" line="1171" />
<source>Add Word to Dictionary</source>
<translation>Añadir Palabra al Diccionario</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1596" />
<location filename="../novelwriter/gui/doceditor.py" line="1597" />
<source>Please select some text before calling replace quotes.</source>
<translation>Por favor seleccione algo del texto antes de intentar reemplazar las comillas.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1903" />
<location filename="../novelwriter/gui/doceditor.py" line="1904" />
<source>Do you want to create a new project note for the tag '{0}'?</source>
<translation>¿Desea crear una nueva nota del proyecto para la etiqueta '{0}'?</translation>
</message>
@@ -1104,52 +1104,52 @@
<context>
<name>GuiDocToolBar</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2270" />
<location filename="../novelwriter/gui/doceditor.py" line="2271" />
<source>Markdown Bold</source>
<translation>Negrita (de Markdown)</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2276" />
<location filename="../novelwriter/gui/doceditor.py" line="2277" />
<source>Markdown Italic</source>
<translation>Cursiva (de Markdown)</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2282" />
<location filename="../novelwriter/gui/doceditor.py" line="2283" />
<source>Markdown Strikethrough</source>
<translation>Tachado (de Markdown)</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2288" />
<location filename="../novelwriter/gui/doceditor.py" line="2289" />
<source>Shortcode Bold</source>
<translation>Negrita (en código)</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2294" />
<location filename="../novelwriter/gui/doceditor.py" line="2295" />
<source>Shortcode Italic</source>
<translation>Cursiva (en código)</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2300" />
<location filename="../novelwriter/gui/doceditor.py" line="2301" />
<source>Shortcode Strikethrough</source>
<translation>Tachado (en código)</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2306" />
<location filename="../novelwriter/gui/doceditor.py" line="2307" />
<source>Shortcode Underline</source>
<translation>Subrayado (en código)</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2312" />
<location filename="../novelwriter/gui/doceditor.py" line="2313" />
<source>Shortcode Highlight</source>
<translation>Resaltado (en código)</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2318" />
<location filename="../novelwriter/gui/doceditor.py" line="2319" />
<source>Shortcode Superscript</source>
<translation>Superíndice (en código)</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2324" />
<location filename="../novelwriter/gui/doceditor.py" line="2325" />
<source>Shortcode Subscript</source>
<translation>Subíndice (en código)</translation>
</message>
@@ -2198,57 +2198,57 @@
<context>
<name>GuiManuscriptBuild</name>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="72" />
<location filename="../novelwriter/tools/manusbuild.py" line="70" />
<source>Build Manuscript</source>
<translation>Compilar el Manuscrito</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="93" />
<location filename="../novelwriter/tools/manusbuild.py" line="91" />
<source>Output Format</source>
<translation>Grabar al Siguiente Formato</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="119" />
<location filename="../novelwriter/tools/manusbuild.py" line="117" />
<source>Table of Contents</source>
<translation>Tabla de Contenidos</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="147" />
<location filename="../novelwriter/tools/manusbuild.py" line="145" />
<source>Path</source>
<translation>Ruta</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="157" />
<location filename="../novelwriter/tools/manusbuild.py" line="155" />
<source>File Name</source>
<translation>Nombre del Archivo</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="160" />
<location filename="../novelwriter/tools/manusbuild.py" line="158" />
<source>Reset file name to default</source>
<translation>Restablecer al Nombre de Archivo Por Defecto</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<location filename="../novelwriter/tools/manusbuild.py" line="182" />
<source>Open Folder</source>
<translation>Abrir la Carpeta</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="186" />
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<source>&amp;Build</source>
<translation>C&amp;ompilar</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="281" />
<location filename="../novelwriter/tools/manusbuild.py" line="279" />
<source>Select Folder</source>
<translation>Seleccionar una Carpeta</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="319" />
<location filename="../novelwriter/tools/manusbuild.py" line="317" />
<source>Output folder does not exist.</source>
<translation>La carpeta destino no existe en la ruta definida.</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="327" />
<location filename="../novelwriter/tools/manusbuild.py" line="325" />
<source>The file already exists. Do you want to overwrite it?</source>
<translation>El archivo ya existe. ¿Desea sobrescribirlo?</translation>
</message>
@@ -2415,7 +2415,7 @@
<context>
<name>GuiOutlineToolBar</name>
<message>
<location filename="../novelwriter/gui/outline.py" line="223" />
<location filename="../novelwriter/gui/outline.py" line="222" />
<source>Outline of</source>
<translation>Estructura de</translation>
</message>
@@ -3860,79 +3860,20 @@
<translation>Restaurar valores predeterminados</translation>
</message>
</context>
<context>
<name>QWizard</name>
<message>
<location filename="qtbase.py" line="54" />
<source>Go Back</source>
<translation>Volver</translation>
</message>
<message>
<location filename="qtbase.py" line="55" />
<source>&lt; &amp;Back</source>
<translation>&lt; &amp;Volver</translation>
</message>
<message>
<location filename="qtbase.py" line="56" />
<source>Continue</source>
<translation>Continuar</translation>
</message>
<message>
<location filename="qtbase.py" line="57" />
<source>&amp;Next</source>
<translation>&amp;Siguiente</translation>
</message>
<message>
<location filename="qtbase.py" line="58" />
<source>&amp;Next &gt;</source>
<translation>Seguir &gt;</translation>
</message>
<message>
<location filename="qtbase.py" line="59" />
<source>Commit</source>
<translation>Confirmar</translation>
</message>
<message>
<location filename="qtbase.py" line="60" />
<source>Done</source>
<translation>Listo</translation>
</message>
<message>
<location filename="qtbase.py" line="61" />
<source>&amp;Finish</source>
<translation>&amp;Finalizar</translation>
</message>
<message>
<location filename="qtbase.py" line="63" />
<location filename="qtbase.py" line="62" />
<source>Cancel</source>
<translation>Cancelar</translation>
</message>
<message>
<location filename="qtbase.py" line="64" />
<source>Help</source>
<translation>Ayuda</translation>
</message>
<message>
<location filename="qtbase.py" line="65" />
<source>&amp;Help</source>
<translation>&amp;Ayuda</translation>
</message>
</context>
<context>
<name>SharedData</name>
<message>
<location filename="../novelwriter/shared.py" line="249" />
<location filename="../novelwriter/shared.py" line="250" />
<source>novelWriter Project File or Zip File</source>
<translation>Archivo de proyecto de novelWriter o archivo Zip</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="250" />
<location filename="../novelwriter/shared.py" line="251" />
<source>novelWriter Project File</source>
<translation>Archivo de Proyecto de novelWriter</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="254" />
<location filename="../novelwriter/shared.py" line="255" />
<source>Open Project</source>
<translation>Abrir un Proyecto</translation>
</message>
@@ -4041,32 +3982,32 @@
<context>
<name>_DetailsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Setting</source>
<translation>Opciones</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Value</source>
<translation>Valores Definidos</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="589" />
<location filename="../novelwriter/tools/manuscript.py" line="600" />
<source>Name</source>
<translation>Nombre</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="595" />
<location filename="../novelwriter/tools/manuscript.py" line="606" />
<source>Selection</source>
<translation>Selecciones</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="611" />
<location filename="../novelwriter/tools/manuscript.py" line="622" />
<source>Title</source>
<translation>Título</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="612" />
<location filename="../novelwriter/tools/manuscript.py" line="623" />
<source>Hidden</source>
<translation>Oculto</translation>
</message>
@@ -4112,22 +4053,22 @@
<context>
<name>_GuiAlert</name>
<message>
<location filename="../novelwriter/shared.py" line="410" />
<location filename="../novelwriter/shared.py" line="422" />
<source>Information</source>
<translation>Información</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="413" />
<location filename="../novelwriter/shared.py" line="425" />
<source>Warning</source>
<translation>Advertencia</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="416" />
<location filename="../novelwriter/shared.py" line="428" />
<source>Error</source>
<translation>Error</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="419" />
<location filename="../novelwriter/shared.py" line="431" />
<source>Question</source>
<translation>Pregunta</translation>
</message>
@@ -4438,27 +4379,27 @@
<context>
<name>_PreviewWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="757" />
<location filename="../novelwriter/tools/manuscript.py" line="769" />
<source>Press the "Preview" button to generate ...</source>
<translation>Pulse el botón "Vista Previa" para así generarla...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="855" />
<location filename="../novelwriter/tools/manuscript.py" line="867" />
<source>Processing ...</source>
<translation>Procesando...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="877" />
<location filename="../novelwriter/tools/manuscript.py" line="888" />
<source>Done</source>
<translation>Hecho</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="927" />
<location filename="../novelwriter/tools/manuscript.py" line="938" />
<source>Unknown</source>
<translation>Desconocido</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="928" />
<location filename="../novelwriter/tools/manuscript.py" line="939" />
<source>Built</source>
<translation>Compilado</translation>
</message>
@@ -4557,59 +4498,59 @@
<context>
<name>_StatsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1090" />
<location filename="../novelwriter/tools/manuscript.py" line="1050" />
<location filename="../novelwriter/tools/manuscript.py" line="1102" />
<location filename="../novelwriter/tools/manuscript.py" line="1062" />
<source>Words</source>
<translation>Palabras</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1117" />
<location filename="../novelwriter/tools/manuscript.py" line="1053" />
<location filename="../novelwriter/tools/manuscript.py" line="1129" />
<location filename="../novelwriter/tools/manuscript.py" line="1065" />
<source>Characters</source>
<translation>Caracteres</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1091" />
<location filename="../novelwriter/tools/manuscript.py" line="1103" />
<source>Words in Headings</source>
<translation>Palabras en Titulos</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1092" />
<location filename="../novelwriter/tools/manuscript.py" line="1104" />
<source>Words in Text</source>
<translation>Palabras en el Texto</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1094" />
<location filename="../novelwriter/tools/manuscript.py" line="1106" />
<source>Headings</source>
<translation>Títulos</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1095" />
<location filename="../novelwriter/tools/manuscript.py" line="1107" />
<source>Paragraphs</source>
<translation>Párrafos</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1118" />
<location filename="../novelwriter/tools/manuscript.py" line="1130" />
<source>Characters in Headings</source>
<translation>Caracteres en los Títulos</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1119" />
<location filename="../novelwriter/tools/manuscript.py" line="1131" />
<source>Characters in Text</source>
<translation>Caracteres en el Texto</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1120" />
<location filename="../novelwriter/tools/manuscript.py" line="1132" />
<source>Characters, No Spaces</source>
<translation>Caracteres, Sin Espacios</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1121" />
<location filename="../novelwriter/tools/manuscript.py" line="1133" />
<source>Characters in Headings, No Spaces</source>
<translation>Caracteres en los Títulos, Sin Espacios</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1122" />
<location filename="../novelwriter/tools/manuscript.py" line="1134" />
<source>Characters in Text, No Spaces</source>
<translation>Caracteres en el Texto, Sin Espacios</translation>
</message>

View File

@@ -791,22 +791,22 @@
<context>
<name>GuiDocEditFooter</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3007" />
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<source>Line: {0} ({1})</source>
<translation>Ligne : {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<source>Words: {0} ({1})</source>
<translation>Mots : {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<location filename="../novelwriter/gui/doceditor.py" line="3010" />
<source>Words: {0} selected</source>
<translation>Mots sélectionnés : {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3021" />
<location filename="../novelwriter/gui/doceditor.py" line="3022" />
<source>Status</source>
<translation>État</translation>
</message>
@@ -814,27 +814,27 @@
<context>
<name>GuiDocEditHeader</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2805" />
<location filename="../novelwriter/gui/doceditor.py" line="2806" />
<source>Toggle Tool Bar</source>
<translation>Afficher/Masquer la barre d'outils</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2810" />
<location filename="../novelwriter/gui/doceditor.py" line="2811" />
<source>Outline</source>
<translation>Plan</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2815" />
<location filename="../novelwriter/gui/doceditor.py" line="2816" />
<source>Search</source>
<translation>Chercher</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2820" />
<location filename="../novelwriter/gui/doceditor.py" line="2821" />
<source>Toggle Focus Mode</source>
<translation>Basculer le mode focus</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2825" />
<location filename="../novelwriter/gui/doceditor.py" line="2826" />
<source>Close</source>
<translation>Fermer</translation>
</message>
@@ -842,62 +842,62 @@
<context>
<name>GuiDocEditSearch</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2413" />
<location filename="../novelwriter/gui/doceditor.py" line="2414" />
<source>Search for</source>
<translation>Rechercher</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2418" />
<location filename="../novelwriter/gui/doceditor.py" line="2419" />
<source>Replace with</source>
<translation>Remplacer par</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2426" />
<location filename="../novelwriter/gui/doceditor.py" line="2427" />
<source>Search</source>
<translation>Chercher</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2434" />
<location filename="../novelwriter/gui/doceditor.py" line="2435" />
<source>Case Sensitive</source>
<translation>Sensible à la casse</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2440" />
<location filename="../novelwriter/gui/doceditor.py" line="2441" />
<source>Whole Words Only</source>
<translation>Mots entiers uniquement</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2446" />
<location filename="../novelwriter/gui/doceditor.py" line="2447" />
<source>RegEx Mode</source>
<translation>Expressions régulières</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2452" />
<location filename="../novelwriter/gui/doceditor.py" line="2453" />
<source>Loop Search</source>
<translation>Recherche en boucle</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2458" />
<location filename="../novelwriter/gui/doceditor.py" line="2459" />
<source>Search Next File</source>
<translation>Chercher dans le fichier suivant</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2466" />
<location filename="../novelwriter/gui/doceditor.py" line="2467" />
<source>Preserve Case</source>
<translation>Conserver la casse</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2474" />
<location filename="../novelwriter/gui/doceditor.py" line="2475" />
<source>Close Search</source>
<translation>Terminer la recherche</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2485" />
<location filename="../novelwriter/gui/doceditor.py" line="2486" />
<source>Find in current document</source>
<translation>Chercher dans le document actuel</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2489" />
<location filename="../novelwriter/gui/doceditor.py" line="2490" />
<source>Find and replace in current document</source>
<translation>Chercher et remplacer dans le document actuel</translation>
</message>
@@ -905,122 +905,122 @@
<context>
<name>GuiDocEditor</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="441" />
<location filename="../novelwriter/gui/doceditor.py" line="442" />
<source>Opened Document: {0}</source>
<translation>Document ouvert : {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="484" />
<location filename="../novelwriter/gui/doceditor.py" line="485" />
<source>This document has been changed outside of novelWriter while it was open. Overwrite the file on disk?</source>
<translation>Ce document a été modifié en dehors de novelWriter pendant qu'il était ouvert. Écraser le fichier sur le disque ?</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="493" />
<location filename="../novelwriter/gui/doceditor.py" line="494" />
<source>Could not save document.</source>
<translation>Impossible d'enregistrer le document.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="518" />
<location filename="../novelwriter/gui/doceditor.py" line="519" />
<source>Saved Document: {0}</source>
<translation>Document enregistré : {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="687" />
<location filename="../novelwriter/gui/doceditor.py" line="688" />
<source>Spell checking requires the package PyEnchant. It does not appear to be installed.</source>
<translation>La vérification orthographique nécessite PyEnchant qui ne semble pas installé ici.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="713" />
<location filename="../novelwriter/gui/doceditor.py" line="714" />
<source>Spell check complete</source>
<translation>La vérification orthographique est terminée</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="833" />
<location filename="../novelwriter/gui/doceditor.py" line="834" />
<source>Document Details</source>
<translation>Détails du document</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="835" />
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<source>Created: {0}</source>
<translation>Créé : {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<location filename="../novelwriter/gui/doceditor.py" line="837" />
<source>Updated: {0}</source>
<translation>Mis à jour : {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="838" />
<location filename="../novelwriter/gui/doceditor.py" line="839" />
<source>File Location: {0}</source>
<translation>Emplacement du fichier : {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1112" />
<location filename="../novelwriter/gui/doceditor.py" line="1113" />
<source>Set as Document Name</source>
<translation>Définir comme nom du document</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1118" />
<location filename="../novelwriter/gui/doceditor.py" line="1119" />
<source>Follow Tag</source>
<translation>Suivre cette étiquette</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1122" />
<location filename="../novelwriter/gui/doceditor.py" line="1123" />
<source>Create Note for Tag</source>
<translation>Créer une note pour l'étiquette</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1128" />
<location filename="../novelwriter/gui/doceditor.py" line="1129" />
<source>Cut</source>
<translation>Couper</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1130" />
<location filename="../novelwriter/gui/doceditor.py" line="1131" />
<source>Copy</source>
<translation>Copier</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1133" />
<location filename="../novelwriter/gui/doceditor.py" line="1134" />
<source>Paste</source>
<translation>Coller</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1138" />
<location filename="../novelwriter/gui/doceditor.py" line="1139" />
<source>Select All</source>
<translation>Sélectionner tout</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1140" />
<location filename="../novelwriter/gui/doceditor.py" line="1141" />
<source>Select Word</source>
<translation>Sélectionner le mot</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1144" />
<location filename="../novelwriter/gui/doceditor.py" line="1145" />
<source>Select Paragraph</source>
<translation>Sélectionner le paragraphe</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1160" />
<location filename="../novelwriter/gui/doceditor.py" line="1161" />
<source>Spelling Suggestion(s)</source>
<translation>Orthographe suggérée</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1167" />
<location filename="../novelwriter/gui/doceditor.py" line="1168" />
<source>No Suggestions</source>
<translation>Pas de suggestion</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1170" />
<location filename="../novelwriter/gui/doceditor.py" line="1171" />
<source>Add Word to Dictionary</source>
<translation>Ajouter ce mot au dictionnaire</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1596" />
<location filename="../novelwriter/gui/doceditor.py" line="1597" />
<source>Please select some text before calling replace quotes.</source>
<translation>Veuillez sélectionner du texte avant de demander le remplacement des guillemets.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1903" />
<location filename="../novelwriter/gui/doceditor.py" line="1904" />
<source>Do you want to create a new project note for the tag '{0}'?</source>
<translation>Voulez-vous créer une nouvelle note de projet pour l'étiquette '{0}' ?</translation>
</message>
@@ -1104,52 +1104,52 @@
<context>
<name>GuiDocToolBar</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2270" />
<location filename="../novelwriter/gui/doceditor.py" line="2271" />
<source>Markdown Bold</source>
<translation>Markdown gras</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2276" />
<location filename="../novelwriter/gui/doceditor.py" line="2277" />
<source>Markdown Italic</source>
<translation>Markdown italique</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2282" />
<location filename="../novelwriter/gui/doceditor.py" line="2283" />
<source>Markdown Strikethrough</source>
<translation>Markdown barré</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2288" />
<location filename="../novelwriter/gui/doceditor.py" line="2289" />
<source>Shortcode Bold</source>
<translation>Code court gras</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2294" />
<location filename="../novelwriter/gui/doceditor.py" line="2295" />
<source>Shortcode Italic</source>
<translation>Code court italique</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2300" />
<location filename="../novelwriter/gui/doceditor.py" line="2301" />
<source>Shortcode Strikethrough</source>
<translation>Code court barré</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2306" />
<location filename="../novelwriter/gui/doceditor.py" line="2307" />
<source>Shortcode Underline</source>
<translation>Code court souligné</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2312" />
<location filename="../novelwriter/gui/doceditor.py" line="2313" />
<source>Shortcode Highlight</source>
<translation>Surligner les codes courts</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2318" />
<location filename="../novelwriter/gui/doceditor.py" line="2319" />
<source>Shortcode Superscript</source>
<translation>Code court exposant</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2324" />
<location filename="../novelwriter/gui/doceditor.py" line="2325" />
<source>Shortcode Subscript</source>
<translation>Code court indice</translation>
</message>
@@ -2198,57 +2198,57 @@
<context>
<name>GuiManuscriptBuild</name>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="72" />
<location filename="../novelwriter/tools/manusbuild.py" line="70" />
<source>Build Manuscript</source>
<translation>Compiler le manuscrit</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="93" />
<location filename="../novelwriter/tools/manusbuild.py" line="91" />
<source>Output Format</source>
<translation>Format de sortie</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="119" />
<location filename="../novelwriter/tools/manusbuild.py" line="117" />
<source>Table of Contents</source>
<translation>Table des matières</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="147" />
<location filename="../novelwriter/tools/manusbuild.py" line="145" />
<source>Path</source>
<translation>Emplacement</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="157" />
<location filename="../novelwriter/tools/manusbuild.py" line="155" />
<source>File Name</source>
<translation>Nom du fichier</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="160" />
<location filename="../novelwriter/tools/manusbuild.py" line="158" />
<source>Reset file name to default</source>
<translation>Rétablir le nom de fichier par défaut</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<location filename="../novelwriter/tools/manusbuild.py" line="182" />
<source>Open Folder</source>
<translation>Ouvrir le dossier</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="186" />
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<source>&amp;Build</source>
<translation>&amp;Compiler</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="281" />
<location filename="../novelwriter/tools/manusbuild.py" line="279" />
<source>Select Folder</source>
<translation>Sélectionner un dossier</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="319" />
<location filename="../novelwriter/tools/manusbuild.py" line="317" />
<source>Output folder does not exist.</source>
<translation>Le dossier de sortie n'existe pas.</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="327" />
<location filename="../novelwriter/tools/manusbuild.py" line="325" />
<source>The file already exists. Do you want to overwrite it?</source>
<translation>Ce fichier existe déjà. Voulez-vous l'écraser ?</translation>
</message>
@@ -2415,7 +2415,7 @@
<context>
<name>GuiOutlineToolBar</name>
<message>
<location filename="../novelwriter/gui/outline.py" line="223" />
<location filename="../novelwriter/gui/outline.py" line="222" />
<source>Outline of</source>
<translation>Plan de</translation>
</message>
@@ -3860,79 +3860,20 @@
<translation>Rétablir les valeurs par défaut</translation>
</message>
</context>
<context>
<name>QWizard</name>
<message>
<location filename="qtbase.py" line="54" />
<source>Go Back</source>
<translation>Revenir en arrière</translation>
</message>
<message>
<location filename="qtbase.py" line="55" />
<source>&lt; &amp;Back</source>
<translation>&lt; Retour</translation>
</message>
<message>
<location filename="qtbase.py" line="56" />
<source>Continue</source>
<translation>Continuer</translation>
</message>
<message>
<location filename="qtbase.py" line="57" />
<source>&amp;Next</source>
<translation>&amp;Suivant</translation>
</message>
<message>
<location filename="qtbase.py" line="58" />
<source>&amp;Next &gt;</source>
<translation>&amp;Suivant &gt;</translation>
</message>
<message>
<location filename="qtbase.py" line="59" />
<source>Commit</source>
<translation>Appliquer</translation>
</message>
<message>
<location filename="qtbase.py" line="60" />
<source>Done</source>
<translation>Terminé</translation>
</message>
<message>
<location filename="qtbase.py" line="61" />
<source>&amp;Finish</source>
<translation>&amp;Terminer</translation>
</message>
<message>
<location filename="qtbase.py" line="63" />
<location filename="qtbase.py" line="62" />
<source>Cancel</source>
<translation>Annuler</translation>
</message>
<message>
<location filename="qtbase.py" line="64" />
<source>Help</source>
<translation>Aide</translation>
</message>
<message>
<location filename="qtbase.py" line="65" />
<source>&amp;Help</source>
<translation>&amp;Aide</translation>
</message>
</context>
<context>
<name>SharedData</name>
<message>
<location filename="../novelwriter/shared.py" line="249" />
<location filename="../novelwriter/shared.py" line="250" />
<source>novelWriter Project File or Zip File</source>
<translation>Fichier de projet ou fichier Zip novelWriter</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="250" />
<location filename="../novelwriter/shared.py" line="251" />
<source>novelWriter Project File</source>
<translation>Fichier projet novelWriter</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="254" />
<location filename="../novelwriter/shared.py" line="255" />
<source>Open Project</source>
<translation>Ouvrir un projet</translation>
</message>
@@ -4041,32 +3982,32 @@
<context>
<name>_DetailsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Setting</source>
<translation>Paramètre</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Value</source>
<translation>Valeur</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="589" />
<location filename="../novelwriter/tools/manuscript.py" line="600" />
<source>Name</source>
<translation>Nom</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="595" />
<location filename="../novelwriter/tools/manuscript.py" line="606" />
<source>Selection</source>
<translation>Sélection</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="611" />
<location filename="../novelwriter/tools/manuscript.py" line="622" />
<source>Title</source>
<translation>Titre</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="612" />
<location filename="../novelwriter/tools/manuscript.py" line="623" />
<source>Hidden</source>
<translation>Caché</translation>
</message>
@@ -4112,22 +4053,22 @@
<context>
<name>_GuiAlert</name>
<message>
<location filename="../novelwriter/shared.py" line="410" />
<location filename="../novelwriter/shared.py" line="422" />
<source>Information</source>
<translation>Information</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="413" />
<location filename="../novelwriter/shared.py" line="425" />
<source>Warning</source>
<translation>Avertissement</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="416" />
<location filename="../novelwriter/shared.py" line="428" />
<source>Error</source>
<translation>Erreur</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="419" />
<location filename="../novelwriter/shared.py" line="431" />
<source>Question</source>
<translation>Question</translation>
</message>
@@ -4438,27 +4379,27 @@
<context>
<name>_PreviewWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="757" />
<location filename="../novelwriter/tools/manuscript.py" line="769" />
<source>Press the "Preview" button to generate ...</source>
<translation>Appuyez sur le bouton "Aperçu" pour générer...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="855" />
<location filename="../novelwriter/tools/manuscript.py" line="867" />
<source>Processing ...</source>
<translation>Traitement en cours...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="877" />
<location filename="../novelwriter/tools/manuscript.py" line="888" />
<source>Done</source>
<translation>Terminé</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="927" />
<location filename="../novelwriter/tools/manuscript.py" line="938" />
<source>Unknown</source>
<translation>Inconnu</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="928" />
<location filename="../novelwriter/tools/manuscript.py" line="939" />
<source>Built</source>
<translation>Compilé</translation>
</message>
@@ -4557,59 +4498,59 @@
<context>
<name>_StatsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1090" />
<location filename="../novelwriter/tools/manuscript.py" line="1050" />
<location filename="../novelwriter/tools/manuscript.py" line="1102" />
<location filename="../novelwriter/tools/manuscript.py" line="1062" />
<source>Words</source>
<translation>Mots</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1117" />
<location filename="../novelwriter/tools/manuscript.py" line="1053" />
<location filename="../novelwriter/tools/manuscript.py" line="1129" />
<location filename="../novelwriter/tools/manuscript.py" line="1065" />
<source>Characters</source>
<translation>Signes, espaces compris</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1091" />
<location filename="../novelwriter/tools/manuscript.py" line="1103" />
<source>Words in Headings</source>
<translation>Mots dans les titres</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1092" />
<location filename="../novelwriter/tools/manuscript.py" line="1104" />
<source>Words in Text</source>
<translation>Mots dans le texte</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1094" />
<location filename="../novelwriter/tools/manuscript.py" line="1106" />
<source>Headings</source>
<translation>Titres</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1095" />
<location filename="../novelwriter/tools/manuscript.py" line="1107" />
<source>Paragraphs</source>
<translation>Paragraphes</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1118" />
<location filename="../novelwriter/tools/manuscript.py" line="1130" />
<source>Characters in Headings</source>
<translation>Signes dans les titres</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1119" />
<location filename="../novelwriter/tools/manuscript.py" line="1131" />
<source>Characters in Text</source>
<translation>Signes dans le texte</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1120" />
<location filename="../novelwriter/tools/manuscript.py" line="1132" />
<source>Characters, No Spaces</source>
<translation>Signes, espaces exclus</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1121" />
<location filename="../novelwriter/tools/manuscript.py" line="1133" />
<source>Characters in Headings, No Spaces</source>
<translation>Signes dans les titres, espaces exclus</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1122" />
<location filename="../novelwriter/tools/manuscript.py" line="1134" />
<source>Characters in Text, No Spaces</source>
<translation>Signes dans le texte, espaces exclus</translation>
</message>

View File

@@ -791,22 +791,22 @@
<context>
<name>GuiDocEditFooter</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3007" />
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<source>Line: {0} ({1})</source>
<translation>Riga: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<source>Words: {0} ({1})</source>
<translation>Parole: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<location filename="../novelwriter/gui/doceditor.py" line="3010" />
<source>Words: {0} selected</source>
<translation>Parole: {0} selezionate</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3021" />
<location filename="../novelwriter/gui/doceditor.py" line="3022" />
<source>Status</source>
<translation>Stato</translation>
</message>
@@ -814,27 +814,27 @@
<context>
<name>GuiDocEditHeader</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2805" />
<location filename="../novelwriter/gui/doceditor.py" line="2806" />
<source>Toggle Tool Bar</source>
<translation>Attiva/disattiva la Barra degli strumenti</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2810" />
<location filename="../novelwriter/gui/doceditor.py" line="2811" />
<source>Outline</source>
<translation>Struttura</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2815" />
<location filename="../novelwriter/gui/doceditor.py" line="2816" />
<source>Search</source>
<translation>Cerca</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2820" />
<location filename="../novelwriter/gui/doceditor.py" line="2821" />
<source>Toggle Focus Mode</source>
<translation>Attiva/Disattiva modalità Focus</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2825" />
<location filename="../novelwriter/gui/doceditor.py" line="2826" />
<source>Close</source>
<translation>Chiudi</translation>
</message>
@@ -842,62 +842,62 @@
<context>
<name>GuiDocEditSearch</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2413" />
<location filename="../novelwriter/gui/doceditor.py" line="2414" />
<source>Search for</source>
<translation>Ricerca</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2418" />
<location filename="../novelwriter/gui/doceditor.py" line="2419" />
<source>Replace with</source>
<translation>Sostituisci con</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2426" />
<location filename="../novelwriter/gui/doceditor.py" line="2427" />
<source>Search</source>
<translation>Cerca</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2434" />
<location filename="../novelwriter/gui/doceditor.py" line="2435" />
<source>Case Sensitive</source>
<translation>Considera maiuscole/minuscole</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2440" />
<location filename="../novelwriter/gui/doceditor.py" line="2441" />
<source>Whole Words Only</source>
<translation>Solo parole intere</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2446" />
<location filename="../novelwriter/gui/doceditor.py" line="2447" />
<source>RegEx Mode</source>
<translation>Modalità RegEx</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2452" />
<location filename="../novelwriter/gui/doceditor.py" line="2453" />
<source>Loop Search</source>
<translation>Ricerca a ciclo continuo</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2458" />
<location filename="../novelwriter/gui/doceditor.py" line="2459" />
<source>Search Next File</source>
<translation>Cerca nel file successivo</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2466" />
<location filename="../novelwriter/gui/doceditor.py" line="2467" />
<source>Preserve Case</source>
<translation>Non considerare maiuscole/minuscole</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2474" />
<location filename="../novelwriter/gui/doceditor.py" line="2475" />
<source>Close Search</source>
<translation>Chiudi ricerca</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2485" />
<location filename="../novelwriter/gui/doceditor.py" line="2486" />
<source>Find in current document</source>
<translation>Trova nel documento corrente</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2489" />
<location filename="../novelwriter/gui/doceditor.py" line="2490" />
<source>Find and replace in current document</source>
<translation>Trova e sostituisci nel documento corrente</translation>
</message>
@@ -905,122 +905,122 @@
<context>
<name>GuiDocEditor</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="441" />
<location filename="../novelwriter/gui/doceditor.py" line="442" />
<source>Opened Document: {0}</source>
<translation>Documento aperto: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="484" />
<location filename="../novelwriter/gui/doceditor.py" line="485" />
<source>This document has been changed outside of novelWriter while it was open. Overwrite the file on disk?</source>
<translation>Questo documento è stato cambiato al di fuori di novelWriter mentre era aperto. Sovrascrivere il file su disco?</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="493" />
<location filename="../novelwriter/gui/doceditor.py" line="494" />
<source>Could not save document.</source>
<translation>Impossibile salvare il documento.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="518" />
<location filename="../novelwriter/gui/doceditor.py" line="519" />
<source>Saved Document: {0}</source>
<translation>Documento salvato: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="687" />
<location filename="../novelwriter/gui/doceditor.py" line="688" />
<source>Spell checking requires the package PyEnchant. It does not appear to be installed.</source>
<translation>Il controllo ortografico richiede il pacchetto PyEnchant. Non sembra essere installato.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="713" />
<location filename="../novelwriter/gui/doceditor.py" line="714" />
<source>Spell check complete</source>
<translation>Controllo ortografico completo</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="833" />
<location filename="../novelwriter/gui/doceditor.py" line="834" />
<source>Document Details</source>
<translation>Dettagli del documento</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="835" />
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<source>Created: {0}</source>
<translation>Creato: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<location filename="../novelwriter/gui/doceditor.py" line="837" />
<source>Updated: {0}</source>
<translation>Aggiornato: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="838" />
<location filename="../novelwriter/gui/doceditor.py" line="839" />
<source>File Location: {0}</source>
<translation>Posizione del file: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1112" />
<location filename="../novelwriter/gui/doceditor.py" line="1113" />
<source>Set as Document Name</source>
<translation>Imposta come nome del documento</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1118" />
<location filename="../novelwriter/gui/doceditor.py" line="1119" />
<source>Follow Tag</source>
<translation>Segui i Tag</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1122" />
<location filename="../novelwriter/gui/doceditor.py" line="1123" />
<source>Create Note for Tag</source>
<translation>Crea una nota per il Tag</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1128" />
<location filename="../novelwriter/gui/doceditor.py" line="1129" />
<source>Cut</source>
<translation>Taglia</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1130" />
<location filename="../novelwriter/gui/doceditor.py" line="1131" />
<source>Copy</source>
<translation>Copia</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1133" />
<location filename="../novelwriter/gui/doceditor.py" line="1134" />
<source>Paste</source>
<translation>Incolla</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1138" />
<location filename="../novelwriter/gui/doceditor.py" line="1139" />
<source>Select All</source>
<translation>Seleziona tutto</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1140" />
<location filename="../novelwriter/gui/doceditor.py" line="1141" />
<source>Select Word</source>
<translation>Seleziona parola</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1144" />
<location filename="../novelwriter/gui/doceditor.py" line="1145" />
<source>Select Paragraph</source>
<translation>Seleziona paragrafo</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1160" />
<location filename="../novelwriter/gui/doceditor.py" line="1161" />
<source>Spelling Suggestion(s)</source>
<translation>Suggerimento(i) ortografico(i)</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1167" />
<location filename="../novelwriter/gui/doceditor.py" line="1168" />
<source>No Suggestions</source>
<translation>Nessun suggerimento</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1170" />
<location filename="../novelwriter/gui/doceditor.py" line="1171" />
<source>Add Word to Dictionary</source>
<translation>Aggiungi parola al dizionario</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1596" />
<location filename="../novelwriter/gui/doceditor.py" line="1597" />
<source>Please select some text before calling replace quotes.</source>
<translation>Per favore seleziona del testo prima di chiedere il cambio di virgolette.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1903" />
<location filename="../novelwriter/gui/doceditor.py" line="1904" />
<source>Do you want to create a new project note for the tag '{0}'?</source>
<translation>Vuoi creare una nuova nota di progetto per il tag '{0}'?</translation>
</message>
@@ -1104,52 +1104,52 @@
<context>
<name>GuiDocToolBar</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2270" />
<location filename="../novelwriter/gui/doceditor.py" line="2271" />
<source>Markdown Bold</source>
<translation>Markdown grassetto</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2276" />
<location filename="../novelwriter/gui/doceditor.py" line="2277" />
<source>Markdown Italic</source>
<translation>Markdown corsivo</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2282" />
<location filename="../novelwriter/gui/doceditor.py" line="2283" />
<source>Markdown Strikethrough</source>
<translation>Markdown barrato</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2288" />
<location filename="../novelwriter/gui/doceditor.py" line="2289" />
<source>Shortcode Bold</source>
<translation>Shortcode grassetto</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2294" />
<location filename="../novelwriter/gui/doceditor.py" line="2295" />
<source>Shortcode Italic</source>
<translation>Shortcode corsivo</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2300" />
<location filename="../novelwriter/gui/doceditor.py" line="2301" />
<source>Shortcode Strikethrough</source>
<translation>Shortcode barrato</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2306" />
<location filename="../novelwriter/gui/doceditor.py" line="2307" />
<source>Shortcode Underline</source>
<translation>Shortcode sottolineato</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2312" />
<location filename="../novelwriter/gui/doceditor.py" line="2313" />
<source>Shortcode Highlight</source>
<translation>Evidenziazione</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2318" />
<location filename="../novelwriter/gui/doceditor.py" line="2319" />
<source>Shortcode Superscript</source>
<translation>Shortcode apice</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2324" />
<location filename="../novelwriter/gui/doceditor.py" line="2325" />
<source>Shortcode Subscript</source>
<translation>Shortcode pendice</translation>
</message>
@@ -2198,57 +2198,57 @@
<context>
<name>GuiManuscriptBuild</name>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="72" />
<location filename="../novelwriter/tools/manusbuild.py" line="70" />
<source>Build Manuscript</source>
<translation>Compila manoscritto</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="93" />
<location filename="../novelwriter/tools/manusbuild.py" line="91" />
<source>Output Format</source>
<translation>Formato di output</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="119" />
<location filename="../novelwriter/tools/manusbuild.py" line="117" />
<source>Table of Contents</source>
<translation>Tavola dei contenuti</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="147" />
<location filename="../novelwriter/tools/manusbuild.py" line="145" />
<source>Path</source>
<translation>Percorso</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="157" />
<location filename="../novelwriter/tools/manusbuild.py" line="155" />
<source>File Name</source>
<translation>Nome file</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="160" />
<location filename="../novelwriter/tools/manusbuild.py" line="158" />
<source>Reset file name to default</source>
<translation>Ripristina il nome del file predefinito</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<location filename="../novelwriter/tools/manusbuild.py" line="182" />
<source>Open Folder</source>
<translation>Apri cartella</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="186" />
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<source>&amp;Build</source>
<translation>&amp;Compila</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="281" />
<location filename="../novelwriter/tools/manusbuild.py" line="279" />
<source>Select Folder</source>
<translation>Seleziona cartella</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="319" />
<location filename="../novelwriter/tools/manusbuild.py" line="317" />
<source>Output folder does not exist.</source>
<translation>La cartella di output non esiste.</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="327" />
<location filename="../novelwriter/tools/manusbuild.py" line="325" />
<source>The file already exists. Do you want to overwrite it?</source>
<translation>Questo file esiste già. Vuoi sovrascriverlo?</translation>
</message>
@@ -2415,7 +2415,7 @@
<context>
<name>GuiOutlineToolBar</name>
<message>
<location filename="../novelwriter/gui/outline.py" line="223" />
<location filename="../novelwriter/gui/outline.py" line="222" />
<source>Outline of</source>
<translation>Struttura di</translation>
</message>
@@ -3860,79 +3860,20 @@
<translation>Ripristina predefiniti</translation>
</message>
</context>
<context>
<name>QWizard</name>
<message>
<location filename="qtbase.py" line="54" />
<source>Go Back</source>
<translation>Torna indietro</translation>
</message>
<message>
<location filename="qtbase.py" line="55" />
<source>&lt; &amp;Back</source>
<translation>&lt; &amp;Indietro</translation>
</message>
<message>
<location filename="qtbase.py" line="56" />
<source>Continue</source>
<translation>Continua</translation>
</message>
<message>
<location filename="qtbase.py" line="57" />
<source>&amp;Next</source>
<translation>&amp;Successivo</translation>
</message>
<message>
<location filename="qtbase.py" line="58" />
<source>&amp;Next &gt;</source>
<translation>&amp;Successivo &gt;</translation>
</message>
<message>
<location filename="qtbase.py" line="59" />
<source>Commit</source>
<translation>Esegui</translation>
</message>
<message>
<location filename="qtbase.py" line="60" />
<source>Done</source>
<translation>Fatto</translation>
</message>
<message>
<location filename="qtbase.py" line="61" />
<source>&amp;Finish</source>
<translation>&amp;Termina</translation>
</message>
<message>
<location filename="qtbase.py" line="63" />
<location filename="qtbase.py" line="62" />
<source>Cancel</source>
<translation>Cancella</translation>
</message>
<message>
<location filename="qtbase.py" line="64" />
<source>Help</source>
<translation>Aiuto</translation>
</message>
<message>
<location filename="qtbase.py" line="65" />
<source>&amp;Help</source>
<translation>&amp;Aiuto</translation>
</message>
</context>
<context>
<name>SharedData</name>
<message>
<location filename="../novelwriter/shared.py" line="249" />
<location filename="../novelwriter/shared.py" line="250" />
<source>novelWriter Project File or Zip File</source>
<translation>File di progetto di novelWriter o file Zip</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="250" />
<location filename="../novelwriter/shared.py" line="251" />
<source>novelWriter Project File</source>
<translation>File di progetto di novelWriter</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="254" />
<location filename="../novelwriter/shared.py" line="255" />
<source>Open Project</source>
<translation>Apri progetto</translation>
</message>
@@ -4041,32 +3982,32 @@
<context>
<name>_DetailsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Setting</source>
<translation>Impostazioni</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Value</source>
<translation>Valore</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="589" />
<location filename="../novelwriter/tools/manuscript.py" line="600" />
<source>Name</source>
<translation>Nome</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="595" />
<location filename="../novelwriter/tools/manuscript.py" line="606" />
<source>Selection</source>
<translation>Selezione</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="611" />
<location filename="../novelwriter/tools/manuscript.py" line="622" />
<source>Title</source>
<translation>Titolo</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="612" />
<location filename="../novelwriter/tools/manuscript.py" line="623" />
<source>Hidden</source>
<translation>Nascosto</translation>
</message>
@@ -4112,22 +4053,22 @@
<context>
<name>_GuiAlert</name>
<message>
<location filename="../novelwriter/shared.py" line="410" />
<location filename="../novelwriter/shared.py" line="422" />
<source>Information</source>
<translation>Informazioni</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="413" />
<location filename="../novelwriter/shared.py" line="425" />
<source>Warning</source>
<translation>Attenzione</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="416" />
<location filename="../novelwriter/shared.py" line="428" />
<source>Error</source>
<translation>Errore</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="419" />
<location filename="../novelwriter/shared.py" line="431" />
<source>Question</source>
<translation>Domanda</translation>
</message>
@@ -4438,27 +4379,27 @@
<context>
<name>_PreviewWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="757" />
<location filename="../novelwriter/tools/manuscript.py" line="769" />
<source>Press the "Preview" button to generate ...</source>
<translation>Premi il tasto "Anteprima" per compilarla ...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="855" />
<location filename="../novelwriter/tools/manuscript.py" line="867" />
<source>Processing ...</source>
<translation>In elaborazione ...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="877" />
<location filename="../novelwriter/tools/manuscript.py" line="888" />
<source>Done</source>
<translation>Fatto</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="927" />
<location filename="../novelwriter/tools/manuscript.py" line="938" />
<source>Unknown</source>
<translation>Sconosciuto</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="928" />
<location filename="../novelwriter/tools/manuscript.py" line="939" />
<source>Built</source>
<translation>Realizzata</translation>
</message>
@@ -4557,59 +4498,59 @@
<context>
<name>_StatsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1090" />
<location filename="../novelwriter/tools/manuscript.py" line="1050" />
<location filename="../novelwriter/tools/manuscript.py" line="1102" />
<location filename="../novelwriter/tools/manuscript.py" line="1062" />
<source>Words</source>
<translation>Parole</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1117" />
<location filename="../novelwriter/tools/manuscript.py" line="1053" />
<location filename="../novelwriter/tools/manuscript.py" line="1129" />
<location filename="../novelwriter/tools/manuscript.py" line="1065" />
<source>Characters</source>
<translation>Caratteri</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1091" />
<location filename="../novelwriter/tools/manuscript.py" line="1103" />
<source>Words in Headings</source>
<translation>Parole nelle intestazioni</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1092" />
<location filename="../novelwriter/tools/manuscript.py" line="1104" />
<source>Words in Text</source>
<translation>Parole nel testo</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1094" />
<location filename="../novelwriter/tools/manuscript.py" line="1106" />
<source>Headings</source>
<translation>Intestazioni</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1095" />
<location filename="../novelwriter/tools/manuscript.py" line="1107" />
<source>Paragraphs</source>
<translation>Paragrafi</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1118" />
<location filename="../novelwriter/tools/manuscript.py" line="1130" />
<source>Characters in Headings</source>
<translation>Caratteri nelle intestazioni</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1119" />
<location filename="../novelwriter/tools/manuscript.py" line="1131" />
<source>Characters in Text</source>
<translation>Caratteri nel testo</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1120" />
<location filename="../novelwriter/tools/manuscript.py" line="1132" />
<source>Characters, No Spaces</source>
<translation>Caratteri, esclusi gli spazi</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1121" />
<location filename="../novelwriter/tools/manuscript.py" line="1133" />
<source>Characters in Headings, No Spaces</source>
<translation>Caratteri nelle intestazioni, esclusi gli spazi</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1122" />
<location filename="../novelwriter/tools/manuscript.py" line="1134" />
<source>Characters in Text, No Spaces</source>
<translation>Caratteri nel testo, esclusi gli spazi</translation>
</message>

View File

@@ -791,22 +791,22 @@
<context>
<name>GuiDocEditFooter</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3007" />
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<source>Line: {0} ({1})</source>
<translation>行: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<source>Words: {0} ({1})</source>
<translation>単語: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<location filename="../novelwriter/gui/doceditor.py" line="3010" />
<source>Words: {0} selected</source>
<translation>単語: {0} 選択済み</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3021" />
<location filename="../novelwriter/gui/doceditor.py" line="3022" />
<source>Status</source>
<translation>ステータス</translation>
</message>
@@ -814,27 +814,27 @@
<context>
<name>GuiDocEditHeader</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2805" />
<location filename="../novelwriter/gui/doceditor.py" line="2806" />
<source>Toggle Tool Bar</source>
<translation>ツールバーの切り替え</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2810" />
<location filename="../novelwriter/gui/doceditor.py" line="2811" />
<source>Outline</source>
<translation>アウトライン</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2815" />
<location filename="../novelwriter/gui/doceditor.py" line="2816" />
<source>Search</source>
<translation>検索</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2820" />
<location filename="../novelwriter/gui/doceditor.py" line="2821" />
<source>Toggle Focus Mode</source>
<translation>フォーカスモードの切り替え</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2825" />
<location filename="../novelwriter/gui/doceditor.py" line="2826" />
<source>Close</source>
<translation>閉じる</translation>
</message>
@@ -842,62 +842,62 @@
<context>
<name>GuiDocEditSearch</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2413" />
<location filename="../novelwriter/gui/doceditor.py" line="2414" />
<source>Search for</source>
<translation>検索</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2418" />
<location filename="../novelwriter/gui/doceditor.py" line="2419" />
<source>Replace with</source>
<translation>置換候補</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2426" />
<location filename="../novelwriter/gui/doceditor.py" line="2427" />
<source>Search</source>
<translation>検索</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2434" />
<location filename="../novelwriter/gui/doceditor.py" line="2435" />
<source>Case Sensitive</source>
<translation>大文字と小文字を区別</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2440" />
<location filename="../novelwriter/gui/doceditor.py" line="2441" />
<source>Whole Words Only</source>
<translation>完全一致のみ</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2446" />
<location filename="../novelwriter/gui/doceditor.py" line="2447" />
<source>RegEx Mode</source>
<translation>正規表現モード</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2452" />
<location filename="../novelwriter/gui/doceditor.py" line="2453" />
<source>Loop Search</source>
<translation>ループ検索</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2458" />
<location filename="../novelwriter/gui/doceditor.py" line="2459" />
<source>Search Next File</source>
<translation>次のファイルを検索</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2466" />
<location filename="../novelwriter/gui/doceditor.py" line="2467" />
<source>Preserve Case</source>
<translation>大文字と小文字を保持</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2474" />
<location filename="../novelwriter/gui/doceditor.py" line="2475" />
<source>Close Search</source>
<translation>検索を閉じる</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2485" />
<location filename="../novelwriter/gui/doceditor.py" line="2486" />
<source>Find in current document</source>
<translation>現在のドキュメント内を検索</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2489" />
<location filename="../novelwriter/gui/doceditor.py" line="2490" />
<source>Find and replace in current document</source>
<translation>現在のドキュメント内を検索して置き換え</translation>
</message>
@@ -905,122 +905,122 @@
<context>
<name>GuiDocEditor</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="441" />
<location filename="../novelwriter/gui/doceditor.py" line="442" />
<source>Opened Document: {0}</source>
<translation>開かれたドキュメント: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="484" />
<location filename="../novelwriter/gui/doceditor.py" line="485" />
<source>This document has been changed outside of novelWriter while it was open. Overwrite the file on disk?</source>
<translation>このドキュメントは、開いている間にnovelWriter以外で変更されました。ディスクにファイルを上書きしますか?</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="493" />
<location filename="../novelwriter/gui/doceditor.py" line="494" />
<source>Could not save document.</source>
<translation>ドキュメントを保存できませんでした。</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="518" />
<location filename="../novelwriter/gui/doceditor.py" line="519" />
<source>Saved Document: {0}</source>
<translation>保存済みドキュメント: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="687" />
<location filename="../novelwriter/gui/doceditor.py" line="688" />
<source>Spell checking requires the package PyEnchant. It does not appear to be installed.</source>
<translation>スペルチェックにはPyEnchantパッケージが必要ですが、インストールされていないようです。</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="713" />
<location filename="../novelwriter/gui/doceditor.py" line="714" />
<source>Spell check complete</source>
<translation>スペルチェック完了</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="833" />
<location filename="../novelwriter/gui/doceditor.py" line="834" />
<source>Document Details</source>
<translation>ドキュメントの詳細</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="835" />
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<source>Created: {0}</source>
<translation>作成済み: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<location filename="../novelwriter/gui/doceditor.py" line="837" />
<source>Updated: {0}</source>
<translation>更新: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="838" />
<location filename="../novelwriter/gui/doceditor.py" line="839" />
<source>File Location: {0}</source>
<translation>ファイル場所: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1112" />
<location filename="../novelwriter/gui/doceditor.py" line="1113" />
<source>Set as Document Name</source>
<translation>ドキュメント名として設定</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1118" />
<location filename="../novelwriter/gui/doceditor.py" line="1119" />
<source>Follow Tag</source>
<translation>タグをフォロー</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1122" />
<location filename="../novelwriter/gui/doceditor.py" line="1123" />
<source>Create Note for Tag</source>
<translation>タグのメモを作成</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1128" />
<location filename="../novelwriter/gui/doceditor.py" line="1129" />
<source>Cut</source>
<translation>切り取り</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1130" />
<location filename="../novelwriter/gui/doceditor.py" line="1131" />
<source>Copy</source>
<translation>コピー</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1133" />
<location filename="../novelwriter/gui/doceditor.py" line="1134" />
<source>Paste</source>
<translation>貼り付け</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1138" />
<location filename="../novelwriter/gui/doceditor.py" line="1139" />
<source>Select All</source>
<translation>すべて選択</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1140" />
<location filename="../novelwriter/gui/doceditor.py" line="1141" />
<source>Select Word</source>
<translation>単語を選択</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1144" />
<location filename="../novelwriter/gui/doceditor.py" line="1145" />
<source>Select Paragraph</source>
<translation>段落を選択</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1160" />
<location filename="../novelwriter/gui/doceditor.py" line="1161" />
<source>Spelling Suggestion(s)</source>
<translation>スペルの提案</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1167" />
<location filename="../novelwriter/gui/doceditor.py" line="1168" />
<source>No Suggestions</source>
<translation>候補なし</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1170" />
<location filename="../novelwriter/gui/doceditor.py" line="1171" />
<source>Add Word to Dictionary</source>
<translation>単語を辞書に追加</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1596" />
<location filename="../novelwriter/gui/doceditor.py" line="1597" />
<source>Please select some text before calling replace quotes.</source>
<translation>置き換え引用符を呼び出す前にテキストを選択してください。</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1903" />
<location filename="../novelwriter/gui/doceditor.py" line="1904" />
<source>Do you want to create a new project note for the tag '{0}'?</source>
<translation>タグ '{0}' の新しいプロジェクトノートを作成しますか?</translation>
</message>
@@ -1104,52 +1104,52 @@
<context>
<name>GuiDocToolBar</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2270" />
<location filename="../novelwriter/gui/doceditor.py" line="2271" />
<source>Markdown Bold</source>
<translation>マークダウン 太字</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2276" />
<location filename="../novelwriter/gui/doceditor.py" line="2277" />
<source>Markdown Italic</source>
<translation>マークダウン 斜体</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2282" />
<location filename="../novelwriter/gui/doceditor.py" line="2283" />
<source>Markdown Strikethrough</source>
<translation>マークダウン 取り消し線</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2288" />
<location filename="../novelwriter/gui/doceditor.py" line="2289" />
<source>Shortcode Bold</source>
<translation>ショートコード 太字</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2294" />
<location filename="../novelwriter/gui/doceditor.py" line="2295" />
<source>Shortcode Italic</source>
<translation>ショートコード 斜体</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2300" />
<location filename="../novelwriter/gui/doceditor.py" line="2301" />
<source>Shortcode Strikethrough</source>
<translation>ショートコード 取り消し線</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2306" />
<location filename="../novelwriter/gui/doceditor.py" line="2307" />
<source>Shortcode Underline</source>
<translation>ショートコード 下線</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2312" />
<location filename="../novelwriter/gui/doceditor.py" line="2313" />
<source>Shortcode Highlight</source>
<translation>ショートコードハイライト</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2318" />
<location filename="../novelwriter/gui/doceditor.py" line="2319" />
<source>Shortcode Superscript</source>
<translation>ショートコード 上付き文字</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2324" />
<location filename="../novelwriter/gui/doceditor.py" line="2325" />
<source>Shortcode Subscript</source>
<translation>ショートコード 下付き文字</translation>
</message>
@@ -2198,57 +2198,57 @@
<context>
<name>GuiManuscriptBuild</name>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="72" />
<location filename="../novelwriter/tools/manusbuild.py" line="70" />
<source>Build Manuscript</source>
<translation>原稿をビルド</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="93" />
<location filename="../novelwriter/tools/manusbuild.py" line="91" />
<source>Output Format</source>
<translation>出力形式</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="119" />
<location filename="../novelwriter/tools/manusbuild.py" line="117" />
<source>Table of Contents</source>
<translation>目次</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="147" />
<location filename="../novelwriter/tools/manusbuild.py" line="145" />
<source>Path</source>
<translation>パス</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="157" />
<location filename="../novelwriter/tools/manusbuild.py" line="155" />
<source>File Name</source>
<translation>ファイル名</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="160" />
<location filename="../novelwriter/tools/manusbuild.py" line="158" />
<source>Reset file name to default</source>
<translation>ファイル名をデフォルトにリセット</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<location filename="../novelwriter/tools/manusbuild.py" line="182" />
<source>Open Folder</source>
<translation>フォルダーを開く</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="186" />
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<source>&amp;Build</source>
<translation>&amp;ビルド</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="281" />
<location filename="../novelwriter/tools/manusbuild.py" line="279" />
<source>Select Folder</source>
<translation>フォルダーを選択</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="319" />
<location filename="../novelwriter/tools/manusbuild.py" line="317" />
<source>Output folder does not exist.</source>
<translation>出力フォルダが存在しません。</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="327" />
<location filename="../novelwriter/tools/manusbuild.py" line="325" />
<source>The file already exists. Do you want to overwrite it?</source>
<translation>このファイルはすでに存在します。上書きしますか?</translation>
</message>
@@ -2415,7 +2415,7 @@
<context>
<name>GuiOutlineToolBar</name>
<message>
<location filename="../novelwriter/gui/outline.py" line="223" />
<location filename="../novelwriter/gui/outline.py" line="222" />
<source>Outline of</source>
<translation>アウトライン</translation>
</message>
@@ -3860,79 +3860,20 @@
<translation>既定値に戻す</translation>
</message>
</context>
<context>
<name>QWizard</name>
<message>
<location filename="qtbase.py" line="54" />
<source>Go Back</source>
<translation>戻る</translation>
</message>
<message>
<location filename="qtbase.py" line="55" />
<source>&lt; &amp;Back</source>
<translation>&lt; &amp;戻る</translation>
</message>
<message>
<location filename="qtbase.py" line="56" />
<source>Continue</source>
<translation>続行</translation>
</message>
<message>
<location filename="qtbase.py" line="57" />
<source>&amp;Next</source>
<translation>&amp;次へ</translation>
</message>
<message>
<location filename="qtbase.py" line="58" />
<source>&amp;Next &gt;</source>
<translation>&amp;次へ &gt;</translation>
</message>
<message>
<location filename="qtbase.py" line="59" />
<source>Commit</source>
<translation>コミット</translation>
</message>
<message>
<location filename="qtbase.py" line="60" />
<source>Done</source>
<translation>完了</translation>
</message>
<message>
<location filename="qtbase.py" line="61" />
<source>&amp;Finish</source>
<translation>&amp;終了</translation>
</message>
<message>
<location filename="qtbase.py" line="63" />
<location filename="qtbase.py" line="62" />
<source>Cancel</source>
<translation>キャンセル</translation>
</message>
<message>
<location filename="qtbase.py" line="64" />
<source>Help</source>
<translation>ヘルプ</translation>
</message>
<message>
<location filename="qtbase.py" line="65" />
<source>&amp;Help</source>
<translation>&amp;ヘルプ</translation>
</message>
</context>
<context>
<name>SharedData</name>
<message>
<location filename="../novelwriter/shared.py" line="249" />
<location filename="../novelwriter/shared.py" line="250" />
<source>novelWriter Project File or Zip File</source>
<translation>novelWriterプロジェクトファイルまたはZipファイル</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="250" />
<location filename="../novelwriter/shared.py" line="251" />
<source>novelWriter Project File</source>
<translation>novelWriterプロジェクトファイル</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="254" />
<location filename="../novelwriter/shared.py" line="255" />
<source>Open Project</source>
<translation>プロジェクトを開く</translation>
</message>
@@ -4041,32 +3982,32 @@
<context>
<name>_DetailsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Setting</source>
<translation>設定</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Value</source>
<translation>値</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="589" />
<location filename="../novelwriter/tools/manuscript.py" line="600" />
<source>Name</source>
<translation>名前</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="595" />
<location filename="../novelwriter/tools/manuscript.py" line="606" />
<source>Selection</source>
<translation>選択</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="611" />
<location filename="../novelwriter/tools/manuscript.py" line="622" />
<source>Title</source>
<translation>タイトル</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="612" />
<location filename="../novelwriter/tools/manuscript.py" line="623" />
<source>Hidden</source>
<translation>非表示</translation>
</message>
@@ -4112,22 +4053,22 @@
<context>
<name>_GuiAlert</name>
<message>
<location filename="../novelwriter/shared.py" line="410" />
<location filename="../novelwriter/shared.py" line="422" />
<source>Information</source>
<translation>情報</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="413" />
<location filename="../novelwriter/shared.py" line="425" />
<source>Warning</source>
<translation>警告</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="416" />
<location filename="../novelwriter/shared.py" line="428" />
<source>Error</source>
<translation>エラー</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="419" />
<location filename="../novelwriter/shared.py" line="431" />
<source>Question</source>
<translation>質問</translation>
</message>
@@ -4438,27 +4379,27 @@
<context>
<name>_PreviewWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="757" />
<location filename="../novelwriter/tools/manuscript.py" line="769" />
<source>Press the "Preview" button to generate ...</source>
<translation>"プレビュー" ボタンを押して生成します ...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="855" />
<location filename="../novelwriter/tools/manuscript.py" line="867" />
<source>Processing ...</source>
<translation>処理中…</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="877" />
<location filename="../novelwriter/tools/manuscript.py" line="888" />
<source>Done</source>
<translation>完了</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="927" />
<location filename="../novelwriter/tools/manuscript.py" line="938" />
<source>Unknown</source>
<translation>不明</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="928" />
<location filename="../novelwriter/tools/manuscript.py" line="939" />
<source>Built</source>
<translation>ビルドされた</translation>
</message>
@@ -4557,59 +4498,59 @@
<context>
<name>_StatsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1090" />
<location filename="../novelwriter/tools/manuscript.py" line="1050" />
<location filename="../novelwriter/tools/manuscript.py" line="1102" />
<location filename="../novelwriter/tools/manuscript.py" line="1062" />
<source>Words</source>
<translation>単語数</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1117" />
<location filename="../novelwriter/tools/manuscript.py" line="1053" />
<location filename="../novelwriter/tools/manuscript.py" line="1129" />
<location filename="../novelwriter/tools/manuscript.py" line="1065" />
<source>Characters</source>
<translation>文字数</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1091" />
<location filename="../novelwriter/tools/manuscript.py" line="1103" />
<source>Words in Headings</source>
<translation>見出し内の単語数</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1092" />
<location filename="../novelwriter/tools/manuscript.py" line="1104" />
<source>Words in Text</source>
<translation>テキスト内の単語数</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1094" />
<location filename="../novelwriter/tools/manuscript.py" line="1106" />
<source>Headings</source>
<translation>見出し</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1095" />
<location filename="../novelwriter/tools/manuscript.py" line="1107" />
<source>Paragraphs</source>
<translation>段落</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1118" />
<location filename="../novelwriter/tools/manuscript.py" line="1130" />
<source>Characters in Headings</source>
<translation>見出し内の文字数</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1119" />
<location filename="../novelwriter/tools/manuscript.py" line="1131" />
<source>Characters in Text</source>
<translation>テキスト内の文字数</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1120" />
<location filename="../novelwriter/tools/manuscript.py" line="1132" />
<source>Characters, No Spaces</source>
<translation>スペースなし文字数</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1121" />
<location filename="../novelwriter/tools/manuscript.py" line="1133" />
<source>Characters in Headings, No Spaces</source>
<translation>見出し内のスペースなし文字数</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1122" />
<location filename="../novelwriter/tools/manuscript.py" line="1134" />
<source>Characters in Text, No Spaces</source>
<translation>テキスト内のスペースなし文字数</translation>
</message>

View File

@@ -791,22 +791,22 @@
<context>
<name>GuiDocEditFooter</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3007" />
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<source>Line: {0} ({1})</source>
<translation>Linje: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<source>Words: {0} ({1})</source>
<translation>Ord: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<location filename="../novelwriter/gui/doceditor.py" line="3010" />
<source>Words: {0} selected</source>
<translation>Ord: {0} valgt</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3021" />
<location filename="../novelwriter/gui/doceditor.py" line="3022" />
<source>Status</source>
<translation>Status</translation>
</message>
@@ -814,27 +814,27 @@
<context>
<name>GuiDocEditHeader</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2805" />
<location filename="../novelwriter/gui/doceditor.py" line="2806" />
<source>Toggle Tool Bar</source>
<translation>Vis/skjul verktøylinje</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2810" />
<location filename="../novelwriter/gui/doceditor.py" line="2811" />
<source>Outline</source>
<translation>Disposisjon</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2815" />
<location filename="../novelwriter/gui/doceditor.py" line="2816" />
<source>Search</source>
<translation>Søk</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2820" />
<location filename="../novelwriter/gui/doceditor.py" line="2821" />
<source>Toggle Focus Mode</source>
<translation>Slå av/på "Fokus-modus"</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2825" />
<location filename="../novelwriter/gui/doceditor.py" line="2826" />
<source>Close</source>
<translation>Lukk</translation>
</message>
@@ -842,62 +842,62 @@
<context>
<name>GuiDocEditSearch</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2413" />
<location filename="../novelwriter/gui/doceditor.py" line="2414" />
<source>Search for</source>
<translation>Søketekst</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2418" />
<location filename="../novelwriter/gui/doceditor.py" line="2419" />
<source>Replace with</source>
<translation>Erstatt med</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2426" />
<location filename="../novelwriter/gui/doceditor.py" line="2427" />
<source>Search</source>
<translation>Søk</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2434" />
<location filename="../novelwriter/gui/doceditor.py" line="2435" />
<source>Case Sensitive</source>
<translation>Skill store/små bokstaver</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2440" />
<location filename="../novelwriter/gui/doceditor.py" line="2441" />
<source>Whole Words Only</source>
<translation>Kun hele ord</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2446" />
<location filename="../novelwriter/gui/doceditor.py" line="2447" />
<source>RegEx Mode</source>
<translation>RegEx-modus</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2452" />
<location filename="../novelwriter/gui/doceditor.py" line="2453" />
<source>Loop Search</source>
<translation>Søk rundt</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2458" />
<location filename="../novelwriter/gui/doceditor.py" line="2459" />
<source>Search Next File</source>
<translation>Søk i neste file</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2466" />
<location filename="../novelwriter/gui/doceditor.py" line="2467" />
<source>Preserve Case</source>
<translation>Behold store/små bokstaver</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2474" />
<location filename="../novelwriter/gui/doceditor.py" line="2475" />
<source>Close Search</source>
<translation>Lukk søk</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2485" />
<location filename="../novelwriter/gui/doceditor.py" line="2486" />
<source>Find in current document</source>
<translation>Søk i det åpne dokumentet</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2489" />
<location filename="../novelwriter/gui/doceditor.py" line="2490" />
<source>Find and replace in current document</source>
<translation>Søk og erstatt i det åpne dokumentet</translation>
</message>
@@ -905,122 +905,122 @@
<context>
<name>GuiDocEditor</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="441" />
<location filename="../novelwriter/gui/doceditor.py" line="442" />
<source>Opened Document: {0}</source>
<translation>Åpnet dokument: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="484" />
<location filename="../novelwriter/gui/doceditor.py" line="485" />
<source>This document has been changed outside of novelWriter while it was open. Overwrite the file on disk?</source>
<translation>Dette dokumentet er endret utenfor novelWriter mens det var åpent. Overskrive filen på disken?</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="493" />
<location filename="../novelwriter/gui/doceditor.py" line="494" />
<source>Could not save document.</source>
<translation>Kunne ikke lagre dokumentet.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="518" />
<location filename="../novelwriter/gui/doceditor.py" line="519" />
<source>Saved Document: {0}</source>
<translation>Lagret dokument: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="687" />
<location filename="../novelwriter/gui/doceditor.py" line="688" />
<source>Spell checking requires the package PyEnchant. It does not appear to be installed.</source>
<translation>Stavekontroll krever at pakken PyEnchant er installert. Det ser det ikke ut til at den er.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="713" />
<location filename="../novelwriter/gui/doceditor.py" line="714" />
<source>Spell check complete</source>
<translation>Stavekontrollen er ferdig</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="833" />
<location filename="../novelwriter/gui/doceditor.py" line="834" />
<source>Document Details</source>
<translation>Dokumentdetaljer</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="835" />
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<source>Created: {0}</source>
<translation>Opprettet: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<location filename="../novelwriter/gui/doceditor.py" line="837" />
<source>Updated: {0}</source>
<translation>Oppdatert: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="838" />
<location filename="../novelwriter/gui/doceditor.py" line="839" />
<source>File Location: {0}</source>
<translation>Filplassering: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1112" />
<location filename="../novelwriter/gui/doceditor.py" line="1113" />
<source>Set as Document Name</source>
<translation>Sett som dokumentnavn</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1118" />
<location filename="../novelwriter/gui/doceditor.py" line="1119" />
<source>Follow Tag</source>
<translation>Følg knagg</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1122" />
<location filename="../novelwriter/gui/doceditor.py" line="1123" />
<source>Create Note for Tag</source>
<translation>Opprett notat for knagg</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1128" />
<location filename="../novelwriter/gui/doceditor.py" line="1129" />
<source>Cut</source>
<translation>Klipp</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1130" />
<location filename="../novelwriter/gui/doceditor.py" line="1131" />
<source>Copy</source>
<translation>Kopier</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1133" />
<location filename="../novelwriter/gui/doceditor.py" line="1134" />
<source>Paste</source>
<translation>Lim inn</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1138" />
<location filename="../novelwriter/gui/doceditor.py" line="1139" />
<source>Select All</source>
<translation>Velg hele teksten</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1140" />
<location filename="../novelwriter/gui/doceditor.py" line="1141" />
<source>Select Word</source>
<translation>Velg hele ordet</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1144" />
<location filename="../novelwriter/gui/doceditor.py" line="1145" />
<source>Select Paragraph</source>
<translation>Velg hele avsnittet</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1160" />
<location filename="../novelwriter/gui/doceditor.py" line="1161" />
<source>Spelling Suggestion(s)</source>
<translation>Forslag fra stavekontrollen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1167" />
<location filename="../novelwriter/gui/doceditor.py" line="1168" />
<source>No Suggestions</source>
<translation>Ingen forslag</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1170" />
<location filename="../novelwriter/gui/doceditor.py" line="1171" />
<source>Add Word to Dictionary</source>
<translation>Legg til ord i ordbok</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1596" />
<location filename="../novelwriter/gui/doceditor.py" line="1597" />
<source>Please select some text before calling replace quotes.</source>
<translation>Venligst velg en del av teksten før du velger å erstatte sitattegn.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1903" />
<location filename="../novelwriter/gui/doceditor.py" line="1904" />
<source>Do you want to create a new project note for the tag '{0}'?</source>
<translation>Vil du opprette et nytt prosjektnotat for knaggen '{0}'?</translation>
</message>
@@ -1104,52 +1104,52 @@
<context>
<name>GuiDocToolBar</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2270" />
<location filename="../novelwriter/gui/doceditor.py" line="2271" />
<source>Markdown Bold</source>
<translation>Fet skrift med Markdown</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2276" />
<location filename="../novelwriter/gui/doceditor.py" line="2277" />
<source>Markdown Italic</source>
<translation>Kursiv med Markdown</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2282" />
<location filename="../novelwriter/gui/doceditor.py" line="2283" />
<source>Markdown Strikethrough</source>
<translation>Gjennomstrek med Markdown</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2288" />
<location filename="../novelwriter/gui/doceditor.py" line="2289" />
<source>Shortcode Bold</source>
<translation>Fet skrift med kortkode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2294" />
<location filename="../novelwriter/gui/doceditor.py" line="2295" />
<source>Shortcode Italic</source>
<translation>Kursiv med kortkode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2300" />
<location filename="../novelwriter/gui/doceditor.py" line="2301" />
<source>Shortcode Strikethrough</source>
<translation>Gjennomstrek med kortkode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2306" />
<location filename="../novelwriter/gui/doceditor.py" line="2307" />
<source>Shortcode Underline</source>
<translation>Understrek med kortkode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2312" />
<location filename="../novelwriter/gui/doceditor.py" line="2313" />
<source>Shortcode Highlight</source>
<translation>Tekstutheving med kortkode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2318" />
<location filename="../novelwriter/gui/doceditor.py" line="2319" />
<source>Shortcode Superscript</source>
<translation>Hevet skrift med kortkode</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2324" />
<location filename="../novelwriter/gui/doceditor.py" line="2325" />
<source>Shortcode Subscript</source>
<translation>Senket skrift med kortkode</translation>
</message>
@@ -2198,57 +2198,57 @@
<context>
<name>GuiManuscriptBuild</name>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="72" />
<location filename="../novelwriter/tools/manusbuild.py" line="70" />
<source>Build Manuscript</source>
<translation>Bygg manuskript</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="93" />
<location filename="../novelwriter/tools/manusbuild.py" line="91" />
<source>Output Format</source>
<translation>Dokumentformat</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="119" />
<location filename="../novelwriter/tools/manusbuild.py" line="117" />
<source>Table of Contents</source>
<translation>Innholdsfortegnelse</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="147" />
<location filename="../novelwriter/tools/manusbuild.py" line="145" />
<source>Path</source>
<translation>Filbane</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="157" />
<location filename="../novelwriter/tools/manusbuild.py" line="155" />
<source>File Name</source>
<translation>Filnavn</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="160" />
<location filename="../novelwriter/tools/manusbuild.py" line="158" />
<source>Reset file name to default</source>
<translation>Tilbakestill filnavn til standard</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<location filename="../novelwriter/tools/manusbuild.py" line="182" />
<source>Open Folder</source>
<translation>Åpne mappe</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="186" />
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<source>&amp;Build</source>
<translation>&amp;Bygg</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="281" />
<location filename="../novelwriter/tools/manusbuild.py" line="279" />
<source>Select Folder</source>
<translation>Velg mappe</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="319" />
<location filename="../novelwriter/tools/manusbuild.py" line="317" />
<source>Output folder does not exist.</source>
<translation>Mappen eksisterer ikke.</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="327" />
<location filename="../novelwriter/tools/manusbuild.py" line="325" />
<source>The file already exists. Do you want to overwrite it?</source>
<translation>Filen eksisterer allerede. Vil du overskrive den?</translation>
</message>
@@ -2415,7 +2415,7 @@
<context>
<name>GuiOutlineToolBar</name>
<message>
<location filename="../novelwriter/gui/outline.py" line="223" />
<location filename="../novelwriter/gui/outline.py" line="222" />
<source>Outline of</source>
<translation>Disposisjon for</translation>
</message>
@@ -3860,79 +3860,20 @@
<translation>Gjennopprett standard</translation>
</message>
</context>
<context>
<name>QWizard</name>
<message>
<location filename="qtbase.py" line="54" />
<source>Go Back</source>
<translation>Gå tilbake</translation>
</message>
<message>
<location filename="qtbase.py" line="55" />
<source>&lt; &amp;Back</source>
<translation>&lt; &amp;Tilbake</translation>
</message>
<message>
<location filename="qtbase.py" line="56" />
<source>Continue</source>
<translation>Fortsett</translation>
</message>
<message>
<location filename="qtbase.py" line="57" />
<source>&amp;Next</source>
<translation>&amp;Neste</translation>
</message>
<message>
<location filename="qtbase.py" line="58" />
<source>&amp;Next &gt;</source>
<translation>&amp;Neste &gt;</translation>
</message>
<message>
<location filename="qtbase.py" line="59" />
<source>Commit</source>
<translation>Gjennomfør</translation>
</message>
<message>
<location filename="qtbase.py" line="60" />
<source>Done</source>
<translation>Ferdig</translation>
</message>
<message>
<location filename="qtbase.py" line="61" />
<source>&amp;Finish</source>
<translation>&amp;Fullfør</translation>
</message>
<message>
<location filename="qtbase.py" line="63" />
<location filename="qtbase.py" line="62" />
<source>Cancel</source>
<translation>Avbryt</translation>
</message>
<message>
<location filename="qtbase.py" line="64" />
<source>Help</source>
<translation>Hjelp</translation>
</message>
<message>
<location filename="qtbase.py" line="65" />
<source>&amp;Help</source>
<translation>&amp;Hjelp</translation>
</message>
</context>
<context>
<name>SharedData</name>
<message>
<location filename="../novelwriter/shared.py" line="249" />
<location filename="../novelwriter/shared.py" line="250" />
<source>novelWriter Project File or Zip File</source>
<translation>novelWriter prosjektfil eller Zip-fil</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="250" />
<location filename="../novelwriter/shared.py" line="251" />
<source>novelWriter Project File</source>
<translation>novelWriter prosjektfil</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="254" />
<location filename="../novelwriter/shared.py" line="255" />
<source>Open Project</source>
<translation>Åpne prosjekt</translation>
</message>
@@ -4041,32 +3982,32 @@
<context>
<name>_DetailsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Setting</source>
<translation>Innstilling</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Value</source>
<translation>Verdi</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="589" />
<location filename="../novelwriter/tools/manuscript.py" line="600" />
<source>Name</source>
<translation>Navn</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="595" />
<location filename="../novelwriter/tools/manuscript.py" line="606" />
<source>Selection</source>
<translation>Utvalg</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="611" />
<location filename="../novelwriter/tools/manuscript.py" line="622" />
<source>Title</source>
<translation>Tittel</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="612" />
<location filename="../novelwriter/tools/manuscript.py" line="623" />
<source>Hidden</source>
<translation>Skjult</translation>
</message>
@@ -4112,22 +4053,22 @@
<context>
<name>_GuiAlert</name>
<message>
<location filename="../novelwriter/shared.py" line="410" />
<location filename="../novelwriter/shared.py" line="422" />
<source>Information</source>
<translation>Informasjon</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="413" />
<location filename="../novelwriter/shared.py" line="425" />
<source>Warning</source>
<translation>Advarsel</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="416" />
<location filename="../novelwriter/shared.py" line="428" />
<source>Error</source>
<translation>Feil</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="419" />
<location filename="../novelwriter/shared.py" line="431" />
<source>Question</source>
<translation>Spørsmål</translation>
</message>
@@ -4438,27 +4379,27 @@
<context>
<name>_PreviewWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="757" />
<location filename="../novelwriter/tools/manuscript.py" line="769" />
<source>Press the "Preview" button to generate ...</source>
<translation>Trykk på "Forhåndsvisning"-knappen for å generere ...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="855" />
<location filename="../novelwriter/tools/manuscript.py" line="867" />
<source>Processing ...</source>
<translation>Behandler ...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="877" />
<location filename="../novelwriter/tools/manuscript.py" line="888" />
<source>Done</source>
<translation>Ferdig</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="927" />
<location filename="../novelwriter/tools/manuscript.py" line="938" />
<source>Unknown</source>
<translation>Ukjent</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="928" />
<location filename="../novelwriter/tools/manuscript.py" line="939" />
<source>Built</source>
<translation>Bygget</translation>
</message>
@@ -4557,59 +4498,59 @@
<context>
<name>_StatsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1090" />
<location filename="../novelwriter/tools/manuscript.py" line="1050" />
<location filename="../novelwriter/tools/manuscript.py" line="1102" />
<location filename="../novelwriter/tools/manuscript.py" line="1062" />
<source>Words</source>
<translation>Ord</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1117" />
<location filename="../novelwriter/tools/manuscript.py" line="1053" />
<location filename="../novelwriter/tools/manuscript.py" line="1129" />
<location filename="../novelwriter/tools/manuscript.py" line="1065" />
<source>Characters</source>
<translation>Tegn</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1091" />
<location filename="../novelwriter/tools/manuscript.py" line="1103" />
<source>Words in Headings</source>
<translation>Ord i overskrifter</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1092" />
<location filename="../novelwriter/tools/manuscript.py" line="1104" />
<source>Words in Text</source>
<translation>Ord i tekst</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1094" />
<location filename="../novelwriter/tools/manuscript.py" line="1106" />
<source>Headings</source>
<translation>Overskrifter</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1095" />
<location filename="../novelwriter/tools/manuscript.py" line="1107" />
<source>Paragraphs</source>
<translation>Avsnitt</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1118" />
<location filename="../novelwriter/tools/manuscript.py" line="1130" />
<source>Characters in Headings</source>
<translation>Tegn i overskrifter</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1119" />
<location filename="../novelwriter/tools/manuscript.py" line="1131" />
<source>Characters in Text</source>
<translation>Tegn i tekst</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1120" />
<location filename="../novelwriter/tools/manuscript.py" line="1132" />
<source>Characters, No Spaces</source>
<translation>Tegn, utenom mellomrom</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1121" />
<location filename="../novelwriter/tools/manuscript.py" line="1133" />
<source>Characters in Headings, No Spaces</source>
<translation>Tegn i overskrifter, utenom mellomrom</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1122" />
<location filename="../novelwriter/tools/manuscript.py" line="1134" />
<source>Characters in Text, No Spaces</source>
<translation>Tegn i tekst, utenom mellomrom</translation>
</message>

View File

@@ -31,32 +31,32 @@
<message>
<location filename="../novelwriter/core/buildsettings.py" line="109" />
<source>Partition Format</source>
<translation type="unfinished" />
<translation>Partitie indeling</translation>
</message>
<message>
<location filename="../novelwriter/core/buildsettings.py" line="110" />
<source>Chapter Format</source>
<translation type="unfinished" />
<translation>Hoofdstuk indeling</translation>
</message>
<message>
<location filename="../novelwriter/core/buildsettings.py" line="111" />
<source>Unnumbered Format</source>
<translation type="unfinished" />
<translation>Ongenummerde indeling</translation>
</message>
<message>
<location filename="../novelwriter/core/buildsettings.py" line="112" />
<source>Scene Format</source>
<translation type="unfinished" />
<translation>Scène indeling</translation>
</message>
<message>
<location filename="../novelwriter/core/buildsettings.py" line="113" />
<source>Alt. Scene Format</source>
<translation type="unfinished" />
<translation>Alt. Scène Formaat</translation>
</message>
<message>
<location filename="../novelwriter/core/buildsettings.py" line="114" />
<source>Section Format</source>
<translation type="unfinished" />
<translation>Sectie indeling</translation>
</message>
<message>
<location filename="../novelwriter/core/buildsettings.py" line="116" />
@@ -86,7 +86,7 @@
<message>
<location filename="../novelwriter/core/buildsettings.py" line="121" />
<source>Ignore These Keywords</source>
<translation type="unfinished" />
<translation>Negeer deze sleutelwoorden</translation>
</message>
<message>
<location filename="../novelwriter/core/buildsettings.py" line="122" />
@@ -206,17 +206,17 @@
<message>
<location filename="../novelwriter/core/buildsettings.py" line="147" />
<source>First Line Indent</source>
<translation type="unfinished" />
<translation>Eerste regel inspringen</translation>
</message>
<message>
<location filename="../novelwriter/core/buildsettings.py" line="149" />
<source>Markdown (.md)</source>
<translation type="unfinished" />
<translation>Markdown (.md)</translation>
</message>
<message>
<location filename="../novelwriter/core/buildsettings.py" line="150" />
<source>Preserve Hard Line Breaks</source>
<translation type="unfinished" />
<translation>Behoud harde regel afbrekingen</translation>
</message>
<message>
<location filename="../novelwriter/core/buildsettings.py" line="152" />
@@ -231,7 +231,7 @@
<message>
<location filename="../novelwriter/core/buildsettings.py" line="154" />
<source>Preserve Tab Characters</source>
<translation type="unfinished" />
<translation>Tab tekens behouden</translation>
</message>
</context>
<context>
@@ -791,22 +791,22 @@
<context>
<name>GuiDocEditFooter</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3007" />
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<source>Line: {0} ({1})</source>
<translation>Regel: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3008" />
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<source>Words: {0} ({1})</source>
<translation>Woorden: {0} ({1})</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3009" />
<location filename="../novelwriter/gui/doceditor.py" line="3010" />
<source>Words: {0} selected</source>
<translation>Woorden: {0} geselecteerd</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="3021" />
<location filename="../novelwriter/gui/doceditor.py" line="3022" />
<source>Status</source>
<translation>Status</translation>
</message>
@@ -814,27 +814,27 @@
<context>
<name>GuiDocEditHeader</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2805" />
<location filename="../novelwriter/gui/doceditor.py" line="2806" />
<source>Toggle Tool Bar</source>
<translation>Toon/verberg werkbalk</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2810" />
<location filename="../novelwriter/gui/doceditor.py" line="2811" />
<source>Outline</source>
<translation type="unfinished" />
<translation>Samenvatting</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2815" />
<location filename="../novelwriter/gui/doceditor.py" line="2816" />
<source>Search</source>
<translation>Zoek</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2820" />
<location filename="../novelwriter/gui/doceditor.py" line="2821" />
<source>Toggle Focus Mode</source>
<translation>Schakel focus modus in/uit</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2825" />
<location filename="../novelwriter/gui/doceditor.py" line="2826" />
<source>Close</source>
<translation>Sluiten</translation>
</message>
@@ -842,62 +842,62 @@
<context>
<name>GuiDocEditSearch</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2413" />
<location filename="../novelwriter/gui/doceditor.py" line="2414" />
<source>Search for</source>
<translation type="unfinished" />
<translation>Zoek naar</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2418" />
<location filename="../novelwriter/gui/doceditor.py" line="2419" />
<source>Replace with</source>
<translation type="unfinished" />
<translation>Vervang door</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2426" />
<location filename="../novelwriter/gui/doceditor.py" line="2427" />
<source>Search</source>
<translation>Zoek</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2434" />
<location filename="../novelwriter/gui/doceditor.py" line="2435" />
<source>Case Sensitive</source>
<translation>Hoofdlettergevoelig</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2440" />
<location filename="../novelwriter/gui/doceditor.py" line="2441" />
<source>Whole Words Only</source>
<translation>Alleen hele woorden</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2446" />
<location filename="../novelwriter/gui/doceditor.py" line="2447" />
<source>RegEx Mode</source>
<translation>RegEx modus</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2452" />
<location filename="../novelwriter/gui/doceditor.py" line="2453" />
<source>Loop Search</source>
<translation>Zoekopdracht lus</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2458" />
<location filename="../novelwriter/gui/doceditor.py" line="2459" />
<source>Search Next File</source>
<translation>Doorzoek volgend bestand</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2466" />
<location filename="../novelwriter/gui/doceditor.py" line="2467" />
<source>Preserve Case</source>
<translation>Behoud hoofd/kleine letters</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2474" />
<location filename="../novelwriter/gui/doceditor.py" line="2475" />
<source>Close Search</source>
<translation>Zoekopdracht afsluiten</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2485" />
<location filename="../novelwriter/gui/doceditor.py" line="2486" />
<source>Find in current document</source>
<translation>Zoeken in huidige document</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2489" />
<location filename="../novelwriter/gui/doceditor.py" line="2490" />
<source>Find and replace in current document</source>
<translation>Zoek en vervang in huidig document</translation>
</message>
@@ -905,122 +905,122 @@
<context>
<name>GuiDocEditor</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="441" />
<location filename="../novelwriter/gui/doceditor.py" line="442" />
<source>Opened Document: {0}</source>
<translation>Geopend document: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="484" />
<location filename="../novelwriter/gui/doceditor.py" line="485" />
<source>This document has been changed outside of novelWriter while it was open. Overwrite the file on disk?</source>
<translation>Dit document is gewijzigd buiten de openstaande novelWriter instantie. Het bestand op de schijf overschrijven?</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="493" />
<location filename="../novelwriter/gui/doceditor.py" line="494" />
<source>Could not save document.</source>
<translation>Kon document niet opslaan.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="518" />
<location filename="../novelwriter/gui/doceditor.py" line="519" />
<source>Saved Document: {0}</source>
<translation>Document opgeslagen: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="687" />
<location filename="../novelwriter/gui/doceditor.py" line="688" />
<source>Spell checking requires the package PyEnchant. It does not appear to be installed.</source>
<translation>Spellingscontrole vereist het pakket PyEnchant. Het lijkt niet geïnstalleerd te zijn.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="713" />
<location filename="../novelwriter/gui/doceditor.py" line="714" />
<source>Spell check complete</source>
<translation>Spellingscontrole compleet</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="833" />
<location filename="../novelwriter/gui/doceditor.py" line="834" />
<source>Document Details</source>
<translation>Document details</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="835" />
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<source>Created: {0}</source>
<translation>Aangemaakt: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="836" />
<location filename="../novelwriter/gui/doceditor.py" line="837" />
<source>Updated: {0}</source>
<translation>Bijgewerkt: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="838" />
<location filename="../novelwriter/gui/doceditor.py" line="839" />
<source>File Location: {0}</source>
<translation>Bestandslocatie: {0}</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1112" />
<location filename="../novelwriter/gui/doceditor.py" line="1113" />
<source>Set as Document Name</source>
<translation>Instellen als documentnaam</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1118" />
<location filename="../novelwriter/gui/doceditor.py" line="1119" />
<source>Follow Tag</source>
<translation>Volg label</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1122" />
<location filename="../novelwriter/gui/doceditor.py" line="1123" />
<source>Create Note for Tag</source>
<translation>Creëer notitie voor label</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1128" />
<location filename="../novelwriter/gui/doceditor.py" line="1129" />
<source>Cut</source>
<translation>Knippen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1130" />
<location filename="../novelwriter/gui/doceditor.py" line="1131" />
<source>Copy</source>
<translation>Kopiëren</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1133" />
<location filename="../novelwriter/gui/doceditor.py" line="1134" />
<source>Paste</source>
<translation>Plakken</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1138" />
<location filename="../novelwriter/gui/doceditor.py" line="1139" />
<source>Select All</source>
<translation>Selecteer alles</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1140" />
<location filename="../novelwriter/gui/doceditor.py" line="1141" />
<source>Select Word</source>
<translation>Selecteer woord</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1144" />
<location filename="../novelwriter/gui/doceditor.py" line="1145" />
<source>Select Paragraph</source>
<translation>Selecteer paragraaf</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1160" />
<location filename="../novelwriter/gui/doceditor.py" line="1161" />
<source>Spelling Suggestion(s)</source>
<translation>Spelling suggestie(s)</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1167" />
<location filename="../novelwriter/gui/doceditor.py" line="1168" />
<source>No Suggestions</source>
<translation>Geen suggesties</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1170" />
<location filename="../novelwriter/gui/doceditor.py" line="1171" />
<source>Add Word to Dictionary</source>
<translation>Woord toevoegen aan woordenboek</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1596" />
<location filename="../novelwriter/gui/doceditor.py" line="1597" />
<source>Please select some text before calling replace quotes.</source>
<translation>Selecteer a.u.b. een tekst voordat u vervang aanhalingstekens aanroept.</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="1903" />
<location filename="../novelwriter/gui/doceditor.py" line="1904" />
<source>Do you want to create a new project note for the tag '{0}'?</source>
<translation>Wilt u een nieuwe project notitie maken voor het label '{0}'?</translation>
</message>
@@ -1058,7 +1058,7 @@
<message>
<location filename="../novelwriter/dialogs/docsplit.py" line="61" />
<source>Document Headings</source>
<translation type="unfinished" />
<translation>Document koppen</translation>
</message>
<message>
<location filename="../novelwriter/dialogs/docsplit.py" line="64" />
@@ -1068,22 +1068,22 @@
<message>
<location filename="../novelwriter/dialogs/docsplit.py" line="86" />
<source>Split on Heading Level 1 (Partition)</source>
<translation type="unfinished" />
<translation>Splits op kop niveau 1 (Partitie)</translation>
</message>
<message>
<location filename="../novelwriter/dialogs/docsplit.py" line="87" />
<source>Split up to Heading Level 2 (Chapter)</source>
<translation type="unfinished" />
<translation>Opsplitsen tot kop niveau 2 (Hoofdstuk)</translation>
</message>
<message>
<location filename="../novelwriter/dialogs/docsplit.py" line="88" />
<source>Split up to Heading Level 3 (Scene)</source>
<translation type="unfinished" />
<translation>Opsplitsen tot kop niveau 3 (Scène)</translation>
</message>
<message>
<location filename="../novelwriter/dialogs/docsplit.py" line="89" />
<source>Split up to Heading Level 4 (Section)</source>
<translation type="unfinished" />
<translation>Opsplitsen tot kop niveau 4 (Sectie)</translation>
</message>
<message>
<location filename="../novelwriter/dialogs/docsplit.py" line="96" />
@@ -1104,52 +1104,52 @@
<context>
<name>GuiDocToolBar</name>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2270" />
<location filename="../novelwriter/gui/doceditor.py" line="2271" />
<source>Markdown Bold</source>
<translation>Markdown vet</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2276" />
<location filename="../novelwriter/gui/doceditor.py" line="2277" />
<source>Markdown Italic</source>
<translation>Markdown cursief</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2282" />
<location filename="../novelwriter/gui/doceditor.py" line="2283" />
<source>Markdown Strikethrough</source>
<translation>Markdown doorstrepen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2288" />
<location filename="../novelwriter/gui/doceditor.py" line="2289" />
<source>Shortcode Bold</source>
<translation>Korte code vet</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2294" />
<location filename="../novelwriter/gui/doceditor.py" line="2295" />
<source>Shortcode Italic</source>
<translation>Korte code cursief</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2300" />
<location filename="../novelwriter/gui/doceditor.py" line="2301" />
<source>Shortcode Strikethrough</source>
<translation>Korte code doorstrepen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2306" />
<location filename="../novelwriter/gui/doceditor.py" line="2307" />
<source>Shortcode Underline</source>
<translation>Korte code onderstrepen</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2312" />
<location filename="../novelwriter/gui/doceditor.py" line="2313" />
<source>Shortcode Highlight</source>
<translation type="unfinished" />
<translation>Snelkoppeling accentuering</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2318" />
<location filename="../novelwriter/gui/doceditor.py" line="2319" />
<source>Shortcode Superscript</source>
<translation>Korte code superscript</translation>
</message>
<message>
<location filename="../novelwriter/gui/doceditor.py" line="2324" />
<location filename="../novelwriter/gui/doceditor.py" line="2325" />
<source>Shortcode Subscript</source>
<translation>Korte code subscript</translation>
</message>
@@ -1187,7 +1187,7 @@
<message>
<location filename="../novelwriter/gui/docviewer.py" line="659" />
<source>Outline</source>
<translation type="unfinished" />
<translation>Samenvatting</translation>
</message>
<message>
<location filename="../novelwriter/gui/docviewer.py" line="664" />
@@ -1854,7 +1854,7 @@
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="676" />
<source>Highlight</source>
<translation type="unfinished" />
<translation>Markeren</translation>
</message>
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="682" />
@@ -1869,22 +1869,22 @@
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="697" />
<source>Heading 1 (Partition)</source>
<translation type="unfinished" />
<translation>Kop 1 (Partitie)</translation>
</message>
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="704" />
<source>Heading 2 (Chapter)</source>
<translation type="unfinished" />
<translation>Kop 2 (Hoofdstuk)</translation>
</message>
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="711" />
<source>Heading 3 (Scene)</source>
<translation type="unfinished" />
<translation>Kop 3 (Scène)</translation>
</message>
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="718" />
<source>Heading 4 (Section)</source>
<translation type="unfinished" />
<translation>Kop 4 (Sectie)</translation>
</message>
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="728" />
@@ -1899,7 +1899,7 @@
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="740" />
<source>Alternative Scene</source>
<translation type="unfinished" />
<translation>Alternatieve scène</translation>
</message>
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="749" />
@@ -1944,12 +1944,12 @@
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="814" />
<source>Replace Straight Single Quotes</source>
<translation type="unfinished" />
<translation>Vervang rechte enkele aanhalingstekens</translation>
</message>
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="820" />
<source>Replace Straight Double Quotes</source>
<translation type="unfinished" />
<translation>Vervang rechte dubbele aanhalingstekens</translation>
</message>
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="826" />
@@ -1989,7 +1989,7 @@
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="869" />
<source>Find in Project</source>
<translation type="unfinished" />
<translation>Zoek in project</translation>
</message>
<message>
<location filename="../novelwriter/gui/mainmenu.py" line="878" />
@@ -2161,12 +2161,12 @@
<message>
<location filename="../novelwriter/tools/manuscript.py" line="158" />
<source>Details</source>
<translation type="unfinished" />
<translation>Details</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="159" />
<source>Outline</source>
<translation type="unfinished" />
<translation>Samenvatting</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="173" />
@@ -2198,57 +2198,57 @@
<context>
<name>GuiManuscriptBuild</name>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="72" />
<location filename="../novelwriter/tools/manusbuild.py" line="70" />
<source>Build Manuscript</source>
<translation>Bouw manuscript</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="93" />
<location filename="../novelwriter/tools/manusbuild.py" line="91" />
<source>Output Format</source>
<translation>Uitvoerformaat</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="119" />
<location filename="../novelwriter/tools/manusbuild.py" line="117" />
<source>Table of Contents</source>
<translation>Inhoudsopgave</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="147" />
<location filename="../novelwriter/tools/manusbuild.py" line="145" />
<source>Path</source>
<translation>Pad</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="157" />
<location filename="../novelwriter/tools/manusbuild.py" line="155" />
<source>File Name</source>
<translation>Bestandsnaam</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="160" />
<location filename="../novelwriter/tools/manusbuild.py" line="158" />
<source>Reset file name to default</source>
<translation>Herstel bestandsnaam naar standaard</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<location filename="../novelwriter/tools/manusbuild.py" line="182" />
<source>Open Folder</source>
<translation>Map openen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="186" />
<location filename="../novelwriter/tools/manusbuild.py" line="184" />
<source>&amp;Build</source>
<translation>&amp;Bouwen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="281" />
<location filename="../novelwriter/tools/manusbuild.py" line="279" />
<source>Select Folder</source>
<translation>Selecteer map</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="319" />
<location filename="../novelwriter/tools/manusbuild.py" line="317" />
<source>Output folder does not exist.</source>
<translation>Uitvoermap bestaat niet.</translation>
</message>
<message>
<location filename="../novelwriter/tools/manusbuild.py" line="327" />
<location filename="../novelwriter/tools/manusbuild.py" line="325" />
<source>The file already exists. Do you want to overwrite it?</source>
<translation>Het bestand bestaat al. Wil je het overschrijven?</translation>
</message>
@@ -2415,7 +2415,7 @@
<context>
<name>GuiOutlineToolBar</name>
<message>
<location filename="../novelwriter/gui/outline.py" line="223" />
<location filename="../novelwriter/gui/outline.py" line="222" />
<source>Outline of</source>
<translation>Omlijning van</translation>
</message>
@@ -2997,27 +2997,27 @@
<message>
<location filename="../novelwriter/gui/search.py" line="74" />
<source>Project Search</source>
<translation type="unfinished" />
<translation>Project zoeken</translation>
</message>
<message>
<location filename="../novelwriter/gui/search.py" line="84" />
<source>Case Sensitive</source>
<translation type="unfinished">Hoofdlettergevoelig</translation>
<translation>Hoofdlettergevoelig</translation>
</message>
<message>
<location filename="../novelwriter/gui/search.py" line="89" />
<source>Whole Words Only</source>
<translation type="unfinished">Alleen hele woorden</translation>
<translation>Alleen hele woorden</translation>
</message>
<message>
<location filename="../novelwriter/gui/search.py" line="94" />
<source>RegEx Mode</source>
<translation type="unfinished">RegEx modus</translation>
<translation>RegEx modus</translation>
</message>
<message>
<location filename="../novelwriter/gui/search.py" line="101" />
<source>Search for</source>
<translation type="unfinished" />
<translation>Zoek naar</translation>
</message>
</context>
<context>
@@ -3231,7 +3231,7 @@
<message>
<location filename="../novelwriter/gui/sidebar.py" line="73" />
<source>Project Search</source>
<translation type="unfinished" />
<translation>Project zoeken</translation>
</message>
<message>
<location filename="../novelwriter/gui/sidebar.py" line="77" />
@@ -3860,79 +3860,20 @@
<translation>Standaardwaarden herstellen</translation>
</message>
</context>
<context>
<name>QWizard</name>
<message>
<location filename="qtbase.py" line="54" />
<source>Go Back</source>
<translation>Ga terug</translation>
</message>
<message>
<location filename="qtbase.py" line="55" />
<source>&lt; &amp;Back</source>
<translation>&lt; &amp;Terug</translation>
</message>
<message>
<location filename="qtbase.py" line="56" />
<source>Continue</source>
<translation>Doorgaan</translation>
</message>
<message>
<location filename="qtbase.py" line="57" />
<source>&amp;Next</source>
<translation>&amp;Volgende</translation>
</message>
<message>
<location filename="qtbase.py" line="58" />
<source>&amp;Next &gt;</source>
<translation>&amp;Volgende &gt;</translation>
</message>
<message>
<location filename="qtbase.py" line="59" />
<source>Commit</source>
<translation>Vastleggen</translation>
</message>
<message>
<location filename="qtbase.py" line="60" />
<source>Done</source>
<translation>Gereed</translation>
</message>
<message>
<location filename="qtbase.py" line="61" />
<source>&amp;Finish</source>
<translation>Vol&amp;tooien</translation>
</message>
<message>
<location filename="qtbase.py" line="63" />
<location filename="qtbase.py" line="62" />
<source>Cancel</source>
<translation>Annuleer</translation>
</message>
<message>
<location filename="qtbase.py" line="64" />
<source>Help</source>
<translation>Help</translation>
</message>
<message>
<location filename="qtbase.py" line="65" />
<source>&amp;Help</source>
<translation>&amp;Help</translation>
</message>
</context>
<context>
<name>SharedData</name>
<message>
<location filename="../novelwriter/shared.py" line="249" />
<location filename="../novelwriter/shared.py" line="250" />
<source>novelWriter Project File or Zip File</source>
<translation>novelWriter project- of zip bestand</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="250" />
<location filename="../novelwriter/shared.py" line="251" />
<source>novelWriter Project File</source>
<translation>novelWriter Projectbestand</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="254" />
<location filename="../novelwriter/shared.py" line="255" />
<source>Open Project</source>
<translation>Project openen</translation>
</message>
@@ -4041,34 +3982,34 @@
<context>
<name>_DetailsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Setting</source>
<translation>Instelling</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="522" />
<location filename="../novelwriter/tools/manuscript.py" line="533" />
<source>Value</source>
<translation>Waarde</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="589" />
<location filename="../novelwriter/tools/manuscript.py" line="600" />
<source>Name</source>
<translation>Naam</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="595" />
<location filename="../novelwriter/tools/manuscript.py" line="606" />
<source>Selection</source>
<translation>Selectie</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="611" />
<location filename="../novelwriter/tools/manuscript.py" line="622" />
<source>Title</source>
<translation>Titel</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="612" />
<location filename="../novelwriter/tools/manuscript.py" line="623" />
<source>Hidden</source>
<translation type="unfinished">Verborgen</translation>
<translation>Verborgen</translation>
</message>
</context>
<context>
@@ -4112,22 +4053,22 @@
<context>
<name>_GuiAlert</name>
<message>
<location filename="../novelwriter/shared.py" line="410" />
<location filename="../novelwriter/shared.py" line="422" />
<source>Information</source>
<translation>Informatie</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="413" />
<location filename="../novelwriter/shared.py" line="425" />
<source>Warning</source>
<translation>Waarschuwing</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="416" />
<location filename="../novelwriter/shared.py" line="428" />
<source>Error</source>
<translation>Foutmelding</translation>
</message>
<message>
<location filename="../novelwriter/shared.py" line="419" />
<location filename="../novelwriter/shared.py" line="431" />
<source>Question</source>
<translation>Vraag</translation>
</message>
@@ -4209,21 +4150,21 @@
<message>
<location filename="../novelwriter/tools/manussettings.py" line="758" />
<source>Additional Styling</source>
<translation type="unfinished" />
<translation>Extra opmaak</translation>
</message>
<message>
<location filename="../novelwriter/tools/manussettings.py" line="795" />
<location filename="../novelwriter/tools/manussettings.py" line="780" />
<location filename="../novelwriter/tools/manussettings.py" line="765" />
<source>Centre</source>
<translation type="unfinished" />
<translation>Midden</translation>
</message>
<message>
<location filename="../novelwriter/tools/manussettings.py" line="797" />
<location filename="../novelwriter/tools/manussettings.py" line="782" />
<location filename="../novelwriter/tools/manussettings.py" line="767" />
<source>Page Break</source>
<translation type="unfinished">Nieuwe pagina</translation>
<translation>Nieuwe pagina</translation>
</message>
</context>
<context>
@@ -4438,27 +4379,27 @@
<context>
<name>_PreviewWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="757" />
<location filename="../novelwriter/tools/manuscript.py" line="769" />
<source>Press the "Preview" button to generate ...</source>
<translation>Druk op de knop "Preview" om te genereren...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="855" />
<location filename="../novelwriter/tools/manuscript.py" line="867" />
<source>Processing ...</source>
<translation>Wordt verwerkt...</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="877" />
<location filename="../novelwriter/tools/manuscript.py" line="888" />
<source>Done</source>
<translation>Gereed</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="927" />
<location filename="../novelwriter/tools/manuscript.py" line="938" />
<source>Unknown</source>
<translation>Onbekend</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="928" />
<location filename="../novelwriter/tools/manuscript.py" line="939" />
<source>Built</source>
<translation>Gebouwd</translation>
</message>
@@ -4557,61 +4498,61 @@
<context>
<name>_StatsWidget</name>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1090" />
<location filename="../novelwriter/tools/manuscript.py" line="1050" />
<location filename="../novelwriter/tools/manuscript.py" line="1102" />
<location filename="../novelwriter/tools/manuscript.py" line="1062" />
<source>Words</source>
<translation type="unfinished">Woorden</translation>
<translation>Woorden</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1117" />
<location filename="../novelwriter/tools/manuscript.py" line="1053" />
<location filename="../novelwriter/tools/manuscript.py" line="1129" />
<location filename="../novelwriter/tools/manuscript.py" line="1065" />
<source>Characters</source>
<translation type="unfinished">Tekens</translation>
<translation>Tekens</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1091" />
<location filename="../novelwriter/tools/manuscript.py" line="1103" />
<source>Words in Headings</source>
<translation type="unfinished" />
<translation>Woorden in koppen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1092" />
<location filename="../novelwriter/tools/manuscript.py" line="1104" />
<source>Words in Text</source>
<translation type="unfinished" />
<translation>Woorden in tekst</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1094" />
<location filename="../novelwriter/tools/manuscript.py" line="1106" />
<source>Headings</source>
<translation type="unfinished">Koppen</translation>
<translation>Koppen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1095" />
<location filename="../novelwriter/tools/manuscript.py" line="1107" />
<source>Paragraphs</source>
<translation type="unfinished">Paragrafen</translation>
<translation>Paragrafen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1118" />
<location filename="../novelwriter/tools/manuscript.py" line="1130" />
<source>Characters in Headings</source>
<translation type="unfinished" />
<translation>Tekens in koppen</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1119" />
<location filename="../novelwriter/tools/manuscript.py" line="1131" />
<source>Characters in Text</source>
<translation type="unfinished" />
<translation>Tekens in tekst</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1120" />
<location filename="../novelwriter/tools/manuscript.py" line="1132" />
<source>Characters, No Spaces</source>
<translation type="unfinished" />
<translation>Tekens, geen spaties</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1121" />
<location filename="../novelwriter/tools/manuscript.py" line="1133" />
<source>Characters in Headings, No Spaces</source>
<translation type="unfinished" />
<translation>Tekens in koppen, geen spaties</translation>
</message>
<message>
<location filename="../novelwriter/tools/manuscript.py" line="1122" />
<location filename="../novelwriter/tools/manuscript.py" line="1134" />
<source>Characters in Text, No Spaces</source>
<translation type="unfinished" />
<translation>Tekens in tekst, geen spaties</translation>
</message>
</context>
<context>
@@ -4771,7 +4712,7 @@
<message>
<location filename="../novelwriter/gui/projtree.py" line="1932" />
<source>Split Document by Headings</source>
<translation type="unfinished" />
<translation>Document splitsen op koppen</translation>
</message>
<message>
<location filename="../novelwriter/gui/projtree.py" line="1942" />
@@ -4786,7 +4727,7 @@
<message>
<location filename="../novelwriter/gui/projtree.py" line="1947" />
<source>Duplicate</source>
<translation type="unfinished" />
<translation>Dupliceren</translation>
</message>
<message>
<location filename="../novelwriter/gui/projtree.py" line="1963" />

File diff suppressed because it is too large Load Diff

View File

@@ -47,19 +47,3 @@ QT_TRANSLATE_NOOP("QPlatformTheme", "Help")
QT_TRANSLATE_NOOP("QPlatformTheme", "Apply")
QT_TRANSLATE_NOOP("QPlatformTheme", "Reset")
QT_TRANSLATE_NOOP("QPlatformTheme", "Restore Defaults")
# QWizard
# =======
QT_TRANSLATE_NOOP("QWizard", "Go Back")
QT_TRANSLATE_NOOP("QWizard", "< &Back")
QT_TRANSLATE_NOOP("QWizard", "Continue")
QT_TRANSLATE_NOOP("QWizard", "&Next")
QT_TRANSLATE_NOOP("QWizard", "&Next >")
QT_TRANSLATE_NOOP("QWizard", "Commit")
QT_TRANSLATE_NOOP("QWizard", "Done")
QT_TRANSLATE_NOOP("QWizard", "&Finish")
QT_TRANSLATE_NOOP("QWizard", "Cancel")
QT_TRANSLATE_NOOP("QWizard", "Cancel")
QT_TRANSLATE_NOOP("QWizard", "Help")
QT_TRANSLATE_NOOP("QWizard", "&Help")

View File

@@ -32,7 +32,7 @@ from typing import TYPE_CHECKING
from PyQt5.QtWidgets import QApplication, QErrorMessage
from novelwriter.config import Config
from novelwriter.error import exceptionHandler, logException
from novelwriter.error import exceptionHandler
from novelwriter.shared import SharedData
if TYPE_CHECKING: # pragma: no cover
@@ -47,9 +47,9 @@ __license__ = "GPLv3"
__author__ = "Veronica Berglyd Olsen"
__maintainer__ = "Veronica Berglyd Olsen"
__email__ = "code@vkbo.net"
__version__ = "2.4"
__hexversion__ = "0x020400f0"
__date__ = "2024-04-20"
__version__ = "2.5b1"
__hexversion__ = "0x020500b1"
__date__ = "2024-05-26"
__status__ = "Stable"
__domain__ = "novelwriter.io"
@@ -162,19 +162,19 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
# Check Packages and Versions
errorData = []
errorCode = 0
if sys.hexversion < 0x030800f0:
if sys.hexversion < 0x030900f0:
errorData.append(
"At least Python 3.8 is required, found %s" % CONFIG.verPyString
"At least Python 3.9 is required, found %s" % CONFIG.verPyString
)
errorCode |= 0x04
if CONFIG.verQtValue < 0x050a00:
if CONFIG.verQtValue < 0x050f00:
errorData.append(
"At least Qt5 version 5.10 is required, found %s" % CONFIG.verQtString
"At least Qt5 version 5.15.0 is required, found %s" % CONFIG.verQtString
)
errorCode |= 0x08
if CONFIG.verPyQtValue < 0x050a00:
if CONFIG.verPyQtValue < 0x050f00:
errorData.append(
"At least PyQt5 version 5.10 is required, found %s" % CONFIG.verPyQtString
"At least PyQt5 version 5.15.0 is required, found %s" % CONFIG.verPyQtString
)
errorCode |= 0x10
@@ -198,52 +198,46 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
# Finish initialising config
CONFIG.initConfig(confPath, dataPath)
if CONFIG.osDarwin:
if sys.platform == "darwin":
try:
from Foundation import NSBundle # type: ignore
bundle = NSBundle.mainBundle()
info = bundle.localizedInfoDictionary() or bundle.infoDictionary()
info["CFBundleName"] = "novelWriter"
except Exception:
logger.error("Failed to set application name")
logException()
elif CONFIG.osWindows:
pass # Quietly ignore error
elif sys.platform == "win32":
try:
import ctypes
appID = f"io.novelwriter.{__version__}"
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appID) # type: ignore
except Exception:
logger.error("Failed to set application name")
logException()
pass # Quietly ignore error
# Import GUI (after dependency checks), and launch
from novelwriter.guimain import GuiMain
if testMode:
nwGUI = GuiMain()
return nwGUI
# Only used for testing where the test framework creates the app
CONFIG.loadConfig()
return GuiMain()
else:
nwApp = QApplication([CONFIG.appName, (f"-style={qtStyle}")])
nwApp.setApplicationName(CONFIG.appName)
nwApp.setApplicationVersion(__version__)
nwApp.setOrganizationDomain(__domain__)
nwApp.setOrganizationName(__domain__)
nwApp.setDesktopFileName(CONFIG.appName)
app = QApplication([CONFIG.appName, (f"-style={qtStyle}")])
app.setApplicationName(CONFIG.appName)
app.setApplicationVersion(__version__)
app.setOrganizationDomain(__domain__)
app.setOrganizationName(__domain__)
app.setDesktopFileName(CONFIG.appName)
# Connect the exception handler before making the main GUI
sys.excepthook = exceptionHandler
# Connect the exception handler before making the main GUI
sys.excepthook = exceptionHandler
# Run Config steps that require the QApplication
CONFIG.initLocalisation(nwApp)
CONFIG.setTextFont(CONFIG.textFont, CONFIG.textSize) # Makes sure these are valid
# Run Config steps that require the QApplication
CONFIG.loadConfig()
CONFIG.initLocalisation(app)
# Launch main GUI
nwGUI = GuiMain()
nwGUI.postLaunchTasks(cmdOpen)
# Launch main GUI
nwGUI = GuiMain()
nwGUI.postLaunchTasks(cmdOpen)
sys.exit(nwApp.exec())
return None
# END Function main
sys.exit(app.exec())

View File

@@ -1,6 +1,7 @@
{
"Synopsis": "Synopsis",
"Short Description": "Short Description",
"Footnotes": "Footnotes",
"Comment": "Comment",
"Notes": "Notes",
"Tag": "Tag",

View File

@@ -59,6 +59,7 @@ fmt_strike-md = nw_tb-strike-md.svg
fmt_subscript = nw_tb-subscript.svg
fmt_superscript = nw_tb-superscript.svg
fmt_underline = nw_tb-underline.svg
font = nw_font.svg
forward = typ_chevron-right.svg
import = mixed_import.svg
list = typ_th-list.svg
@@ -78,6 +79,7 @@ proj_scene = mixed_document-scene.svg
proj_section = mixed_document-section.svg
proj_stats = typ_chart-bar-grey.svg
proj_title = mixed_document-title.svg
quote = nw_quote.svg
refresh = typ_refresh.svg
remove = typ_minus.svg
revert = typ_refresh-flipped.svg

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m2.5744 5.8208v-4.2853h-1.4527v-1.0064h4.1067v1.0064h-1.4527v4.2853z" fill="#aeaeae" stroke-width="1.1503"/>
</svg>

After

Width:  |  Height:  |  Size: 267 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m1.3715 3.0659q-0.32092-0.25674-0.44928-0.44928-0.12836-0.19255-0.12837-0.44928 0-0.42361 0.28241-0.69319 0.28241-0.28241 0.68035-0.28241 0.25674 0 0.44928 0.10269 0.19255 0.089858 0.3466 0.23106 0.24391 0.4878 0.24391 1.0141 0 0.42361-0.14121 0.82157-0.12837 0.38511-0.37227 0.71886-0.23106 0.33377-0.55199 0.61618-0.30808 0.26958-0.65467 0.46213l-0.25674-0.41078q0.29525-0.21822 0.57766-0.53914 0.28241-0.33375 0.46213-0.73171zm2.7598 0q-0.32092-0.25674-0.44928-0.44928-0.12837-0.19255-0.12837-0.44928 0-0.42361 0.28241-0.69319 0.28241-0.28241 0.68035-0.28241 0.25674 0 0.44928 0.10269 0.19255 0.089858 0.3466 0.23106 0.24391 0.4878 0.24391 1.0141 0 0.42361-0.14121 0.82157-0.12837 0.38511-0.37227 0.71886-0.23106 0.33377-0.55199 0.61618-0.30808 0.26958-0.65467 0.46213l-0.25674-0.41078q0.29525-0.21822 0.57766-0.53914 0.28241-0.33375 0.46213-0.73171z" fill="#aeaeae" fill-opacity=".78039" stroke-width="1.8194"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -59,6 +59,7 @@ fmt_strike-md = nw_tb-strike-md.svg
fmt_subscript = nw_tb-subscript.svg
fmt_superscript = nw_tb-superscript.svg
fmt_underline = nw_tb-underline.svg
font = nw_font.svg
forward = typ_chevron-right.svg
import = mixed_import.svg
list = typ_th-list.svg
@@ -78,6 +79,7 @@ proj_scene = mixed_document-scene.svg
proj_section = mixed_document-section.svg
proj_stats = typ_chart-bar-grey.svg
proj_title = mixed_document-title.svg
quote = nw_quote.svg
refresh = typ_refresh.svg
remove = typ_minus.svg
revert = typ_refresh-flipped.svg

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m2.5744 5.8208v-4.2853h-1.4527v-1.0064h4.1067v1.0064h-1.4527v4.2853z" fill-opacity=".78039" stroke-width="1.1503"/>
</svg>

After

Width:  |  Height:  |  Size: 274 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" viewBox="0 0 6.35 6.35" xmlns="http://www.w3.org/2000/svg">
<path d="m1.3715 3.0659q-0.32092-0.25674-0.44928-0.44928-0.12836-0.19255-0.12837-0.44928 0-0.42361 0.28241-0.69319 0.28241-0.28241 0.68035-0.28241 0.25674 0 0.44928 0.10269 0.19255 0.089858 0.3466 0.23106 0.24391 0.4878 0.24391 1.0141 0 0.42361-0.14121 0.82157-0.12837 0.38511-0.37227 0.71886-0.23106 0.33377-0.55199 0.61618-0.30808 0.26958-0.65467 0.46213l-0.25674-0.41078q0.29525-0.21822 0.57766-0.53914 0.28241-0.33375 0.46213-0.73171zm2.7598 0q-0.32092-0.25674-0.44928-0.44928-0.12837-0.19255-0.12837-0.44928 0-0.42361 0.28241-0.69319 0.28241-0.28241 0.68035-0.28241 0.25674 0 0.44928 0.10269 0.19255 0.089858 0.3466 0.23106 0.24391 0.4878 0.24391 1.0141 0 0.42361-0.14121 0.82157-0.12837 0.38511-0.37227 0.71886-0.23106 0.33377-0.55199 0.61618-0.30808 0.26958-0.65467 0.46213l-0.25674-0.41078q0.29525-0.21822 0.57766-0.53914 0.28241-0.33375 0.46213-0.73171z" fill-opacity=".78039" stroke-width="1.8194"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -12,15 +12,17 @@ link = 77, 077, 255
headertext = 255, 255, 255
headertag = 50, 0, 180
emphasis = 0, 255, 255
straightquotes = 7, 27, 219
doublequotes = 0, 255, 0
singlequotes = 0, 140, 255
dialog = 0, 255, 0
altdialog = 0, 140, 255
note = 150, 150, 150
hidden = 77, 77, 100
shortcode = 255, 255, 0
keyword = 255, 100, 255
tag = 255, 150, 10
value = 255, 150, 10
spellcheckline = 242, 72, 23
errorline = 186, 218, 4
replacetag = 0, 0, 180
modifier = 144, 142, 176
optional = 180, 180, 180
texthighlight = 255, 255, 132, 96

View File

@@ -6,23 +6,37 @@ url = https://github.com/vkbo/novelWriter
license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
##
# Colours:
# Background = 2a2a2a : 42, 42, 42
# Foreground = cccccc : 204, 204, 204
# Comment = 969696 : 150, 150, 150
# Red = e64250 : 230, 66, 80
# Orange = f99157 : 249, 145, 87
# Yellow = ffff84 : 255, 255, 132
# Green = 99cc99 : 153, 204, 153
# Blue = 288ed7 : 102, 153, 204
# Purple = 8c56d7 : 140, 86, 215
##
[Syntax]
background = 54, 54, 54
text = 199, 207, 208
link = 184, 200, 0
headertext = 0, 155, 200
headertag = 0, 105, 135
emphasis = 200, 120, 0
straightquotes = 200, 46, 0
doublequotes = 184, 200, 0
singlequotes = 136, 200, 0
background = 42, 42, 42
text = 204, 204, 204
link = 102, 153, 204
headertext = 153, 204, 153
headertag = 153, 204, 153, 160
emphasis = 249, 145, 87
dialog = 102, 153, 204
altdialog = 102, 153, 204
note = 255, 255, 216
hidden = 150, 150, 150
shortcode = 0, 155, 200
keyword = 200, 46, 0
value = 184, 200, 0
optional = 0, 155, 200
spellcheckline = 200, 46, 0
errorline = 46, 200, 0
replacetag = 0, 184, 46
modifier = 200, 120, 0
texthighlight = 136, 200, 0, 96
shortcode = 153, 204, 153
keyword = 230, 66, 80
tag = 153, 204, 153
value = 102, 153, 204
optional = 153, 204, 153
spellcheckline = 230, 66, 80
errorline = 153, 204, 153
replacetag = 153, 204, 153
modifier = 153, 204, 153
texthighlight = 255, 255, 132, 96

View File

@@ -6,23 +6,37 @@ url = https://github.com/vkbo/novelWriter
license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
##
# Colours:
# Background = ffffff : 255, 255, 255
# Foreground = 000000 : 0, 0, 0
# Comment = 646464 : 100, 100, 100
# Red = c83232 : 200, 50, 50
# Orange = ae6e1e : 174, 110, 30
# Yellow = 969600 : 150, 150, 0
# Green = 006400 : 0, 100, 0
# Blue = 0000c8 : 0, 0, 200
# Purple = 6400ae : 100, 0, 174
##
[Syntax]
background = 255, 255, 255
text = 0, 0, 0
link = 0, 0, 200
headertext = 0, 100, 0
headertag = 50, 100, 50
emphasis = 150, 110, 30
straightquotes = 200, 0, 0
doublequotes = 0, 0, 200
singlequotes = 0, 0, 200
headertag = 0, 100, 0, 160
emphasis = 174, 110, 30
dialog = 0, 0, 200
altdialog = 0, 0, 200
note = 100, 100, 0
hidden = 100, 100, 100
shortcode = 0, 100, 0
keyword = 200, 50, 50
value = 50, 150, 50
tag = 0, 100, 0
value = 0, 0, 200
optional = 0, 100, 0
spellcheckline = 200, 0, 0
errorline = 0, 150, 0
replacetag = 0, 150, 0
modifier = 150, 110, 30
spellcheckline = 200, 50, 50
errorline = 0, 100, 0
replacetag = 0, 100, 0
modifier = 0, 100, 0
texthighlight = 150, 150, 0, 96

View File

@@ -0,0 +1,44 @@
[Main]
name = Dracula
author = Veronica Berglyd Olsen (adaptation)
credit = Zeno Rocha
url = https://draculatheme.com
license = MIT
licenseurl = https://github.com/dracula/dracula-theme/blob/main/LICENSE
##
# Colours:
# Background = 282a36 : 40, 42, 54
# Foreground = f8f8f2 : 248, 248, 242
# Comment = 6272a4 : 98, 114, 164
# Cyan = 8be9fd : 139, 233, 253
# Green = 50fa7b : 80, 250, 123
# Orange = ffb86c : 255, 184, 108
# Pink = ff79c6 : 255, 121, 198
# Purple = bd93f9 : 189, 147, 249
# Red = ff5555 : 255, 85, 85
# Yellow = f1fa8c : 241, 250, 140
##
[Syntax]
# See: https://spec.draculatheme.com/
background = 40, 42, 54
text = 248, 248, 242
link = 255, 121, 198
headertext = 189, 147, 249
headertag = 189, 147, 249, 160
emphasis = 255, 184, 108
dialog = 80, 250, 123
altdialog = 241, 250, 140
note = 255, 204, 233
hidden = 98, 114, 164
shortcode = 139, 233, 253
keyword = 255, 121, 198
tag = 255, 184, 108
value = 255, 184, 108
optional = 80, 250, 123
spellcheckline = 255, 85, 85
errorline = 80, 250, 123
replacetag = 241, 250, 140
modifier = 139, 233, 253
texthighlight = 241, 250, 140, 96

View File

@@ -11,14 +11,15 @@ background = 54, 54, 54
text = 200, 200, 200
link = 200, 200, 200
headertext = 225, 225, 225
headertag = 225, 225, 225
headertag = 225, 225, 225, 160
emphasis = 200, 200, 200
straightquotes = 200, 200, 200
doublequotes = 200, 200, 200
singlequotes = 200, 200, 200
dialog = 200, 200, 200
altdialog = 200, 200, 200
note = 200, 200, 200
hidden = 150, 150, 150
shortcode = 225, 225, 225
keyword = 225, 225, 225
tag = 200, 200, 200
value = 200, 200, 200
optional = 225, 225, 225
spellcheckline = 200, 46, 0

View File

@@ -11,14 +11,15 @@ background = 255, 255, 255
text = 20, 20, 20
link = 20, 20, 20
headertext = 0, 0, 0
headertag = 0, 0, 0
headertag = 0, 0, 0, 160
emphasis = 20, 20, 20
straightquotes = 20, 20, 20
doublequotes = 20, 20, 20
singlequotes = 20, 20, 20
dialog = 20, 20, 20
altdialog = 20, 20, 20
note = 20, 20, 20
hidden = 100, 100, 100
shortcode = 0, 0, 0
keyword = 0, 0, 0
tag = 20, 20, 20
value = 20, 20, 20
optional = 0, 0, 0
spellcheckline = 200, 0, 0

View File

@@ -31,18 +31,19 @@ background = 251, 251, 251
text = 64, 63, 82
link = 40, 142, 215
headertext = 40, 142, 215
headertag = 40, 142, 215
headertag = 40, 142, 215, 160
emphasis = 224, 175, 5
straightquotes = 222, 61, 58
doublequotes = 8, 145, 106
singlequotes = 255, 235, 149
dialog = 8, 145, 106
altdialog = 218, 170, 1
note = 128, 118, 75
hidden = 152, 159, 177
shortcode = 40, 142, 215
keyword = 222, 61, 58
value = 150, 74, 193
tag = 150, 74, 193
value = 224, 175, 5
optional = 40, 142, 215
spellcheckline = 222, 61, 58
errorline = 8, 145, 106
replacetag = 42, 162, 152
modifier = 224, 175, 5
modifier = 8, 145, 106
texthighlight = 218, 170, 1, 96

View File

@@ -31,18 +31,19 @@ background = 1, 22, 39
text = 214, 222, 235
link = 130, 170, 255
headertext = 130, 170, 255
headertag = 130, 170, 255
headertag = 130, 170, 255, 160
emphasis = 236, 196, 141
straightquotes = 247, 140, 108
doublequotes = 173, 219, 103
singlequotes = 255, 235, 149
dialog = 173, 219, 103
altdialog = 255, 235, 149
note = 255, 249, 202
hidden = 99, 119, 119
shortcode = 130, 170, 255
keyword = 247, 140, 108
value = 199, 146, 234
tag = 199, 146, 234
value = 255, 235, 149
optional = 130, 170, 255
spellcheckline = 247, 140, 108
errorline = 173, 219, 103
replacetag = 127, 219, 202
modifier = 236, 196, 141
modifier = 173, 219, 103
texthighlight = 255, 235, 149, 96

View File

@@ -0,0 +1,42 @@
[Main]
name = Snazzy Light
author = Veronica Berglyd Olsen (adaptation)
credit = Florian Reuschel (color theme)
url = https://github.com/loilo/vscode-snazzy-light
license = MIT License
licenseurl = https://github.com/loilo/vscode-snazzy-light/blob/master/LICENSE
##
# Colours:
# Background = fafbfc : 250, 251, 252
# Foreground = 565869 : 86, 88, 105
# Comment = 9194a2 : 145, 148, 162
# Yellow = cf9c00 : 207, 156, 0
# Red = ff5c57 : 255, 92, 87
# Pink = f767bb : 247, 103, 187
# Blue = 09a1ed : 9, 161, 237
# Cyan = 13bbb7 : 19, 187, 183
# Green = 2dae58 : 45, 174, 88
##
[Syntax]
background = 250, 251, 252
text = 86, 88, 105
link = 9, 161, 237
headertext = 45, 174, 88
headertag = 45, 174, 88, 160
emphasis = 19, 187, 183
dialog = 207, 156, 0
altdialog = 207, 156, 0
note = 120, 187, 185
hidden = 145, 148, 162
shortcode = 247, 103, 187
keyword = 9, 161, 237
tag = 45, 174, 88
value = 9, 161, 237
optional = 207, 156, 0
spellcheckline = 255, 92, 87
errorline = 45, 174, 88
replacetag = 19, 187, 183
modifier = 247, 103, 187
texthighlight = 207, 156, 0, 96

View File

@@ -13,12 +13,13 @@ link = 38, 139, 210
headertext = 147, 161, 161
headertag = 42, 161, 152
emphasis = 38, 139, 210
straightquotes = 211, 54, 130
doublequotes = 42, 161, 152
singlequotes = 42, 161, 152
dialog = 42, 161, 152
altdialog = 42, 161, 152
note = 101, 161, 156
hidden = 147, 161, 161
shortcode = 147, 161, 161
keyword = 133, 153, 0
tag = 203, 75, 22
value = 203, 75, 22
optional = 147, 161, 161
spellcheckline = 203, 75, 22

View File

@@ -13,12 +13,13 @@ link = 38, 139, 210
headertext = 88, 110, 117
headertag = 42, 161, 152
emphasis = 38, 139, 210
straightquotes = 211, 54, 130
doublequotes = 42, 161, 152
singlequotes = 42, 161, 152
dialog = 42, 161, 152
altdialog = 42, 161, 152
note = 27, 102, 96
hidden = 88, 110, 117
shortcode = 88, 110, 117
keyword = 133, 153, 0
tag = 203, 75, 22
value = 203, 75, 22
optional = 88, 110, 117
spellcheckline = 203, 75, 22

View File

@@ -2,22 +2,38 @@
name = Tango
author = Veronica Berglyd Olsen (adaptation)
##
# Colours:
# Background = 30302f : 48, 48, 47
# Foreground = eeeeec : 238, 238, 236
# Comment = 8f8f8d : 143, 143, 141
# Red = ef2929 : 239, 41, 41
# Orange = c4a000 : 196, 160, 0
# Yellow = fce94f : 252, 233, 79
# Green = 8ae234 : 138, 226, 52
# Cyan = 34e2e2 : 52, 226, 226
# Blue = 3465a4 : 114, 159, 207
# Purple = ad7fa8 : 173, 127, 168
##
[Syntax]
background = 48, 48, 47
text = 238, 238, 236
link = 115, 159, 207
headertext = 115, 159, 207
headertag = 52, 101, 164
link = 114, 159, 207
headertext = 114, 159, 207
headertag = 114, 159, 207, 160
emphasis = 196, 160, 0
straightquotes = 239, 41, 41
doublequotes = 138, 226, 52
singlequotes = 252, 233, 79
dialog = 138, 226, 52
altdialog = 252, 233, 79
note = 252, 242, 164
hidden = 143, 143, 141
shortcode = 115, 159, 207
shortcode = 114, 159, 207
keyword = 239, 41, 41
value = 173, 127, 168
optional = 115, 159, 207
tag = 173, 127, 168
value = 239, 41, 41
optional = 114, 159, 207
spellcheckline = 239, 41, 41
errorline = 138, 226, 52
replacetag = 51, 226, 226
modifier = 196, 160, 0
replacetag = 52, 226, 226
modifier = 114, 159, 207
texthighlight = 252, 233, 79, 96

View File

@@ -31,15 +31,16 @@ background = 255, 255, 255
text = 77, 77, 76
link = 66, 113, 174
headertext = 66, 113, 174
headertag = 66, 113, 174
headertag = 66, 113, 174, 160
emphasis = 245, 135, 31
straightquotes = 240, 40, 41
doublequotes = 113, 140, 0
singlequotes = 234, 183, 0
dialog = 113, 140, 0
altdialog = 234, 183, 0
note = 115, 90, 0
hidden = 142, 144, 140
shortcode = 66, 113, 174
keyword = 240, 40, 41
value = 137, 89, 168
tag = 137, 89, 168
value = 234, 183, 0
optional = 66, 113, 174
spellcheckline = 240, 40, 41
errorline = 113, 140, 0

View File

@@ -29,17 +29,18 @@ licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.
[Syntax]
background = 29, 31, 33
text = 197, 200, 198
link = 129, 162, 190
link = 197, 200, 198, 160
headertext = 129, 162, 190
headertag = 129, 162, 190
headertag = 94, 119, 139
emphasis = 222, 147, 95
straightquotes = 204, 102, 102
doublequotes = 181, 189, 104
singlequotes = 240, 198, 116
dialog = 181, 189, 104
altdialog = 240, 198, 116
note = 240, 219, 178
hidden = 150, 152, 150
shortcode = 129, 162, 190
keyword = 204, 102, 102
value = 178, 148, 187
tag = 178, 148, 187
value = 240, 198, 116
optional = 129, 162, 190
spellcheckline = 204, 102, 102
errorline = 181, 189, 104

View File

@@ -31,15 +31,16 @@ background = 0, 36, 81
text = 255, 255, 255
link = 187, 218, 255
headertext = 187, 218, 255
headertag = 187, 218, 255
headertag = 187, 218, 255, 160
emphasis = 255, 197, 143
straightquotes = 255, 157, 164
doublequotes = 209, 241, 169
singlequotes = 255, 238, 173
dialog = 209, 241, 169
altdialog = 255, 238, 173
note = 255, 247, 214
hidden = 114, 133, 183
shortcode = 187, 218, 255
keyword = 255, 157, 164
value = 235, 187, 255
tag = 235, 187, 255
value = 255, 238, 173
optional = 187, 218, 255
spellcheckline = 255, 157, 164
errorline = 209, 241, 169

View File

@@ -31,15 +31,16 @@ background = 0, 0, 0
text = 234, 234, 234
link = 122, 166, 218
headertext = 122, 166, 218
headertag = 122, 166, 218
headertag = 122, 166, 218, 160
emphasis = 231, 140, 69
straightquotes = 213, 78, 83
doublequotes = 185, 202, 74
singlequotes = 231, 197, 71
dialog = 185, 202, 74
altdialog = 231, 197, 71
note = 231, 214, 150
hidden = 150, 152, 150
shortcode = 122, 166, 218
keyword = 213, 78, 83
value = 195, 151, 216
tag = 195, 151, 216
value = 231, 197, 71
optional = 122, 166, 218
spellcheckline = 213, 78, 83
errorline = 185, 202, 74

View File

@@ -31,15 +31,16 @@ background = 45, 45, 45
text = 204, 204, 204
link = 102, 153, 204
headertext = 102, 153, 204
headertag = 102, 153, 204
headertag = 102, 153, 204, 160
emphasis = 249, 145, 57
straightquotes = 242, 119, 122
doublequotes = 153, 204, 153
singlequotes = 255, 204, 102
dialog = 153, 204, 153
altdialog = 255, 204, 102
note = 255, 230, 179
hidden = 153, 153, 153
shortcode = 102, 153, 204
keyword = 242, 119, 122
value = 204, 153, 204
tag = 204, 153, 204
value = 255, 204, 102
optional = 102, 153, 204
spellcheckline = 242, 119, 122
errorline = 153, 204, 153

View File

@@ -60,7 +60,10 @@ more contributions are listed on the project's <a href="https://crowdin.com/proj
&nbsp;&bull;&nbsp;<a href="https://github.com/stephenhutchings/typicons.font">Typicons</a> icons by Stephen Hutchings (CC BY-SA 4.0)<br>
&nbsp;&bull;&nbsp;<a href="https://github.com/chriskempson/base16">Tomorrow</a> syntax themes by Chris Kempson (MIT License)<br>
&nbsp;&bull;&nbsp;<a href="https://github.com/sdras/night-owl-vscode-theme">Owl</a> syntax themes by Sarah Drasner (MIT License)<br>
&nbsp;&bull;&nbsp;<a href="https://github.com/altercation/solarized">Solarized</a> themes by Ethan Schoonover (MIT License)
&nbsp;&bull;&nbsp;<a href="https://github.com/altercation/solarized">Solarized</a> themes by Ethan Schoonover (MIT License)<br>
&nbsp;&bull;&nbsp;<a href="https://github.com/alemvigh">Cyberpunk Night</a> theme by Anders Lemvigh (CC BY-SA 4.0)<br>
&nbsp;&bull;&nbsp;<a href="https://draculatheme.com">Dracula</a> theme by Zeno Rocha (MIT License)<br>
&nbsp;&bull;&nbsp;<a href="https://github.com/loilo/vscode-snazzy-light">Snazzy Light</a> theme by Florian Reuschel (MIT License)
</p>
<h3>Fonts</h3>

View File

@@ -24,6 +24,8 @@ link = 77, 77, 255
linkvisited = 50, 0, 80
[GUI]
helptext = 97, 97, 97
errortext = 255, 77, 77
statusnone = 50, 50, 50
statussaved = 77, 255, 77
statusunsaved = 255, 77, 77

View File

@@ -26,6 +26,7 @@ linkvisited = 102, 153, 204
[GUI]
helptext = 164, 164, 164
errortext = 255, 164, 164
statusnone = 150, 152, 150
statussaved = 39, 135, 78
statusunsaved = 138, 32, 32

View File

@@ -26,6 +26,7 @@ linkvisited = 66, 113, 174
[GUI]
helptext = 92, 92, 92
errortext = 255, 92, 92
statusnone = 120, 120, 120
statussaved = 200, 15, 39
statusunsaved = 2, 133, 37

View File

@@ -0,0 +1,47 @@
[Main]
name = Dracula
description = A dark theme with bright colours
author = Veronica Berglyd Olsen (adaptation)
credit = Zeno Rocha
url = https://draculatheme.com
license = MIT
licenseurl = https://github.com/dracula/dracula-theme/blob/main/LICENSE
icontheme = typicons_dark
##
# Colours:
# Background = 282a36 : 40, 42, 54
# Foreground = f8f8f2 : 248, 248, 242
# Current Line = 44475A : 68, 71, 90
# Comment = 6272a4 : 98, 114, 164
# Cyan = 8be9fd : 139, 233, 253
# Green = 50fa7b : 80, 250, 123
# Orange = ffb86c : 255, 184, 108
# Pink = ff79c6 : 255, 121, 198
# Purple = bd93f9 : 189, 147, 249
# Red = ff5555 : 255, 85, 85
# Yellow = f1fa8c : 241, 250, 140
##
[Palette]
window = 68, 71, 90
windowtext = 248, 248, 242
base = 40, 42, 54
alternatebase = 51, 54, 69
text = 248, 248, 242
tooltipbase = 241, 250, 140
tooltiptext = 40, 42, 54
button = 80, 83, 105
buttontext = 248, 248, 242
brighttext = 68, 71, 90
highlight = 166, 129, 218
highlightedtext = 248, 248, 242
link = 139, 233, 253
linkvisited = 139, 233, 253
[GUI]
helptext = 204, 172, 249
errortext = 255, 85, 85
statusnone = 98, 114, 164
statussaved = 80, 250, 123
statusunsaved = 255, 85, 85

View File

@@ -25,6 +25,7 @@ linkvisited = 38, 139, 210
[GUI]
helptext = 166, 161, 149
errortext = 255, 161, 149
statusnone = 88, 110, 117
statussaved = 42, 161, 152
statusunsaved = 203, 75, 22

View File

@@ -25,6 +25,7 @@ linkvisited = 38, 139, 210
[GUI]
helptext = 78, 91, 95
errortext = 255, 91, 95
statusnone = 88, 110, 117
statussaved = 42, 161, 152
statusunsaved = 203, 75, 22

View File

@@ -24,30 +24,32 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
import json
import uuid
import logging
import unicodedata
import uuid
import xml.etree.ElementTree as ET
from typing import TYPE_CHECKING, Any, Literal
from pathlib import Path
from datetime import datetime
from configparser import ConfigParser
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal, TypeVar
from urllib.parse import urljoin
from urllib.request import pathname2url
from PyQt5.QtGui import QColor, QDesktopServices
from PyQt5.QtCore import QCoreApplication, QUrl
from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontInfo
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
from novelwriter.error import logException
from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
if TYPE_CHECKING: # pragma: no cover
from typing import TypeGuard # Requires Python 3.10
logger = logging.getLogger(__name__)
_Type = TypeVar("_Type")
##
# Checker Functions
@@ -172,6 +174,11 @@ def isItemLayout(value: Any) -> TypeGuard[str]:
return isinstance(value, str) and value in nwItemLayout.__members__
def isListInstance(data: Any, check: type[_Type]) -> TypeGuard[list[_Type]]:
"""Check that all items of a list is of a given type."""
return isinstance(data, list) and all(isinstance(item, check) for item in data)
def hexToInt(value: Any, default: int = 0) -> int:
"""Convert a hex string to an integer."""
if isinstance(value, str):
@@ -272,6 +279,13 @@ def simplified(text: str) -> str:
return " ".join(str(text).strip().split())
def elide(text: str, length: int) -> str:
"""Elide a piece of text to a maximum length."""
if len(text) > (cut := max(4, length)):
return f"{text[:cut-4].rstrip()} ..."
return text
def yesNo(value: int | bool | None) -> Literal["yes", "no"]:
"""Convert a boolean evaluated variable to a yes or no."""
return "yes" if value else "no"
@@ -387,6 +401,16 @@ def cssCol(col: QColor, alpha: int | None = None) -> str:
return f"rgba({col.red()}, {col.green()}, {col.blue()}, {alpha or col.alpha()})"
def describeFont(font: QFont) -> str:
"""Describe a font in a way that can be displayed on the GUI."""
if isinstance(font, QFont):
info = QFontInfo(font)
family = info.family()
styles = [v for v in info.styleName().split() if v not in family]
return " ".join([f"{info.pointSize()} pt", family] + styles)
return "Error"
##
# Encoder Functions
##
@@ -589,5 +613,3 @@ class NWConfigParser(ConfigParser):
for i in range(min(len(data), len(result))):
result[i] = checkInt(data[i].strip(), result[i])
return result
# END Class NWConfigParser

View File

@@ -24,24 +24,24 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import sys
import json
import logging
import sys
from time import time
from pathlib import Path
from datetime import datetime
from pathlib import Path
from time import time
from PyQt5.QtGui import QFontDatabase
from PyQt5.QtCore import (
PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo,
QLocale, QStandardPaths, QSysInfo, QTranslator
)
from PyQt5.QtGui import QFont, QFontDatabase
from PyQt5.QtWidgets import QApplication
from novelwriter.error import formatException, logException
from novelwriter.common import NWConfigParser, checkInt, checkPath, formatTimeStamp
from novelwriter.common import NWConfigParser, checkInt, checkPath, describeFont, formatTimeStamp
from novelwriter.constants import nwFiles, nwUnicode
from novelwriter.error import formatException, logException
logger = logging.getLogger(__name__)
@@ -106,15 +106,15 @@ class Config:
self._recentObj = RecentProjects(self)
# General GUI Settings
self.guiLocale = self._qLocale.name()
self.guiTheme = "default" # GUI theme
self.guiSyntax = "default_light" # Syntax theme
self.guiFont = "" # Defaults to system default font in theme class
self.guiFontSize = 11 # Is overridden if system default is loaded
self.guiScale = 1.0 # Set automatically by Theme class
self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
self.lastNotes = "0x0" # The latest release notes that have been shown
self.guiLocale = self._qLocale.name()
self.guiTheme = "default" # GUI theme
self.guiSyntax = "default_light" # Syntax theme
self.guiFont = QFont() # Main GUI font
self.guiScale = 1.0 # Set automatically by Theme class
self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
self.lastNotes = "0x0" # The latest release notes that have been shown
self.nativeFont = True # Use native font dialog
# Size Settings
self._mainWinSize = [1200, 650] # Last size of the main GUI window
@@ -132,40 +132,42 @@ class Config:
self.askBeforeBackup = True # Flag for asking before running automatic backup
# Text Editor Settings
self.textFont = "" # Editor font
self.textSize = 12 # Editor font size
self.textWidth = 700 # Editor text width
self.textMargin = 40 # Editor/viewer text margin
self.tabWidth = 40 # Editor tabulator width
self.textFont = QFont() # Editor font
self.textWidth = 700 # Editor text width
self.textMargin = 40 # Editor/viewer text margin
self.tabWidth = 40 # Editor tabulator width
self.focusWidth = 800 # Focus Mode text width
self.hideFocusFooter = False # Hide document footer in Focus Mode
self.showFullPath = True # Show full document path in editor header
self.autoSelect = True # Auto-select word when applying format with no selection
self.focusWidth = 800 # Focus Mode text width
self.hideFocusFooter = False # Hide document footer in Focus Mode
self.showFullPath = True # Show full document path in editor header
self.autoSelect = True # Auto-select word when applying format with no selection
self.doJustify = False # Justify text
self.showTabsNSpaces = False # Show tabs and spaces in editor
self.showLineEndings = False # Show line endings in editor
self.showMultiSpaces = True # Highlight multiple spaces in the text
self.doJustify = False # Justify text
self.showTabsNSpaces = False # Show tabs and spaces in editor
self.showLineEndings = False # Show line endings in editor
self.showMultiSpaces = True # Highlight multiple spaces in the text
self.doReplace = True # Enable auto-replace as you type
self.doReplaceSQuote = True # Smart single quotes
self.doReplaceDQuote = True # Smart double quotes
self.doReplaceDash = True # Replace multiple hyphens with dashes
self.doReplaceDots = True # Replace three dots with ellipsis
self.doReplace = True # Enable auto-replace as you type
self.doReplaceSQuote = True # Smart single quotes
self.doReplaceDQuote = True # Smart double quotes
self.doReplaceDash = True # Replace multiple hyphens with dashes
self.doReplaceDots = True # Replace three dots with ellipsis
self.autoScroll = False # Typewriter-like scrolling
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
self.scrollPastEnd = True # Scroll past end of document, and centre cursor
self.autoScroll = False # Typewriter-like scrolling
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
self.scrollPastEnd = True # Scroll past end of document, and centre cursor
self.highlightQuotes = True # Highlight text in quotes
self.allowOpenSQuote = False # Allow open-ended single quotes
self.allowOpenDQuote = True # Allow open-ended double quotes
self.highlightEmph = True # Add colour to text emphasis
self.dialogStyle = 2 # Quote type to use for dialogue
self.allowOpenDial = True # Allow open-ended dialogue quotes
self.narratorBreak = "" # Symbol to use for narrator break
self.dialogLine = "" # Symbol to use for dialogue line
self.altDialogOpen = "" # Alternative dialog symbol, open
self.altDialogClose = "" # Alternative dialog symbol, close
self.highlightEmph = True # Add colour to text emphasis
self.stopWhenIdle = True # Stop the status bar clock when the user is idle
self.userIdleTime = 300 # Time of inactivity to consider user idle
self.incNotesWCount = True # The status bar word count includes notes
self.stopWhenIdle = True # Stop the status bar clock when the user is idle
self.userIdleTime = 300 # Time of inactivity to consider user idle
self.incNotesWCount = True # The status bar word count includes notes
# User-Selected Symbol Settings
self.fmtApostrophe = nwUnicode.U_RSQUO
@@ -359,23 +361,53 @@ class Config:
self._backupPath = checkPath(path, self._backPath)
return
def setTextFont(self, family: str | None, pointSize: int = 12) -> None:
def setGuiFont(self, value: QFont | str | None) -> None:
"""Update the GUI's font style from settings."""
if isinstance(value, QFont):
self.guiFont = value
elif value and isinstance(value, str):
self.guiFont = QFont()
self.guiFont.fromString(value)
else:
font = QFont()
fontDB = QFontDatabase()
if self.osWindows and "Arial" in fontDB.families():
# On Windows we default to Arial if possible
font.setFamily("Arial")
font.setPointSize(10)
else:
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.guiFont = font
logger.debug("GUI font set to: %s", describeFont(font))
QApplication.setFont(self.guiFont)
return
def setTextFont(self, value: QFont | str | None) -> None:
"""Set the text font if it exists. If it doesn't, or is None,
set to default font.
"""
fontDB = QFontDatabase()
fontFam = fontDB.families()
self.textSize = pointSize
if family is None or family not in fontFam:
logger.warning("Unknown font '%s'", family)
if self.osWindows and "Arial" in fontFam:
self.textFont = "Arial"
elif self.osDarwin and "Helvetica" in fontFam:
self.textFont = "Helvetica"
else:
self.textFont = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont).family()
if isinstance(value, QFont):
self.textFont = value
elif value and isinstance(value, str):
self.textFont = QFont()
self.textFont.fromString(value)
else:
self.textFont = family
fontDB = QFontDatabase()
fontFam = fontDB.families()
if self.osWindows and "Arial" in fontFam:
font = QFont()
font.setFamily("Arial")
font.setPointSize(12)
elif self.osDarwin and "Helvetica" in fontFam:
font = QFont()
font.setFamily("Helvetica")
font.setPointSize(12)
else:
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.textFont = font
logger.debug("Text font set to: %s", describeFont(font))
return
##
@@ -499,12 +531,6 @@ class Config:
(self._dataPath / "syntax").mkdir(exist_ok=True)
(self._dataPath / "themes").mkdir(exist_ok=True)
# Check if config file exists, and load it. If not, we save defaults
if (self._confPath / nwFiles.CONF_FILE).is_file():
self.loadConfig()
else:
self.saveConfig()
self._recentObj.loadCache()
self._checkOptionalPackages()
@@ -545,6 +571,14 @@ class Config:
conf = NWConfigParser()
cnfPath = self._confPath / nwFiles.CONF_FILE
if not cnfPath.exists():
# Initial file, so we just create one from defaults
self.setGuiFont(None)
self.setTextFont(None)
self.saveConfig()
return True
try:
with open(cnfPath, mode="r", encoding="utf-8") as inFile:
conf.read_file(inFile)
@@ -558,14 +592,14 @@ class Config:
# Main
sec = "Main"
self.setGuiFont(conf.rdStr(sec, "font", ""))
self.guiTheme = conf.rdStr(sec, "theme", self.guiTheme)
self.guiSyntax = conf.rdStr(sec, "syntax", self.guiSyntax)
self.guiFont = conf.rdStr(sec, "font", self.guiFont)
self.guiFontSize = conf.rdInt(sec, "fontsize", self.guiFontSize)
self.guiLocale = conf.rdStr(sec, "localisation", self.guiLocale)
self.hideVScroll = conf.rdBool(sec, "hidevscroll", self.hideVScroll)
self.hideHScroll = conf.rdBool(sec, "hidehscroll", self.hideHScroll)
self.lastNotes = conf.rdStr(sec, "lastnotes", self.lastNotes)
self.nativeFont = conf.rdBool(sec, "nativefont", self.nativeFont)
self._lastPath = conf.rdPath(sec, "lastpath", self._lastPath)
# Sizes
@@ -588,8 +622,7 @@ class Config:
# Editor
sec = "Editor"
self.textFont = conf.rdStr(sec, "textfont", self.textFont)
self.textSize = conf.rdInt(sec, "textsize", self.textSize)
self.setTextFont(conf.rdStr(sec, "textfont", ""))
self.textWidth = conf.rdInt(sec, "width", self.textWidth)
self.textMargin = conf.rdInt(sec, "margin", self.textMargin)
self.tabWidth = conf.rdInt(sec, "tabwidth", self.tabWidth)
@@ -618,9 +651,12 @@ class Config:
self.showMultiSpaces = conf.rdBool(sec, "showmultispaces", self.showMultiSpaces)
self.incNotesWCount = conf.rdBool(sec, "incnoteswcount", self.incNotesWCount)
self.showFullPath = conf.rdBool(sec, "showfullpath", self.showFullPath)
self.highlightQuotes = conf.rdBool(sec, "highlightquotes", self.highlightQuotes)
self.allowOpenSQuote = conf.rdBool(sec, "allowopensquote", self.allowOpenSQuote)
self.allowOpenDQuote = conf.rdBool(sec, "allowopendquote", self.allowOpenDQuote)
self.dialogStyle = conf.rdInt(sec, "dialogstyle", self.dialogStyle)
self.allowOpenDial = conf.rdBool(sec, "allowopendial", self.allowOpenDial)
self.narratorBreak = conf.rdStr(sec, "narratorbreak", self.narratorBreak)
self.altDialogOpen = conf.rdStr(sec, "altdialogopen", self.altDialogOpen)
self.altDialogClose = conf.rdStr(sec, "altdialogclose", self.altDialogClose)
self.dialogLine = conf.rdStr(sec, "dialogline", self.dialogLine)
self.highlightEmph = conf.rdBool(sec, "highlightemph", self.highlightEmph)
self.stopWhenIdle = conf.rdBool(sec, "stopwhenidle", self.stopWhenIdle)
self.userIdleTime = conf.rdInt(sec, "useridletime", self.userIdleTime)
@@ -666,14 +702,14 @@ class Config:
}
conf["Main"] = {
"font": self.guiFont.toString(),
"theme": str(self.guiTheme),
"syntax": str(self.guiSyntax),
"font": str(self.guiFont),
"fontsize": str(self.guiFontSize),
"localisation": str(self.guiLocale),
"hidevscroll": str(self.hideVScroll),
"hidehscroll": str(self.hideHScroll),
"lastnotes": str(self.lastNotes),
"nativefont": str(self.nativeFont),
"lastpath": str(self._lastPath),
}
@@ -696,8 +732,7 @@ class Config:
}
conf["Editor"] = {
"textfont": str(self.textFont),
"textsize": str(self.textSize),
"textfont": self.textFont.toString(),
"width": str(self.textWidth),
"margin": str(self.textMargin),
"tabwidth": str(self.tabWidth),
@@ -726,9 +761,12 @@ class Config:
"showmultispaces": str(self.showMultiSpaces),
"incnoteswcount": str(self.incNotesWCount),
"showfullpath": str(self.showFullPath),
"highlightquotes": str(self.highlightQuotes),
"allowopensquote": str(self.allowOpenSQuote),
"allowopendquote": str(self.allowOpenDQuote),
"dialogstyle": str(self.dialogStyle),
"allowopendial": str(self.allowOpenDial),
"narratorbreak": str(self.narratorBreak),
"altdialogopen": str(self.altDialogOpen),
"altdialogclose": str(self.altDialogClose),
"dialogline": str(self.dialogLine),
"highlightemph": str(self.highlightEmph),
"stopwhenidle": str(self.stopWhenIdle),
"useridletime": str(self.userIdleTime),
@@ -787,8 +825,6 @@ class Config:
logger.debug("Checking package 'pyenchant': OK")
return
# END Class Config
class RecentProjects:
@@ -857,5 +893,3 @@ class RecentProjects:
logger.debug("Removed recent: %s", path)
self.saveCache()
return
# END Class RecentProjects

View File

@@ -23,9 +23,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
from novelwriter.enum import (
nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape
)
def trConst(text: str) -> str:
@@ -58,8 +60,6 @@ class nwConst:
# Dialogs
DLG_FINISHED = 2
# END Class nwConst
class nwRegEx:
@@ -67,9 +67,7 @@ class nwRegEx:
FMT_EB = r"(?<![\w\\])([\*]{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_ST = r"(?<![\w\\])([~]{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:i|b|s|u|m|sup|sub)\])"
FMT_SV = r"(?<!\\)(\[(?i)(?:fn|footnote):)(.+?)(?<!\\)(\])"
# END Class nwRegEx
FMT_SV = r"(?<!\\)(\[(?i)(?:footnote):)(.+?)(?<!\\)(\])"
class nwShortcode:
@@ -89,15 +87,19 @@ class nwShortcode:
SUB_O = "[sub]"
SUB_C = "[/sub]"
# END Class nwShortcode
FOOTNOTE_B = "[footnote:"
COMMENT_STYLES = {
nwComment.FOOTNOTE: "[footnote:{0}]",
nwComment.COMMENT: "[comment:{0}]",
}
class nwHeaders:
H_VALID = ("H0", "H1", "H2", "H3", "H4")
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
# END Class nwHeaders
H_SIZES = {0: 2.50, 1: 2.00, 2: 1.75, 3: 1.50, 4: 1.25}
class nwFiles:
@@ -118,8 +120,6 @@ class nwFiles:
DICT_FILE = "userdict.json"
SESS_FILE = "sessions.jsonl"
# END Class nwFiles
class nwKeyWords:
@@ -153,8 +153,6 @@ class nwKeyWords:
CUSTOM_KEY: nwItemClass.CUSTOM,
}
# END Class nwKeyWords
class nwLists:
@@ -168,8 +166,6 @@ class nwLists:
nwItemClass.CUSTOM,
]
# END Class nwLists
class nwLabels:
@@ -268,6 +264,34 @@ class nwLabels:
nwBuildFmt.J_HTML: ".json",
nwBuildFmt.J_NWD: ".json",
}
SHAPES_PLAIN = {
nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"),
nwStatusShape.TRIANGLE: QT_TRANSLATE_NOOP("Constant", "Triangle"),
nwStatusShape.NABLA: QT_TRANSLATE_NOOP("Constant", "Nabla"),
nwStatusShape.DIAMOND: QT_TRANSLATE_NOOP("Constant", "Diamond"),
nwStatusShape.PENTAGON: QT_TRANSLATE_NOOP("Constant", "Pentagon"),
nwStatusShape.HEXAGON: QT_TRANSLATE_NOOP("Constant", "Hexagon"),
nwStatusShape.STAR: QT_TRANSLATE_NOOP("Constant", "Star"),
nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Constant", "Pacman"),
}
SHAPES_CIRCLE = {
nwStatusShape.CIRCLE_Q: QT_TRANSLATE_NOOP("Constant", "1/4 Circle"),
nwStatusShape.CIRCLE_H: QT_TRANSLATE_NOOP("Constant", "Half Circle"),
nwStatusShape.CIRCLE_T: QT_TRANSLATE_NOOP("Constant", "3/4 Circle"),
nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Full Circle"),
}
SHAPES_BARS = {
nwStatusShape.BARS_1: QT_TRANSLATE_NOOP("Constant", "1 Bar"),
nwStatusShape.BARS_2: QT_TRANSLATE_NOOP("Constant", "2 Bars"),
nwStatusShape.BARS_3: QT_TRANSLATE_NOOP("Constant", "3 Bars"),
nwStatusShape.BARS_4: QT_TRANSLATE_NOOP("Constant", "4 Bars"),
}
SHAPES_BLOCKS = {
nwStatusShape.BLOCK_1: QT_TRANSLATE_NOOP("Constant", "1 Block"),
nwStatusShape.BLOCK_2: QT_TRANSLATE_NOOP("Constant", "2 Blocks"),
nwStatusShape.BLOCK_3: QT_TRANSLATE_NOOP("Constant", "3 Blocks"),
nwStatusShape.BLOCK_4: QT_TRANSLATE_NOOP("Constant", "4 Blocks"),
}
FILE_FILTERS = {
"*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"),
"*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"),
@@ -302,8 +326,6 @@ class nwLabels:
"Custom": (-1.0, -1.0),
}
# END Class nwLabels
class nwHeadFmt:
@@ -329,8 +351,6 @@ class nwHeadFmt:
ODT_PAGE = "{Page}"
ODT_AUTO = "{Project} / {Author} / {Page}"
# END Class nwHeadFmt
class nwQuotes:
"""Allowed quotation marks.
@@ -361,8 +381,6 @@ class nwQuotes:
"\u300f": QT_TRANSLATE_NOOP("Constant", "Right white corner bracket"),
}
# END Class nwQuotes
class nwUnicode:
"""Supported unicode character constants and their HTML equivalents."""
@@ -497,8 +515,6 @@ class nwUnicode:
H_LTRI = "&#9664;"
H_LTRIS = "&#9666;"
# END Class nwUnicode
class nwHtmlUnicode():
@@ -554,5 +570,3 @@ class nwHtmlUnicode():
nwUnicode.U_TIMES: nwUnicode.H_TIMES,
nwUnicode.U_DIVIDE: nwUnicode.H_DIVIDE,
}
# END Class nwHtmlUnicode

View File

@@ -25,21 +25,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
import json
import uuid
import logging
import uuid
from collections.abc import Iterable
from enum import Enum
from pathlib import Path
from collections.abc import Iterable
from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
from novelwriter import CONFIG
from novelwriter.enum import nwBuildFmt
from novelwriter.error import logException
from novelwriter.common import checkUuid, isHandle, jsonEncode
from novelwriter.constants import nwFiles, nwHeadFmt
from novelwriter.core.project import NWProject
from novelwriter.enum import nwBuildFmt
from novelwriter.error import logException
logger = logging.getLogger(__name__)
@@ -76,12 +76,15 @@ SETTINGS_TEMPLATE = {
"text.includeBodyText": (bool, True),
"text.ignoredKeywords": (str, ""),
"text.addNoteHeadings": (bool, True),
"format.textFont": (str, CONFIG.textFont),
"format.textSize": (int, 12),
"format.textFont": (str, CONFIG.textFont.toString()),
"format.lineHeight": (float, 1.15, 0.75, 3.0),
"format.justifyText": (bool, False),
"format.stripUnicode": (bool, False),
"format.replaceTabs": (bool, False),
"format.keepBreaks": (bool, True),
"format.firstLineIndent": (bool, False),
"format.firstIndentWidth": (float, 1.4),
"format.indentFirstPar": (bool, False),
"format.pageUnit": (str, "cm"),
"format.pageSize": (str, "A4"),
"format.pageWidth": (float, 21.0),
@@ -93,65 +96,65 @@ SETTINGS_TEMPLATE = {
"odt.addColours": (bool, True),
"odt.pageHeader": (str, nwHeadFmt.ODT_AUTO),
"odt.pageCountOffset": (int, 0),
"odt.firstLineIndent": (bool, False),
"md.preserveBreaks": (bool, True),
"html.addStyles": (bool, True),
"html.preserveTabs": (bool, False),
}
SETTINGS_LABELS = {
"filter": QT_TRANSLATE_NOOP("Builds", "Document Filters"),
"filter.includeNovel": QT_TRANSLATE_NOOP("Builds", "Novel Documents"),
"filter.includeNotes": QT_TRANSLATE_NOOP("Builds", "Project Notes"),
"filter.includeInactive": QT_TRANSLATE_NOOP("Builds", "Inactive Documents"),
"filter": QT_TRANSLATE_NOOP("Builds", "Document Filters"),
"filter.includeNovel": QT_TRANSLATE_NOOP("Builds", "Novel Documents"),
"filter.includeNotes": QT_TRANSLATE_NOOP("Builds", "Project Notes"),
"filter.includeInactive": QT_TRANSLATE_NOOP("Builds", "Inactive Documents"),
"headings": QT_TRANSLATE_NOOP("Builds", "Headings"),
"headings.fmtTitle": QT_TRANSLATE_NOOP("Builds", "Partition Format"),
"headings.fmtChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Format"),
"headings.fmtUnnumbered": QT_TRANSLATE_NOOP("Builds", "Unnumbered Format"),
"headings.fmtScene": QT_TRANSLATE_NOOP("Builds", "Scene Format"),
"headings.fmtAltScene": QT_TRANSLATE_NOOP("Builds", "Alt. Scene Format"),
"headings.fmtSection": QT_TRANSLATE_NOOP("Builds", "Section Format"),
"headings": QT_TRANSLATE_NOOP("Builds", "Headings"),
"headings.fmtTitle": QT_TRANSLATE_NOOP("Builds", "Partition Format"),
"headings.fmtChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Format"),
"headings.fmtUnnumbered": QT_TRANSLATE_NOOP("Builds", "Unnumbered Format"),
"headings.fmtScene": QT_TRANSLATE_NOOP("Builds", "Scene Format"),
"headings.fmtAltScene": QT_TRANSLATE_NOOP("Builds", "Alt. Scene Format"),
"headings.fmtSection": QT_TRANSLATE_NOOP("Builds", "Section Format"),
"text.grpContent": QT_TRANSLATE_NOOP("Builds", "Text Content"),
"text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"),
"text.includeComments": QT_TRANSLATE_NOOP("Builds", "Include Comments"),
"text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Include Keywords"),
"text.includeBodyText": QT_TRANSLATE_NOOP("Builds", "Include Body Text"),
"text.ignoredKeywords": QT_TRANSLATE_NOOP("Builds", "Ignore These Keywords"),
"text.grpInsert": QT_TRANSLATE_NOOP("Builds", "Insert Content"),
"text.addNoteHeadings": QT_TRANSLATE_NOOP("Builds", "Add Titles for Notes"),
"text.grpContent": QT_TRANSLATE_NOOP("Builds", "Text Content"),
"text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"),
"text.includeComments": QT_TRANSLATE_NOOP("Builds", "Include Comments"),
"text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Include Keywords"),
"text.includeBodyText": QT_TRANSLATE_NOOP("Builds", "Include Body Text"),
"text.ignoredKeywords": QT_TRANSLATE_NOOP("Builds", "Ignore These Keywords"),
"text.grpInsert": QT_TRANSLATE_NOOP("Builds", "Insert Content"),
"text.addNoteHeadings": QT_TRANSLATE_NOOP("Builds", "Add Titles for Notes"),
"format.grpFormat": QT_TRANSLATE_NOOP("Builds", "Text Format"),
"format.textFont": QT_TRANSLATE_NOOP("Builds", "Font Family"),
"format.textSize": QT_TRANSLATE_NOOP("Builds", "Font Size"),
"format.lineHeight": QT_TRANSLATE_NOOP("Builds", "Line Height"),
"format.grpOptions": QT_TRANSLATE_NOOP("Builds", "Text Options"),
"format.justifyText": QT_TRANSLATE_NOOP("Builds", "Justify Text Margins"),
"format.stripUnicode": QT_TRANSLATE_NOOP("Builds", "Replace Unicode Characters"),
"format.replaceTabs": QT_TRANSLATE_NOOP("Builds", "Replace Tabs with Spaces"),
"format.grpPage": QT_TRANSLATE_NOOP("Builds", "Page Layout"),
"format.pageUnit": QT_TRANSLATE_NOOP("Builds", "Unit"),
"format.pageSize": QT_TRANSLATE_NOOP("Builds", "Page Size"),
"format.pageWidth": QT_TRANSLATE_NOOP("Builds", "Page Width"),
"format.pageHeight": QT_TRANSLATE_NOOP("Builds", "Page Height"),
"format.topMargin": QT_TRANSLATE_NOOP("Builds", "Top Margin"),
"format.bottomMargin": QT_TRANSLATE_NOOP("Builds", "Bottom Margin"),
"format.leftMargin": QT_TRANSLATE_NOOP("Builds", "Left Margin"),
"format.rightMargin": QT_TRANSLATE_NOOP("Builds", "Right Margin"),
"format.grpFormat": QT_TRANSLATE_NOOP("Builds", "Text Format"),
"format.textFont": QT_TRANSLATE_NOOP("Builds", "Text Font"),
"format.lineHeight": QT_TRANSLATE_NOOP("Builds", "Line Height"),
"format.grpOptions": QT_TRANSLATE_NOOP("Builds", "Text Options"),
"format.justifyText": QT_TRANSLATE_NOOP("Builds", "Justify Text Margins"),
"format.stripUnicode": QT_TRANSLATE_NOOP("Builds", "Replace Unicode Characters"),
"format.replaceTabs": QT_TRANSLATE_NOOP("Builds", "Replace Tabs with Spaces"),
"format.keepBreaks": QT_TRANSLATE_NOOP("Builds", "Preserve Hard Line Breaks"),
"odt": QT_TRANSLATE_NOOP("Builds", "Open Document (.odt)"),
"odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"),
"odt.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
"odt.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
"odt.firstLineIndent": QT_TRANSLATE_NOOP("Builds", "First Line Indent"),
"format.grpParIndent": QT_TRANSLATE_NOOP("Builds", "First Line Indent"),
"format.firstLineIndent": QT_TRANSLATE_NOOP("Builds", "Enable Indent"),
"format.firstIndentWidth": QT_TRANSLATE_NOOP("Builds", "Indent Width"),
"format.indentFirstPar": QT_TRANSLATE_NOOP("Builds", "Indent First Paragraph"),
"md": QT_TRANSLATE_NOOP("Builds", "Markdown (.md)"),
"md.preserveBreaks": QT_TRANSLATE_NOOP("Builds", "Preserve Hard Line Breaks"),
"format.grpPage": QT_TRANSLATE_NOOP("Builds", "Page Layout"),
"format.pageUnit": QT_TRANSLATE_NOOP("Builds", "Unit"),
"format.pageSize": QT_TRANSLATE_NOOP("Builds", "Page Size"),
"format.pageWidth": QT_TRANSLATE_NOOP("Builds", "Page Width"),
"format.pageHeight": QT_TRANSLATE_NOOP("Builds", "Page Height"),
"format.topMargin": QT_TRANSLATE_NOOP("Builds", "Top Margin"),
"format.bottomMargin": QT_TRANSLATE_NOOP("Builds", "Bottom Margin"),
"format.leftMargin": QT_TRANSLATE_NOOP("Builds", "Left Margin"),
"format.rightMargin": QT_TRANSLATE_NOOP("Builds", "Right Margin"),
"html": QT_TRANSLATE_NOOP("Builds", "HTML (.html)"),
"html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
"html.preserveTabs": QT_TRANSLATE_NOOP("Builds", "Preserve Tab Characters"),
"odt": QT_TRANSLATE_NOOP("Builds", "Open Document (.odt)"),
"odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"),
"odt.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
"odt.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
"html": QT_TRANSLATE_NOOP("Builds", "HTML (.html)"),
"html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
"html.preserveTabs": QT_TRANSLATE_NOOP("Builds", "Preserve Tab Characters"),
}
@@ -165,8 +168,6 @@ class FilterMode(Enum):
SKIPPED = 4
ROOT = 5
# END Enum FilterMode
class BuildSettings:
"""Core: Build Settings Class
@@ -481,8 +482,6 @@ class BuildSettings:
return
# END Class BuildSettings
class BuildCollection:
"""Core: Build Collection Class
@@ -630,5 +629,3 @@ class BuildCollection:
return False
return True
# END Class BuildCollection

View File

@@ -104,7 +104,7 @@ class DocMerger:
docText = self._project.storage.getDocumentText(srcHandle).rstrip("\n")
if addComment:
docInfo = srcItem.describeMe()
docSt, _ = srcItem.getImportStatus(incIcon=False)
docSt, _ = srcItem.getImportStatus()
cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n"
docText = cmtLine + docText
@@ -129,8 +129,6 @@ class DocMerger:
return status
# END Class DocMerger
class DocSplitter:
"""Document tool for splitting a document into a set of new
@@ -257,8 +255,6 @@ class DocSplitter:
return
# END Class DocSplitter
class DocDuplicator:
"""A class that will duplicate all documents and folders starting
@@ -297,8 +293,6 @@ class DocDuplicator:
nHandle = None
return
# END Class DocDuplicator
class DocSearch:
@@ -370,23 +364,11 @@ class DocSearch:
def _buildPattern(self, search: str) -> str:
"""Build the search pattern string."""
if self._escape:
if CONFIG.verQtValue >= 0x050f00:
search = QRegularExpression.escape(search)
else:
# For older Qt versions, we escape manually
escaped = ""
for c in search:
if c.isalnum() or c == "_":
escaped += c
else:
escaped += f"\\{c}"
search = escaped
search = QRegularExpression.escape(search)
if self._words:
search = f"(?:^|\\b){search}(?:$|\\b)"
return search
# END Class DocSearch
class ProjectBuilder:
"""A class to build a new project from a set of user-defined
@@ -619,5 +601,3 @@ class ProjectBuilder:
return False
return True
# END Class ProjectBuilder

View File

@@ -25,22 +25,23 @@ from __future__ import annotations
import logging
from pathlib import Path
from collections.abc import Iterable
from pathlib import Path
from PyQt5.QtGui import QFont, QFontInfo
from PyQt5.QtGui import QFont
from novelwriter import CONFIG
from novelwriter.constants import nwLabels
from novelwriter.core.buildsettings import BuildSettings
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
from novelwriter.core.tohtml import ToHtml
from novelwriter.core.tokenizer import Tokenizer
from novelwriter.core.tomarkdown import ToMarkdown
from novelwriter.core.toodt import ToOdt
from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument
from novelwriter.enum import nwBuildFmt
from novelwriter.error import formatException, logException
from novelwriter.constants import nwLabels
from novelwriter.core.item import NWItem
from novelwriter.core.tomd import ToMarkdown
from novelwriter.core.toodt import ToOdt
from novelwriter.core.tohtml import ToHtml
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer
from novelwriter.core.buildsettings import BuildSettings
logger = logging.getLogger(__name__)
@@ -54,7 +55,7 @@ class NWBuildDocument:
__slots__ = (
"_project", "_build", "_queue", "_error", "_cache", "_count",
"_outline", "_preview"
"_outline",
)
def __init__(self, project: NWProject, build: BuildSettings) -> None:
@@ -65,7 +66,6 @@ class NWBuildDocument:
self._cache = None
self._count = False
self._outline = False
self._preview = False
return
##
@@ -99,15 +99,6 @@ class NWBuildDocument:
self._outline = state
return
def setPreviewMode(self, state: bool) -> None:
"""Set the preview mode of the build. This also enables stats
count and outline mode.
"""
self._preview = state
self._outline = state
self._count = state
return
##
# Special Methods
##
@@ -134,6 +125,32 @@ class NWBuildDocument:
self._queue.append(item.itemHandle)
return
def iterBuildPreview(self, theme: TextDocumentTheme) -> Iterable[tuple[int, bool]]:
"""Build a preview QTextDocument."""
makeObj = ToQTextDocument(self._project)
filtered = self._setupBuild(makeObj)
self._outline = True
self._count = True
font = QFont()
font.fromString(self._build.getStr("format.textFont"))
makeObj.initDocument(font, theme)
for i, tHandle in enumerate(self._queue):
self._error = None
if filtered.get(tHandle, (False, 0))[0]:
yield i, self._doBuild(makeObj, tHandle)
else:
yield i, False
makeObj.appendFootnotes()
self._error = None
self._cache = makeObj
return
def iterBuild(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
"""Wrapper for builders based on format."""
if bFormat in (nwBuildFmt.ODT, nwBuildFmt.FODT):
@@ -182,8 +199,6 @@ class NWBuildDocument:
makeObj = ToHtml(self._project)
filtered = self._setupBuild(makeObj)
makeObj.setPreview(self._preview)
makeObj.setLinkHeadings(self._preview)
for i, tHandle in enumerate(self._queue):
self._error = None
if filtered.get(tHandle, (False, 0))[0]:
@@ -191,7 +206,9 @@ class NWBuildDocument:
else:
yield i, False
if not (self._build.getBool("html.preserveTabs") or self._preview):
makeObj.appendFootnotes()
if not self._build.getBool("html.preserveTabs"):
makeObj.replaceTabs()
self._error = None
@@ -214,16 +231,10 @@ class NWBuildDocument:
makeObj = ToMarkdown(self._project)
filtered = self._setupBuild(makeObj)
if extendedMd:
makeObj.setExtendedMarkdown()
else:
makeObj.setStandardMarkdown()
makeObj.setExtendedMarkdown(extendedMd)
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
makeObj.setPreserveBreaks(self._build.getBool("md.preserveBreaks"))
for i, tHandle in enumerate(self._queue):
self._error = None
if filtered.get(tHandle, (False, 0))[0]:
@@ -231,6 +242,8 @@ class NWBuildDocument:
else:
yield i, False
makeObj.appendFootnotes()
self._error = None
self._cache = makeObj
@@ -281,13 +294,9 @@ class NWBuildDocument:
def _setupBuild(self, bldObj: Tokenizer) -> dict:
"""Configure the build object."""
# Get Settings
textFont = self._build.getStr("format.textFont")
textSize = self._build.getInt("format.textSize")
fontFamily = textFont or CONFIG.textFont
bldFont = QFont(fontFamily, textSize)
fontInfo = QFontInfo(bldFont)
textFixed = fontInfo.fixedPitch()
textFont = QFont(CONFIG.textFont)
textFont.fromString(self._build.getStr("format.textFont"))
bldObj.setFont(textFont)
bldObj.setTitleFormat(
self._build.getStr("headings.fmtTitle"),
@@ -326,9 +335,14 @@ class NWBuildDocument:
self._build.getBool("headings.breakScene")
)
bldObj.setFont(fontFamily, textSize, textFixed)
bldObj.setJustify(self._build.getBool("format.justifyText"))
bldObj.setLineHeight(self._build.getFloat("format.lineHeight"))
bldObj.setKeepLineBreaks(self._build.getBool("format.keepBreaks"))
bldObj.setFirstLineIndent(
self._build.getBool("format.firstLineIndent"),
self._build.getFloat("format.firstIndentWidth"),
self._build.getBool("format.indentFirstPar"),
)
bldObj.setBodyText(self._build.getBool("text.includeBodyText"))
bldObj.setSynopsis(self._build.getBool("text.includeSynopsis"))
@@ -346,7 +360,6 @@ class NWBuildDocument:
bldObj.setHeaderFormat(
self._build.getStr("odt.pageHeader"), self._build.getInt("odt.pageCountOffset")
)
bldObj.setFirstLineIndent(self._build.getBool("odt.firstLineIndent"))
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))
@@ -397,5 +410,3 @@ class NWBuildDocument:
return False
return True
# END Class NWBuildDocument

View File

@@ -26,14 +26,14 @@ from __future__ import annotations
import hashlib
import logging
from pathlib import Path
from time import time
from typing import TYPE_CHECKING
from pathlib import Path
from novelwriter.enum import nwItemLayout, nwItemClass
from novelwriter.error import formatException, logException
from novelwriter.common import formatTimeStamp, isHandle
from novelwriter.core.item import NWItem
from novelwriter.enum import nwItemClass, nwItemLayout
from novelwriter.error import formatException, logException
if TYPE_CHECKING: # pragma: no cover
from novelwriter.core.project import NWProject
@@ -359,5 +359,3 @@ class NWDocument:
logger.debug("Unknown meta data: '%s'", metaLine.strip())
return
# END Class NWDocument

View File

@@ -29,17 +29,20 @@ from __future__ import annotations
import json
import logging
import random
from time import time
from typing import TYPE_CHECKING
from pathlib import Path
from collections.abc import ItemsView, Iterable
from pathlib import Path
from time import time
from typing import TYPE_CHECKING, Literal
from novelwriter import SHARED
from novelwriter.enum import nwComment, nwItemClass, nwItemType, nwItemLayout
from novelwriter.common import (
checkInt, isHandle, isItemClass, isListInstance, isTitleTag, jsonEncode
)
from novelwriter.constants import nwFiles, nwHeaders, nwKeyWords
from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
from novelwriter.common import checkInt, isHandle, isItemClass, isTitleTag, jsonEncode
from novelwriter.constants import nwFiles, nwKeyWords, nwHeaders
from novelwriter.text.counting import standardCounter
if TYPE_CHECKING: # pragma: no cover
@@ -48,7 +51,12 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__)
TT_NONE = "T0000"
T_NoteTypes = Literal["footnotes", "comments"]
TT_NONE = "T0000" # Default title key
MAX_RETRY = 1000 # Key generator recursion limit
KEY_SOURCE = "0123456789bcdfghjklmnpqrstvwxz"
NOTE_TYPES: list[T_NoteTypes] = ["footnotes", "comments"]
class NWIndex:
@@ -301,9 +309,9 @@ class NWIndex:
def _scanActive(self, tHandle: str, nwItem: NWItem, text: str, tags: dict[str, bool]) -> None:
"""Scan an active document for meta data."""
nTitle = 0 # Line Number of the previous title
cTitle = TT_NONE # Tag of the current title
pTitle = TT_NONE # Tag of the previous title
nTitle = 0 # Line Number of the previous title
cTitle = TT_NONE # Tag of the current title
pTitle = TT_NONE # Tag of the previous title
canSetHead = True # First heading has not yet been set
lines = text.splitlines()
@@ -335,10 +343,11 @@ class NWIndex:
self._indexKeyword(tHandle, line, cTitle, nwItem.itemClass, tags)
elif line.startswith("%"):
if cTitle != TT_NONE:
cStyle, cText, _ = processComment(line)
if cStyle in (nwComment.SYNOPSIS, nwComment.SHORT):
self._itemIndex.setHeadingSynopsis(tHandle, cTitle, cText)
cStyle, cKey, cText, _, _ = processComment(line)
if cStyle in (nwComment.SYNOPSIS, nwComment.SHORT):
self._itemIndex.setHeadingSynopsis(tHandle, cTitle, cText)
elif cStyle == nwComment.FOOTNOTE:
self._itemIndex.addNoteKey(tHandle, "footnotes", cKey)
# Count words for remaining text after last heading
if pTitle != TT_NONE:
@@ -506,6 +515,14 @@ class NWIndex:
name, _, display = text.partition("|")
return name.rstrip(), display.lstrip()
def newCommentKey(self, tHandle: str, style: nwComment) -> str:
"""Generate a new key for a comment style."""
if style == nwComment.FOOTNOTE:
return self._itemIndex.genNewNoteKey(tHandle, "footnotes")
elif style == nwComment.COMMENT:
return self._itemIndex.genNewNoteKey(tHandle, "comments")
return "err"
##
# Extract Data
##
@@ -690,12 +707,9 @@ class NWIndex:
return tName, tClass, iItem, None if iItem is None else iItem[tHeading]
return "", "", None, None
# END Class NWIndex
# =============================================================================================== #
# The Tags Index Object
# =============================================================================================== #
# The Tags Index Object
# =====================
class TagsIndex:
"""Core: Tags Index Wrapper Class
@@ -790,7 +804,7 @@ class TagsIndex:
for key, entry in data.items():
if not isinstance(key, str):
raise ValueError("tagsIndex keys must be a string")
raise ValueError("tagsIndex key must be a string")
if not isinstance(entry, dict):
raise ValueError("tagsIndex entry is not a dict")
@@ -815,12 +829,9 @@ class TagsIndex:
return
# END Class TagsIndex
# =============================================================================================== #
# The Item Index Objects
# =============================================================================================== #
# The Item Index Objects
# ======================
class ItemIndex:
"""Core: Item Index Wrapper Class
@@ -950,6 +961,25 @@ class ItemIndex:
self._items[tHandle].addHeadingRef(sTitle, tagKeys, refType)
return
def addNoteKey(self, tHandle: str, style: T_NoteTypes, key: str) -> None:
"""Set notes key for a given item."""
if tHandle in self._items:
self._items[tHandle].addNoteKey(style, key)
return
def genNewNoteKey(self, tHandle: str, style: T_NoteTypes) -> str:
"""Set notes key for a given item."""
if style in NOTE_TYPES and (item := self._items.get(tHandle)):
keys = set()
for entry in self._items.values():
keys.update(entry.noteKeys(style))
for _ in range(MAX_RETRY):
key = style[:1] + "".join(random.choices(KEY_SOURCE, k=4))
if key not in keys:
item.addNoteKey(style, key)
return key
return "err"
##
# Pack/Unpack
##
@@ -978,8 +1008,6 @@ class ItemIndex:
return
# END Class ItemIndex
class IndexItem:
"""Core: Single Index Item Class
@@ -991,12 +1019,13 @@ class IndexItem:
must be reset each time the item is re-indexed.
"""
__slots__ = ("_handle", "_item", "_headings", "_count")
__slots__ = ("_handle", "_item", "_headings", "_count", "_notes")
def __init__(self, tHandle: str, nwItem: NWItem) -> None:
self._handle = tHandle
self._item = nwItem
self._headings: dict[str, IndexHeading] = {TT_NONE: IndexHeading(TT_NONE)}
self._notes: dict[str, set[str]] = {}
self._count = 0
return
@@ -1064,6 +1093,13 @@ class IndexItem:
self._headings[sTitle].addReference(tagKey, refType)
return
def addNoteKey(self, style: T_NoteTypes, key: str) -> None:
"""Add a note key to the index."""
if style not in self._notes:
self._notes[style] = set()
self._notes[style].add(key)
return
##
# Data Methods
##
@@ -1085,6 +1121,10 @@ class IndexItem:
self._count += 1
return f"T{self._count:04d}"
def noteKeys(self, style: T_NoteTypes) -> set[str]:
"""Return a set of all note keys."""
return self._notes.get(style, set())
##
# Pack/Unpack
##
@@ -1103,6 +1143,8 @@ class IndexItem:
data["headings"] = heads
if refs:
data["references"] = refs
if self._notes:
data["notes"] = {style: list(keys) for style, keys in self._notes.items()}
return data
@@ -1116,9 +1158,15 @@ class IndexItem:
tHeading.unpackData(hData)
tHeading.unpackReferences(references.get(sTitle, {}))
self.addHeading(tHeading)
return
# END Class IndexItem
for style, keys in data.get("notes", {}).items():
if style not in NOTE_TYPES:
raise ValueError("The notes style is invalid")
if not isListInstance(keys, str):
raise ValueError("The notes keys must be a list of strings")
self._notes[style] = set(keys)
return
class IndexHeading:
@@ -1295,25 +1343,47 @@ class IndexHeading:
raise ValueError("The itemIndex contains an invalid reference type")
return
# END Class IndexHeading
# Text Processing Functions
# =========================
# =============================================================================================== #
# Text Processing Functions
# =============================================================================================== #
CLASSIFIERS = {
"short": nwComment.SHORT,
MODIFIERS = {
"synopsis": nwComment.SYNOPSIS,
"short": nwComment.SHORT,
"note": nwComment.NOTE,
"footnote": nwComment.FOOTNOTE,
}
KEY_REQ = {
"synopsis": 0, # Key not allowed
"short": 0, # Key not allowed
"note": 1, # Key optional
"footnote": 2, # Key required
}
def processComment(text: str) -> tuple[nwComment, str, int]:
"""Extract comment style and text. Should only be called on text
starting with a %.
def _checkModKey(modifier: str, key: str) -> bool:
"""Check if a modifier and key set are ok."""
if modifier in MODIFIERS:
if key == "":
return KEY_REQ[modifier] < 2
elif key.replace("_", "").isalnum():
return KEY_REQ[modifier] > 0
return False
def processComment(text: str) -> tuple[nwComment, str, str, int, int]:
"""Extract comment style, key and text. Should only be called on
text starting with a %.
"""
check = text[1:].lstrip()
classifier, _, content = check.partition(":")
if content and (clean := classifier.strip().lower()) in CLASSIFIERS:
return CLASSIFIERS[clean], content.strip(), text.find(":") + 1
return nwComment.PLAIN, check, 0
if text[:2] == "%~":
return nwComment.IGNORE, "", text[2:].lstrip(), 0, 0
check = text[1:].strip()
start, _, content = check.partition(":")
modifier, _, key = start.rstrip().partition(".")
if content and (clean := modifier.lower()) and _checkModKey(clean, key):
col = text.find(":") + 1
dot = text.find(".", 0, col) + 1
return MODIFIERS[clean], key, content.lstrip(), dot, col
return nwComment.PLAIN, "", check, 0, 0

View File

@@ -25,15 +25,16 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any, Literal, overload
from typing import TYPE_CHECKING, Any
from PyQt5.QtGui import QIcon
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
from novelwriter.common import (
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified, yesNo
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified,
yesNo
)
from novelwriter.constants import nwHeaders, nwLabels, trConst
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
if TYPE_CHECKING: # pragma: no cover
from novelwriter.core.project import NWProject
@@ -308,25 +309,15 @@ class NWItem:
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
@overload # pragma: no cover
def getImportStatus(self, incIcon: Literal[True] = True) -> tuple[str, QIcon]:
pass
@overload # pragma: no cover
def getImportStatus(self, incIcon: Literal[False]) -> tuple[str, None]:
pass
def getImportStatus(self, incIcon=True):
def getImportStatus(self) -> tuple[str, QIcon]:
"""Return the relevant importance or status label and icon for
the current item based on its class.
"""
if self.isNovelLike():
stName = self._project.data.itemStatus.name(self._status)
stIcon = self._project.data.itemStatus.icon(self._status) if incIcon else None
entry = self._project.data.itemStatus[self._status]
else:
stName = self._project.data.itemImport.name(self._import)
stIcon = self._project.data.itemImport.icon(self._import) if incIcon else None
return stName, stIcon
entry = self._project.data.itemImport[self._import]
return entry.name, entry.icon
##
# Checker Methods
@@ -567,5 +558,3 @@ class NWItem:
"""Save the initial word count."""
self._initCount = self._wordCount
return
# END Class NWItem

View File

@@ -28,12 +28,12 @@ import json
import logging
from enum import Enum
from typing import TYPE_CHECKING, Any, TypeVar
from pathlib import Path
from typing import TYPE_CHECKING, Any, TypeVar
from novelwriter.error import logException
from novelwriter.common import checkBool, checkFloat, checkInt, checkString, jsonEncode
from novelwriter.constants import nwFiles
from novelwriter.error import logException
if TYPE_CHECKING: # pragma: no cover
from novelwriter.core.project import NWProject
@@ -218,5 +218,3 @@ class OptionState:
if value in lookup.__members__:
return lookup[value]
return default
# END Class OptionState

View File

@@ -26,33 +26,33 @@ from __future__ import annotations
import json
import logging
from collections.abc import Iterable
from enum import Enum
from functools import partial
from pathlib import Path
from time import time
from typing import TYPE_CHECKING
from pathlib import Path
from functools import partial
from collections.abc import Iterable
from PyQt5.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED, __version__, __hexversion__
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
from novelwriter.error import logException
from novelwriter.constants import trConst, nwLabels
from novelwriter.core.tree import NWTree
from novelwriter import CONFIG, SHARED, __hexversion__, __version__
from novelwriter.common import (
checkStringNone, formatInt, formatTimeStamp, getFileSize, hexToInt,
makeFileNameSafe, minmax
)
from novelwriter.constants import nwLabels, trConst
from novelwriter.core.index import NWIndex
from novelwriter.core.options import OptionState
from novelwriter.core.storage import NWStorage, NWStorageOpen
from novelwriter.core.sessions import NWSessionLog
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.core.projectdata import NWProjectData
from novelwriter.common import (
checkStringNone, formatInt, formatTimeStamp, getFileSize, hexToInt, makeFileNameSafe, minmax
)
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.core.sessions import NWSessionLog
from novelwriter.core.storage import NWStorage, NWStorageOpen
from novelwriter.core.tree import NWTree
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
if TYPE_CHECKING: # pragma: no cover
from novelwriter.core.item import NWItem
from novelwriter.core.status import NWStatus
logger = logging.getLogger(__name__)
@@ -64,8 +64,6 @@ class NWProjectState(Enum):
RECOVERY = 2
READY = 3
# END Enum NWProjectState
class NWProject:
@@ -461,14 +459,14 @@ class NWProject:
def setDefaultStatusImport(self) -> None:
"""Set the default status and importance values."""
self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100))
self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0))
self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0))
self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0))
self._data.itemImport.write(None, self.tr("New"), (100, 100, 100))
self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0))
self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0))
self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0))
self._data.itemStatus.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0)
self._data.itemStatus.add(None, self.tr("Note"), (200, 50, 0), "SQUARE", 0)
self._data.itemStatus.add(None, self.tr("Draft"), (200, 150, 0), "SQUARE", 0)
self._data.itemStatus.add(None, self.tr("Finished"), (50, 200, 0), "SQUARE", 0)
self._data.itemImport.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0)
self._data.itemImport.add(None, self.tr("Minor"), (200, 50, 0), "SQUARE", 0)
self._data.itemImport.add(None, self.tr("Major"), (200, 150, 0), "SQUARE", 0)
self._data.itemImport.add(None, self.tr("Main"), (50, 200, 0), "SQUARE", 0)
return
def setProjectLang(self, language: str | None) -> None:
@@ -491,14 +489,6 @@ class NWProject:
self.setProjectChanged(True)
return
def setStatusColours(self, new: list[dict], deleted: list[str]) -> bool:
"""Update the list of novel file status flags."""
return self._setStatusImport(new, deleted, self._data.itemStatus)
def setImportColours(self, new: list[dict], deleted: list[str]) -> bool:
"""Update the list of note file importance flags."""
return self._setStatusImport(new, deleted, self._data.itemImport)
def setProjectChanged(self, status: bool) -> bool:
"""Toggle the project changed flag, and propagate the
information to the GUI statusbar.
@@ -584,28 +574,6 @@ class NWProject:
# Internal Functions
##
def _setStatusImport(self, new: list[dict], delete: list[str], target: NWStatus) -> bool:
"""Update the list of novel file status or importance flags, and
delete those that have been requested deleted.
"""
if not (new or delete):
return False
order = []
for entry in new:
key = entry.get("key", None)
name = entry.get("name", "")
cols = entry.get("cols", (100, 100, 100))
if name:
order.append(target.write(key, name, cols))
for key in delete:
target.remove(key)
target.reorder(order)
return True
def _loadProjectLocalisation(self) -> bool:
"""Load the language data for the current project language."""
if self._data.language is None or CONFIG._nwLangPath is None:
@@ -626,5 +594,3 @@ class NWProject:
return False
return True
# END Class NWProject

View File

@@ -23,8 +23,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import uuid
import logging
import uuid
from typing import TYPE_CHECKING, Any
@@ -322,5 +322,3 @@ class NWProjectData:
self._autoReplace[key] = simplified(entry)
self._project.setProjectChanged(True)
return
# END Class NWProjectData

View File

@@ -29,24 +29,24 @@ import logging
import xml.etree.ElementTree as ET
from enum import Enum
from pathlib import Path
from time import time
from typing import TYPE_CHECKING
from pathlib import Path
from novelwriter import __version__, __hexversion__
from novelwriter import __hexversion__, __version__
from novelwriter.common import (
checkBool, checkInt, checkString, checkStringNone, formatTimeStamp,
hexToInt, simplified, xmlIndent, yesNo
)
if TYPE_CHECKING: # pragma: no cover
from novelwriter.core.status import NWStatus
from novelwriter.core.projectdata import NWProjectData
from novelwriter.core.status import NWStatus
logger = logging.getLogger(__name__)
FILE_VERSION = "1.5" # The current project file format version
FILE_REVISION = "3" # The current project file format revision
FILE_REVISION = "4" # The current project file format revision
HEX_VERSION = 0x0105
NUM_VERSION = {
@@ -70,8 +70,6 @@ class XMLReadState(Enum):
PARSED_OK = 5
WAS_LEGACY = 6
# END Class XMLReadState
class ProjectXMLReader:
"""Core: Project XML Reader
@@ -109,6 +107,8 @@ class ProjectXMLReader:
Rev 2: Drops the title node from project and adds the TEMPLATE
class for items. 2.3 Beta 1.
Rev 3: Added TEMPLATE class. 2.3.
Rev 4: Added shape attribute to status and importance entry
nodes. 2.5.
"""
def __init__(self, path: str | Path) -> None:
@@ -356,8 +356,8 @@ class ProjectXMLReader:
logger.debug("Parsing <content> section (legacy format)")
# Create maps to look up name -> key for status and importance
statusMap = {entry.get("name"): key for key, entry in data.itemStatus.items()}
importMap = {entry.get("name"): key for key, entry in data.itemImport.items()}
sMap: dict[str | None, str] = {e.name: k for k, e in data.itemStatus.iterItems()}
iMap: dict[str | None, str] = {e.name: k for k, e in data.itemImport.iterItems()}
for xItem in xSection:
if xItem.tag != "item":
@@ -404,9 +404,9 @@ class ProjectXMLReader:
# Status was split into separate status/import with a key in 1.4
if item.get("class", "") in ("NOVEL", "ARCHIVE"):
name["status"] = statusMap.get(tmpStatus, None)
name["status"] = sMap.get(tmpStatus, None)
else:
name["import"] = importMap.get(tmpStatus, None)
name["import"] = iMap.get(tmpStatus, None)
# A number of layouts were removed in 1.3
if item.get("layout", "") in (
@@ -436,7 +436,8 @@ class ProjectXMLReader:
green = checkInt(xEntry.attrib.get("green", 0), 0)
blue = checkInt(xEntry.attrib.get("blue", 0), 0)
count = checkInt(xEntry.attrib.get("count", 0), 0)
sObject.write(key, xEntry.text or "", (red, green, blue), count)
shape = xEntry.attrib.get("shape", "")
sObject.add(key, xEntry.text or "", (red, green, blue), shape, count)
return
def _parseDictKeyText(self, xItem: ET.Element) -> dict:
@@ -455,8 +456,6 @@ class ProjectXMLReader:
"""
return {xNode.tag: checkString(xNode.text, "") for xNode in xItem}
# END Class ProjectXMLReader
class ProjectXMLWriter:
"""Core: Project XML Writer
@@ -576,5 +575,3 @@ class ProjectXMLWriter:
xEntry = ET.SubElement(xItem, "entry", attrib={"key": key})
xEntry.text = str(value) or ""
return
# END Class ProjectXMLWriter

View File

@@ -26,14 +26,14 @@ from __future__ import annotations
import json
import logging
from collections.abc import Iterable
from pathlib import Path
from time import time
from typing import TYPE_CHECKING
from pathlib import Path
from collections.abc import Iterable
from novelwriter.error import logException
from novelwriter.common import formatTimeStamp
from novelwriter.constants import nwFiles
from novelwriter.error import logException
if TYPE_CHECKING: # pragma: no cover
from novelwriter.core.project import NWProject
@@ -135,5 +135,3 @@ class NWSessionLog:
"novel": novel, "notes": notes, "idle": idle,
})
return f"{data}\n"
# END Class NWSessionLog

View File

@@ -27,14 +27,14 @@ from __future__ import annotations
import json
import logging
from typing import TYPE_CHECKING
from pathlib import Path
from collections.abc import Iterator
from pathlib import Path
from typing import TYPE_CHECKING
from PyQt5.QtCore import QLocale
from novelwriter.error import logException
from novelwriter.constants import nwFiles
from novelwriter.error import logException
if TYPE_CHECKING: # pragma: no cover
from novelwriter.core.project import NWProject
@@ -164,11 +164,10 @@ class NWSpellEnchant:
name = ""
return tag, name
# END Class NWSpellEnchant
class FakeEnchant:
"""Fallback for when Enchant is selected, but not installed."""
def __init__(self) -> None:
class FakeProvider:
@@ -188,8 +187,6 @@ class FakeEnchant:
def add_to_session(self, word: str) -> None:
return
# END Class FakeEnchant
class UserDictionary:
@@ -240,5 +237,3 @@ class UserDictionary:
logger.error("Failed to save user dictionary")
logException()
return
# END Class UserDictionary

View File

@@ -24,17 +24,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import random
import dataclasses
import logging
import random
from collections.abc import Iterable
from typing import TYPE_CHECKING, Literal
from collections.abc import ItemsView, Iterable, Iterator, KeysView, ValuesView
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor
from PyQt5.QtCore import QRectF
from PyQt5.QtCore import QPointF, Qt
from PyQt5.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygonF
from novelwriter import CONFIG
from novelwriter.common import minmax, simplified
from novelwriter import SHARED
from novelwriter.common import simplified
from novelwriter.enum import nwStatusShape
from novelwriter.types import QtPaintAnitAlias, QtTransparent
if TYPE_CHECKING: # pragma: no cover
@@ -43,83 +45,92 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__)
@dataclasses.dataclass
class StatusEntry:
name: str
color: QColor
shape: nwStatusShape
icon: QIcon
count: int = 0
@classmethod
def duplicate(cls, source: StatusEntry) -> StatusEntry:
"""Create a deep copy of the source object."""
cls = dataclasses.replace(source)
cls.color = QColor(source.color)
cls.icon = QIcon(source.icon)
return cls
NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0)
class NWStatus:
STATUS = 1
IMPORT = 2
STATUS = "s"
IMPORT = "i"
def __init__(self, kind: Literal[1, 2]) -> None:
__slots__ = ("_store", "_default", "_prefix", "_height")
self._type = kind
self._store = {}
def __init__(self, prefix: Literal["s", "i"]) -> None:
self._store: dict[str, StatusEntry] = {}
self._default = None
self._iPX = CONFIG.pxInt(24)
pA = CONFIG.pxInt(2)
pB = CONFIG.pxInt(20)
pR = float(CONFIG.pxInt(4))
self._iconPath = QPainterPath()
self._iconPath.addRoundedRect(QRectF(pA, pA, pB, pB), pR, pR)
self._defaultIcon = self._createIcon(100, 100, 100)
if self._type == self.STATUS:
self._prefix = "s"
elif self._type == self.IMPORT:
self._prefix = "i"
else:
raise Exception("This is a bug!")
self._prefix = prefix[:1]
self._height = SHARED.theme.baseIconHeight
return
def write(self, key: str | None, name: str, col: tuple, count: int | None = None) -> str:
def __len__(self) -> int:
return len(self._store)
def __getitem__(self, key: str | None) -> StatusEntry:
"""Return the entry associated with a given key."""
if key and key in self._store:
return self._store[key]
elif self._default is not None:
return self._store[self._default]
return NO_ENTRY
##
# Methods
##
def add(self, key: str | None, name: str, color: tuple[int, int, int],
shape: str, count: int) -> str:
"""Add or update a status entry. If the key is invalid, a new
key is generated.
"""
if not self._isKey(key):
key = self._newKey()
if not isinstance(col, tuple):
col = (100, 100, 100)
if len(col) != 3:
col = (100, 100, 100)
if isinstance(color, tuple) and len(color) == 3:
qColor = QColor(*color)
else:
qColor = QColor(100, 100, 100)
cR = minmax(col[0], 0, 255)
cG = minmax(col[1], 0, 255)
cB = minmax(col[2], 0, 255)
try:
iShape = nwStatusShape[shape]
except KeyError:
iShape = nwStatusShape.SQUARE
key = self._checkKey(key)
name = simplified(name)
if count is None:
count = self._store.get(key, {}).get("count", 0)
self._store[key] = {
"name": name,
"icon": self._createIcon(cR, cG, cB),
"cols": (cR, cG, cB),
"count": count,
}
icon = self.createIcon(self._height, qColor, iShape)
self._store[key] = StatusEntry(name, qColor, iShape, icon, count)
if self._default is None:
self._default = key
return key
def remove(self, key: str) -> bool:
"""Remove an entry in the list, except if the count > 0."""
if key not in self._store:
return False
if self._store[key]["count"] > 0:
return False
def update(self, update: list[tuple[str | None, StatusEntry]]) -> None:
"""Update the list of statuses, and from removed list."""
self._store.clear()
for key, entry in update:
self._store[self._checkKey(key)] = entry
del self._store[key]
# Check if we need a new default
if self._default not in self._store:
self._default = next(iter(self._store)) if self._store else None
keys = list(self._store.keys())
if key == self._default:
if len(keys) > 0:
self._default = keys[0]
else:
self._default = None
return True
return
def check(self, value: str) -> str:
"""Check the key against the stored status names."""
@@ -129,93 +140,51 @@ class NWStatus:
return self._default
return ""
def name(self, key: str | None) -> str:
"""Return the name associated with a given key."""
if key and key in self._store:
return self._store[key]["name"]
elif self._default is not None:
return self._store[self._default]["name"]
return ""
def cols(self, key: str | None) -> tuple[int, int, int]:
"""Return the colours associated with a given key."""
if key and key in self._store:
return self._store[key]["cols"]
elif self._default is not None:
return self._store[self._default]["cols"]
return 100, 100, 100
def count(self, key: str | None) -> int:
"""Return the count associated with a given key."""
if key and key in self._store:
return self._store[key]["count"]
elif self._default is not None:
return self._store[self._default]["count"]
return 0
def icon(self, key: str | None) -> QIcon:
"""Return the icon associated with a given key."""
if key and key in self._store:
return self._store[key]["icon"]
elif self._default is not None:
return self._store[self._default]["icon"]
return self._defaultIcon
def reorder(self, order: list[str]) -> bool:
"""Reorder the items according to list."""
if len(order) != len(self._store):
logger.error("Length mismatch between new and old order")
return False
if order == list(self._store.keys()):
return False
store = {}
for key in order:
if key in self._store:
store[key] = self._store[key]
else:
logger.error("Unknown key '%s' in order", key)
return False
self._store = store
return True
def resetCounts(self) -> None:
"""Clear the counts of references to the status entries."""
for key in self._store:
self._store[key]["count"] = 0
for entry in self._store.values():
entry.count = 0
return
def increment(self, key: str | None) -> None:
"""Increment the counter for a given entry."""
if key and key in self._store:
self._store[key]["count"] += 1
self._store[key].count += 1
return
def pack(self) -> Iterable[tuple[str, dict]]:
"""Pack the status entries into a dictionary."""
for key, data in self._store.items():
yield (data["name"], {
for key, entry in self._store.items():
yield (entry.name, {
"key": key,
"count": str(data["count"]),
"red": str(data["cols"][0]),
"green": str(data["cols"][1]),
"blue": str(data["cols"][2]),
"count": str(entry.count),
"red": str(entry.color.red()),
"green": str(entry.color.green()),
"blue": str(entry.color.blue()),
"shape": entry.shape.name,
})
return
def unpack(self, data: dict) -> None:
"""Unpack a data dictionary and set the class values."""
self._store = {}
self._default = None
for key, entry in data.items():
label = entry.get("label", "")
colour = entry.get("colour", (100, 100, 100))
count = entry.get("count", 0)
self.write(key, label, colour, count)
return
def iterItems(self) -> Iterable[tuple[str, StatusEntry]]:
"""Yield entries from the status icons."""
yield from self._store.items()
@staticmethod
def createIcon(height: int, color: QColor, shape: nwStatusShape) -> QIcon:
"""Generate an icon for a status label."""
pixmap = QPixmap(48, 48)
pixmap.fill(QtTransparent)
painter = QPainter(pixmap)
painter.setRenderHint(QtPaintAnitAlias)
painter.fillPath(_SHAPES.getShape(shape), color)
painter.end()
return QIcon(pixmap.scaled(
height, height,
Qt.AspectRatioMode.IgnoreAspectRatio,
Qt.TransformationMode.SmoothTransformation
))
##
# Internal Functions
@@ -246,38 +215,116 @@ class NWStatus:
return False
return True
def _createIcon(self, red: int, green: int, blue: int) -> QIcon:
"""Generate an icon for a status label."""
pixmap = QPixmap(self._iPX, self._iPX)
pixmap.fill(QtTransparent)
def _checkKey(self, key: str | None) -> str:
"""Check key is valid, and if not, generate one."""
return key if self._isKey(key) else self._newKey()
painter = QPainter(pixmap)
painter.setRenderHint(QtPaintAnitAlias)
painter.fillPath(self._iconPath, QColor(red, green, blue))
painter.end()
return QIcon(pixmap)
class _ShapeCache:
##
# Iterator Bits
##
def __init__(self) -> None:
self._cache: dict[nwStatusShape, QPainterPath] = {}
return
def __len__(self) -> int:
return len(self._store)
def getShape(self, shape: nwStatusShape) -> QPainterPath:
"""Return a painter shape for an icon."""
if shape in self._cache:
return self._cache[shape]
def __getitem__(self, key: str) -> dict:
return self._store[key]
path = QPainterPath()
if shape == nwStatusShape.SQUARE:
path.addRoundedRect(2.0, 2.0, 44.0, 44.0, 4.0, 4.0)
elif shape == nwStatusShape.TRIANGLE:
path.addPolygon(QPolygonF([
QPointF(24.00, 3.00),
QPointF(43.92, 37.50),
QPointF(4.08, 37.50),
]))
elif shape == nwStatusShape.NABLA:
path.addPolygon(QPolygonF([
QPointF(24.00, 48.00),
QPointF(4.08, 14.50),
QPointF(43.92, 14.50),
]))
elif shape == nwStatusShape.DIAMOND:
path.addPolygon(QPolygonF([
QPointF(24.00, 2.00),
QPointF(44.00, 24.00),
QPointF(24.00, 46.00),
QPointF(4.00, 24.00),
]))
elif shape == nwStatusShape.PENTAGON:
path.addPolygon(QPolygonF([
QPointF(24.00, 1.50),
QPointF(45.87, 17.39),
QPointF(37.52, 43.11),
QPointF(10.48, 43.11),
QPointF(2.13, 17.39),
]))
elif shape == nwStatusShape.HEXAGON:
path.addPolygon(QPolygonF([
QPointF(24.00, 1.50),
QPointF(43.92, 13.00),
QPointF(43.92, 36.00),
QPointF(24.00, 47.50),
QPointF(4.08, 36.00),
QPointF(4.08, 13.00),
]))
elif shape == nwStatusShape.STAR:
path.addPolygon(QPolygonF([
QPointF(24.00, 0.50), QPointF(31.05, 14.79),
QPointF(46.83, 17.08), QPointF(35.41, 28.21),
QPointF(38.11, 43.92), QPointF(24.00, 36.50),
QPointF(9.89, 43.92), QPointF(12.59, 28.21),
QPointF(1.17, 17.08), QPointF(15.37, 16.16),
]))
elif shape == nwStatusShape.PACMAN:
path.moveTo(24.0, 24.0)
path.arcTo(2.0, 2.0, 44.0, 44.0, 40.0, 280.0)
elif shape == nwStatusShape.CIRCLE_Q:
path.moveTo(24.0, 24.0)
path.arcTo(2.0, 2.0, 44.0, 44.0, 0.0, 90.0)
elif shape == nwStatusShape.CIRCLE_H:
path.moveTo(24.0, 24.0)
path.arcTo(2.0, 2.0, 44.0, 44.0, -90.0, 180.0)
elif shape == nwStatusShape.CIRCLE_T:
path.moveTo(24.0, 24.0)
path.arcTo(2.0, 2.0, 44.0, 44.0, -180.0, 270.0)
elif shape == nwStatusShape.CIRCLE:
path.addEllipse(2.0, 2.0, 44.0, 44.0)
elif shape == nwStatusShape.BARS_1:
path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0)
elif shape == nwStatusShape.BARS_2:
path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0)
path.addRoundedRect(14.0, 2.0, 8.0, 44.0, 4.0, 4.0)
elif shape == nwStatusShape.BARS_3:
path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0)
path.addRoundedRect(14.0, 2.0, 8.0, 44.0, 4.0, 4.0)
path.addRoundedRect(26.0, 2.0, 8.0, 44.0, 4.0, 4.0)
elif shape == nwStatusShape.BARS_4:
path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0)
path.addRoundedRect(14.0, 2.0, 8.0, 44.0, 4.0, 4.0)
path.addRoundedRect(26.0, 2.0, 8.0, 44.0, 4.0, 4.0)
path.addRoundedRect(38.0, 2.0, 8.0, 44.0, 4.0, 4.0)
elif shape == nwStatusShape.BLOCK_1:
path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0)
elif shape == nwStatusShape.BLOCK_2:
path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0)
path.addRoundedRect(24.0, 2.0, 20.0, 20.0, 4.0, 4.0)
elif shape == nwStatusShape.BLOCK_3:
path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0)
path.addRoundedRect(2.0, 24.0, 20.0, 20.0, 4.0, 4.0)
path.addRoundedRect(24.0, 2.0, 20.0, 20.0, 4.0, 4.0)
elif shape == nwStatusShape.BLOCK_4:
path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0)
path.addRoundedRect(2.0, 24.0, 20.0, 20.0, 4.0, 4.0)
path.addRoundedRect(24.0, 2.0, 20.0, 20.0, 4.0, 4.0)
path.addRoundedRect(24.0, 24.0, 20.0, 20.0, 4.0, 4.0)
def __iter__(self) -> Iterator[dict]:
return iter(self._store)
self._cache[shape] = path
def keys(self) -> KeysView[str]:
return self._store.keys()
return path
def items(self) -> ItemsView[str, dict]:
return self._store.items()
def values(self) -> ValuesView[dict]:
return self._store.values()
# END Class NWStatus
# Create Singleton
_SHAPES = _ShapeCache()

View File

@@ -54,8 +54,6 @@ class NWStorageOpen(Enum):
FAILED = 3
READY = 4
# END Enum NWStorageOpen
class NWStorageCreate(Enum):
@@ -63,8 +61,6 @@ class NWStorageCreate(Enum):
OS_ERROR = 1
READY = 2
# END Enum NWStorageCreate
class NWStorage:
"""Core: Project Storage Class
@@ -391,8 +387,6 @@ class NWStorage:
self._lockedBy = None
return True
# END Class NWStorage
class _LegacyStorage:
"""Core: Legacy Storage Converter Utils
@@ -590,5 +584,3 @@ class _LegacyStorage:
logException()
return
# END Class _LegacyStorage

View File

@@ -26,17 +26,36 @@ from __future__ import annotations
import json
import logging
from time import time
from pathlib import Path
from time import time
from novelwriter import CONFIG
from novelwriter.common import formatTimeStamp
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwHtmlUnicode
from novelwriter.constants import nwHeadFmt, nwHtmlUnicode, nwKeyWords, nwLabels
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer, stripEscape
from novelwriter.core.tokenizer import T_Formats, Tokenizer, stripEscape
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS
logger = logging.getLogger(__name__)
HTML5_TAGS = {
Tokenizer.FMT_B_B: "<strong>",
Tokenizer.FMT_B_E: "</strong>",
Tokenizer.FMT_I_B: "<em>",
Tokenizer.FMT_I_E: "</em>",
Tokenizer.FMT_D_B: "<del>",
Tokenizer.FMT_D_E: "</del>",
Tokenizer.FMT_U_B: "<span style='text-decoration: underline;'>",
Tokenizer.FMT_U_E: "</span>",
Tokenizer.FMT_M_B: "<mark>",
Tokenizer.FMT_M_E: "</mark>",
Tokenizer.FMT_SUP_B: "<sup>",
Tokenizer.FMT_SUP_E: "</sup>",
Tokenizer.FMT_SUB_B: "<sub>",
Tokenizer.FMT_SUB_E: "</sub>",
Tokenizer.FMT_STRIP: "",
}
class ToHtml(Tokenizer):
"""Core: HTML Document Writer
@@ -45,19 +64,15 @@ class ToHtml(Tokenizer):
also used by the Document Viewer, and Manuscript Build Preview.
"""
M_PREVIEW = 0 # Tweak output for the DocViewer
M_EXPORT = 1 # Tweak output for saving to HTML or printing
M_EBOOK = 2 # Tweak output for converting to epub
def __init__(self, project: NWProject) -> None:
super().__init__(project)
self._genMode = self.M_EXPORT
self._cssStyles = True
self._fullHTML: list[str] = []
# Internals
self._trMap = {}
self._usedNotes: dict[str, int] = {}
self.setReplaceUnicode(False)
return
@@ -74,11 +89,6 @@ class ToHtml(Tokenizer):
# Setters
##
def setPreview(self, state: bool) -> None:
"""Set to preview generator mode."""
self._genMode = self.M_PREVIEW if state else self.M_EXPORT
return
def setStyles(self, cssStyles: bool) -> None:
"""Enable or disable CSS styling. Some elements may still have
class tags.
@@ -117,39 +127,9 @@ class ToHtml(Tokenizer):
def doConvert(self) -> None:
"""Convert the list of text tokens into an HTML document."""
if self._genMode == self.M_PREVIEW:
htmlTags = { # HTML4 + CSS2 (for Qt)
self.FMT_B_B: "<b>",
self.FMT_B_E: "</b>",
self.FMT_I_B: "<i>",
self.FMT_I_E: "</i>",
self.FMT_D_B: "<span style='text-decoration: line-through;'>",
self.FMT_D_E: "</span>",
self.FMT_U_B: "<u>",
self.FMT_U_E: "</u>",
self.FMT_M_B: "<mark>",
self.FMT_M_E: "</mark>",
}
else:
htmlTags = { # HTML5 (for export)
self.FMT_B_B: "<strong>",
self.FMT_B_E: "</strong>",
self.FMT_I_B: "<em>",
self.FMT_I_E: "</em>",
self.FMT_D_B: "<del>",
self.FMT_D_E: "</del>",
self.FMT_U_B: "<span style='text-decoration: underline;'>",
self.FMT_U_E: "</span>",
self.FMT_M_B: "<mark>",
self.FMT_M_E: "</mark>",
}
self._result = ""
htmlTags[self.FMT_SUP_B] = "<sup>"
htmlTags[self.FMT_SUP_E] = "</sup>"
htmlTags[self.FMT_SUB_B] = "<sub>"
htmlTags[self.FMT_SUB_E] = "</sub>"
if self._isNovel and self._genMode != self.M_PREVIEW:
if self._isNovel:
# For story files, we bump the titles one level up
h1Cl = " class='title'"
h1 = "h1"
@@ -163,12 +143,7 @@ class ToHtml(Tokenizer):
h3 = "h3"
h4 = "h4"
self._result = ""
para = []
pStyle = None
lines = []
tHandle = self._handle
for tType, nHead, tText, tFormat, tStyle in self._tokens:
@@ -181,11 +156,11 @@ class ToHtml(Tokenizer):
for c in tText:
if c == "<":
cText.append("&lt;")
tFormat = [[p + 3 if p > i else p, f] for p, f in tFormat]
tFormat = [(p + 3 if p > i else p, f, k) for p, f, k in tFormat]
i += 4
elif c == ">":
cText.append("&gt;")
tFormat = [[p + 3 if p > i else p, f] for p, f in tFormat]
tFormat = [(p + 3 if p > i else p, f, k) for p, f, k in tFormat]
i += 4
else:
cText.append(c)
@@ -235,37 +210,27 @@ class ToHtml(Tokenizer):
aNm = ""
# Process Text Type
if tType == self.T_EMPTY:
if pStyle is None:
pStyle = ""
if len(para) > 1 and self._cssStyles:
pClass = " class='break'"
else:
pClass = ""
if len(para) > 0:
tTemp = "<br/>".join(para)
lines.append(f"<p{pClass+pStyle}>{tTemp.rstrip()}</p>\n")
para = []
pStyle = None
if tType == self.T_TEXT:
lines.append(f"<p{hStyle}>{self._formatText(tText, tFormat)}</p>\n")
elif tType == self.T_TITLE:
tHead = tText.replace(nwHeadFmt.BR, "<br/>")
tHead = tText.replace(nwHeadFmt.BR, "<br>")
lines.append(f"<h1 class='title'{hStyle}>{aNm}{tHead}</h1>\n")
elif tType == self.T_HEAD1:
tHead = tText.replace(nwHeadFmt.BR, "<br/>")
tHead = tText.replace(nwHeadFmt.BR, "<br>")
lines.append(f"<{h1}{h1Cl}{hStyle}>{aNm}{tHead}</{h1}>\n")
elif tType == self.T_HEAD2:
tHead = tText.replace(nwHeadFmt.BR, "<br/>")
tHead = tText.replace(nwHeadFmt.BR, "<br>")
lines.append(f"<{h2}{hStyle}>{aNm}{tHead}</{h2}>\n")
elif tType == self.T_HEAD3:
tHead = tText.replace(nwHeadFmt.BR, "<br/>")
tHead = tText.replace(nwHeadFmt.BR, "<br>")
lines.append(f"<{h3}{hStyle}>{aNm}{tHead}</{h3}>\n")
elif tType == self.T_HEAD4:
tHead = tText.replace(nwHeadFmt.BR, "<br/>")
tHead = tText.replace(nwHeadFmt.BR, "<br>")
lines.append(f"<{h4}{hStyle}>{aNm}{tHead}</{h4}>\n")
elif tType == self.T_SEP:
@@ -274,22 +239,14 @@ class ToHtml(Tokenizer):
elif tType == self.T_SKIP:
lines.append(f"<p class='skip'{hStyle}>&nbsp;</p>\n")
elif tType == self.T_TEXT:
tTemp = tText
if pStyle is None:
pStyle = hStyle
for pos, fmt in reversed(tFormat):
tTemp = f"{tTemp[:pos]}{htmlTags[fmt]}{tTemp[pos:]}"
para.append(stripEscape(tTemp.rstrip()))
elif tType == self.T_SYNOPSIS and self._doSynopsis:
lines.append(self._formatSynopsis(tText, True))
lines.append(self._formatSynopsis(self._formatText(tText, tFormat), True))
elif tType == self.T_SHORT and self._doSynopsis:
lines.append(self._formatSynopsis(tText, False))
lines.append(self._formatSynopsis(self._formatText(tText, tFormat), False))
elif tType == self.T_COMMENT and self._doComments:
lines.append(self._formatComments(tText))
lines.append(self._formatComments(self._formatText(tText, tFormat)))
elif tType == self.T_KEYWORD and self._doKeywords:
tag, text = self._formatKeywords(tText)
@@ -302,6 +259,26 @@ class ToHtml(Tokenizer):
return
def appendFootnotes(self) -> None:
"""Append the footnotes in the buffer."""
if self._usedNotes:
footnotes = self._localLookup("Footnotes")
lines = []
lines.append(f"<h3>{footnotes}</h3>\n")
lines.append("<ol>\n")
for key, index in self._usedNotes.items():
if content := self._footnotes.get(key):
text = self._formatText(*content)
lines.append(f"<li id='footnote_{index}'><p>{text}</p></li>\n")
lines.append("</ol>\n")
result = "".join(lines)
self._result += result
self._fullHTML.append(result)
return
def saveHtml5(self, path: str | Path) -> None:
"""Save the data to an HTML file."""
with open(path, mode="w", encoding="utf-8") as fObj:
@@ -367,8 +344,16 @@ class ToHtml(Tokenizer):
mScale = self._lineHeight/1.15
styles = []
styles.append("body {{font-family: '{0:s}'; font-size: {1:d}pt;}}".format(
self._textFont, self._textSize
font = self._textFont
styles.append((
"body {{"
"font-family: '{0:s}'; font-size: {1:d}pt; "
"font-weight: {2:d}; font-style: {3:s};"
"}}"
).format(
font.family(), font.pointSize(),
FONT_WEIGHTS.get(font.weight(), 400),
FONT_STYLE.get(font.style(), "normal"),
))
styles.append((
"p {{"
@@ -442,7 +427,7 @@ class ToHtml(Tokenizer):
styles.append("a {color: rgb(66, 113, 174);}")
styles.append("mark {background: rgb(255, 255, 166);}")
styles.append(".tags {color: rgb(245, 135, 31); font-weight: bold;}")
styles.append(".keyword {color: rgb(245, 135, 31); font-weight: bold;}")
styles.append(".break {text-align: left;}")
styles.append(".synopsis {font-style: italic;}")
styles.append(".comment {font-style: italic; color: rgb(100, 100, 100);}")
@@ -453,24 +438,36 @@ class ToHtml(Tokenizer):
# Internal Functions
##
def _formatText(self, text: str, tFmt: T_Formats) -> str:
"""Apply formatting tags to text."""
temp = text
for pos, fmt, data in reversed(tFmt):
html = ""
if fmt == self.FMT_FNOTE:
if data in self._footnotes:
index = len(self._usedNotes) + 1
self._usedNotes[data] = index
html = f"<sup><a href='#footnote_{index}'>{index}</a></sup>"
else:
html = "<sup>ERR</sup>"
else:
html = HTML5_TAGS.get(fmt, "ERR")
temp = f"{temp[:pos]}{html}{temp[pos:]}"
temp = temp.replace("\n", "<br>")
return stripEscape(temp)
def _formatSynopsis(self, text: str, synopsis: bool) -> str:
"""Apply HTML formatting to synopsis."""
if synopsis:
sSynop = self._localLookup("Synopsis")
else:
sSynop = self._localLookup("Short Description")
if self._genMode == self.M_PREVIEW:
return f"<p class='comment'><span class='synopsis'>{sSynop}:</span> {text}</p>\n"
else:
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:
"""Apply HTML formatting to comments."""
if self._genMode == self.M_PREVIEW:
return f"<p class='comment'>{text}</p>\n"
else:
sComm = self._localLookup("Comment")
return f"<p class='comment'><strong>{sComm}:</strong> {text}</p>\n"
sComm = self._localLookup("Comment")
return f"<p class='comment'><strong>{sComm}:</strong> {text}</p>\n"
def _formatKeywords(self, text: str) -> tuple[str, str]:
"""Apply HTML formatting to keywords."""
@@ -478,19 +475,16 @@ class ToHtml(Tokenizer):
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
return "", ""
result = f"<span class='tags'>{self._localLookup(nwLabels.KEY_NAME[bits[0]])}:</span> "
result = f"<span class='keyword'>{self._localLookup(nwLabels.KEY_NAME[bits[0]])}:</span> "
if len(bits) > 1:
if bits[0] == nwKeyWords.TAG_KEY:
one, two = self._project.index.parseValue(bits[1])
result += f"<a name='tag_{one}'>{one}</a>"
result += f"<a class='tag' name='tag_{one}'>{one}</a>"
if two:
result += f" | <span class='optional'>{two}</a>"
else:
if self._genMode == self.M_PREVIEW:
result += ", ".join(f"<a href='#{bits[0][1:]}={t}'>{t}</a>" for t in bits[1:])
else:
result += ", ".join(f"<a href='#tag_{t}'>{t}</a>" for t in bits[1:])
result += ", ".join(
f"<a class='tag' href='#tag_{t}'>{t}</a>" for t in bits[1:]
)
return bits[0][1:], result
# END Class ToHtml

View File

@@ -24,18 +24,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import re
import json
import logging
import re
from abc import ABC, abstractmethod
from time import time
from pathlib import Path
from functools import partial
from pathlib import Path
from time import time
from PyQt5.QtCore import QCoreApplication, QRegularExpression
from PyQt5.QtGui import QFont
from novelwriter.common import formatTimeStamp, numberToRoman, checkInt
from novelwriter.common import checkInt, formatTimeStamp, numberToRoman
from novelwriter.constants import (
nwHeadFmt, nwKeyWords, nwLabels, nwRegEx, nwShortcode, nwUnicode, trConst
)
@@ -48,6 +49,10 @@ logger = logging.getLogger(__name__)
ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""}
RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL)
T_Formats = list[tuple[int, int, str]]
T_Comment = tuple[str, T_Formats]
T_Token = tuple[int, int, str, T_Formats, int]
def stripEscape(text: str) -> str:
"""Strip escaped Markdown characters from paragraph text."""
@@ -80,6 +85,8 @@ class Tokenizer(ABC):
FMT_SUP_E = 12 # End superscript
FMT_SUB_B = 13 # Begin subscript
FMT_SUB_E = 14 # End subscript
FMT_FNOTE = 15 # Footnote marker
FMT_STRIP = 16 # Strip the format code
# Block Type
T_EMPTY = 1 # Empty line (new paragraph)
@@ -111,45 +118,53 @@ class Tokenizer(ABC):
# Lookups
L_HEADINGS = [T_TITLE, T_HEAD1, T_HEAD2, T_HEAD3, T_HEAD4]
L_SKIP_INDENT = [T_TITLE, T_HEAD1, T_HEAD2, T_HEAD2, T_HEAD3, T_HEAD4, T_SEP, T_SKIP]
L_SUMMARY = [T_SYNOPSIS, T_SHORT]
def __init__(self, project: NWProject) -> None:
self._project = project
# Data Variables
self._text = "" # The raw text to be tokenized
self._handle = None # The item handle currently being processed
self._result = "" # The result of the last document
self._text = "" # The raw text to be tokenized
self._handle = None # The item handle currently being processed
self._result = "" # The result of the last document
self._keepMD = False # Whether to keep the markdown text
self._keepMarkdown = False # Whether to keep the markdown text
self._allMarkdown = [] # The result novelWriter markdown of all documents
# Tokens and Meta Data (Per Document)
self._tokens: list[T_Token] = []
self._footnotes: dict[str, T_Comment] = {}
# Processed Tokens and Meta Data
self._tokens: list[tuple[int, int, str, list[tuple[int, int]], int]] = []
# Tokens and Meta Data (Per Instance)
self._counts: dict[str, int] = {}
self._outline: dict[str, str] = {}
self._markdown: list[str] = []
# User Settings
self._textFont = "Serif" # Output text font
self._textSize = 11 # Output text size
self._textFixed = False # Fixed width text
self._textFont = QFont("Serif", 11) # Output text font
self._lineHeight = 1.15 # Line height in units of em
self._blockIndent = 4.00 # Block indent in units of em
self._firstIndent = False # Enable first line indent
self._firstWidth = 1.40 # First line indent in units of em
self._indentFirst = False # Indent first paragraph
self._doJustify = False # Justify text
self._doBodyText = True # Include body text
self._doSynopsis = False # Also process synopsis comments
self._doComments = False # Also process comments
self._doKeywords = False # Also process keywords like tags and references
self._skipKeywords = set() # Keywords to ignore
self._keepBreaks = True # Keep line breaks in paragraphs
# Margins
self._marginTitle = (1.000, 0.500)
self._marginHead1 = (1.000, 0.500)
self._marginHead2 = (0.834, 0.500)
self._marginHead3 = (0.584, 0.500)
self._marginHead4 = (0.584, 0.500)
self._marginTitle = (1.417, 0.500)
self._marginHead1 = (1.417, 0.500)
self._marginHead2 = (1.668, 0.500)
self._marginHead3 = (1.168, 0.500)
self._marginHead4 = (1.168, 0.500)
self._marginText = (0.000, 0.584)
self._marginMeta = (0.000, 0.584)
self._marginFoot = (1.417, 0.467)
self._marginSep = (1.168, 1.168)
# Title Formats
self._fmtTitle = nwHeadFmt.TITLE # Formatting for titles
@@ -205,6 +220,9 @@ class Tokenizer(ABC):
nwShortcode.SUP_O: self.FMT_SUP_B, nwShortcode.SUP_C: self.FMT_SUP_E,
nwShortcode.SUB_O: self.FMT_SUB_B, nwShortcode.SUB_C: self.FMT_SUB_E,
}
self._shortCodeVals = {
nwShortcode.FOOTNOTE_B: self.FMT_FNOTE,
}
return
@@ -220,7 +238,7 @@ class Tokenizer(ABC):
@property
def allMarkdown(self) -> list[str]:
"""The combined novelWriter Markdown text."""
return self._allMarkdown
return self._markdown
@property
def textStats(self) -> dict[str, int]:
@@ -298,11 +316,9 @@ class Tokenizer(ABC):
)
return
def setFont(self, family: str, size: int, isFixed: bool = False) -> None:
def setFont(self, font: QFont) -> None:
"""Set the build font."""
self._textFont = family
self._textSize = round(int(size))
self._textFixed = isFixed
self._textFont = font
return
def setLineHeight(self, height: float) -> None:
@@ -315,6 +331,15 @@ class Tokenizer(ABC):
self._blockIndent = min(max(float(indent), 0.0), 10.0)
return
def setFirstLineIndent(self, state: bool, indent: float, first: bool) -> None:
"""Set first line indent and whether to also indent first
paragraph after a heading.
"""
self._firstIndent = state
self._firstWidth = indent
self._indentFirst = first
return
def setJustify(self, state: bool) -> None:
"""Enable or disable text justification."""
self._doJustify = state
@@ -355,6 +380,11 @@ class Tokenizer(ABC):
self._marginMeta = (float(upper), float(lower))
return
def setSeparatorMargins(self, upper: float, lower: float) -> None:
"""Set the upper and lower meta text margin."""
self._marginSep = (float(upper), float(lower))
return
def setLinkHeadings(self, state: bool) -> None:
"""Enable or disable adding an anchor before headings."""
self._linkHeadings = state
@@ -385,9 +415,14 @@ class Tokenizer(ABC):
self._skipKeywords = set(x.lower().strip() for x in keywords.split(","))
return
def setKeepLineBreaks(self, state: bool) -> None:
"""Keep line breaks in paragraphs."""
self._keepBreaks = state
return
def setKeepMarkdown(self, state: bool) -> None:
"""Keep original markdown during build."""
self._keepMarkdown = state
self._keepMD = state
return
##
@@ -417,8 +452,8 @@ class Tokenizer(ABC):
self._tokens.append((
self.T_TITLE, 1, title, [], textAlign
))
if self._keepMarkdown:
self._allMarkdown.append(f"#! {title}\n\n")
if self._keepMD:
self._markdown.append(f"#! {title}\n\n")
return
@@ -466,22 +501,23 @@ class Tokenizer(ABC):
4: The internal formatting map of the text, self.FMT_*
5: The style of the block, self.A_*
"""
self._tokens = []
if self._isNovel:
self._hFormatter.setHandle(self._handle)
nHead = 0
breakNext = False
tmpMarkdown = []
tHandle = self._handle or ""
tokens: list[T_Token] = []
for aLine in self._text.splitlines():
sLine = aLine.strip().lower()
# Check for blank lines
if len(sLine) == 0:
self._tokens.append((
tokens.append((
self.T_EMPTY, nHead, "", [], self.A_NONE
))
if self._keepMarkdown:
if self._keepMD:
tmpMarkdown.append("\n")
continue
@@ -507,7 +543,7 @@ class Tokenizer(ABC):
continue
elif sLine == "[vspace]":
self._tokens.append(
tokens.append(
(self.T_SKIP, nHead, "", [], sAlign)
)
continue
@@ -515,11 +551,11 @@ class Tokenizer(ABC):
elif sLine.startswith("[vspace:") and sLine.endswith("]"):
nSkip = checkInt(sLine[8:-1], 0)
if nSkip >= 1:
self._tokens.append(
tokens.append(
(self.T_SKIP, nHead, "", [], sAlign)
)
if nSkip > 1:
self._tokens += (nSkip - 1) * [
tokens += (nSkip - 1) * [
(self.T_SKIP, nHead, "", [], self.A_NONE)
]
continue
@@ -533,24 +569,32 @@ class Tokenizer(ABC):
if aLine.startswith("%~"):
continue
cStyle, cText, _ = processComment(aLine)
cStyle, cKey, cText, _, _ = processComment(aLine)
if cStyle == nwComment.SYNOPSIS:
self._tokens.append((
self.T_SYNOPSIS, nHead, cText, [], sAlign
tLine, tFmt = self._extractFormats(cText)
tokens.append((
self.T_SYNOPSIS, nHead, tLine, tFmt, sAlign
))
if self._doSynopsis and self._keepMarkdown:
if self._doSynopsis and self._keepMD:
tmpMarkdown.append(f"{aLine}\n")
elif cStyle == nwComment.SHORT:
self._tokens.append((
self.T_SHORT, nHead, cText, [], sAlign
tLine, tFmt = self._extractFormats(cText)
tokens.append((
self.T_SHORT, nHead, tLine, tFmt, sAlign
))
if self._doSynopsis and self._keepMarkdown:
if self._doSynopsis and self._keepMD:
tmpMarkdown.append(f"{aLine}\n")
elif cStyle == nwComment.FOOTNOTE:
tLine, tFmt = self._extractFormats(cText, skip=self.FMT_FNOTE)
self._footnotes[f"{tHandle}:{cKey}"] = (tLine, tFmt)
if self._keepMD:
tmpMarkdown.append(f"{aLine}\n")
else:
self._tokens.append((
self.T_COMMENT, nHead, cText, [], sAlign
tLine, tFmt = self._extractFormats(cText)
tokens.append((
self.T_COMMENT, nHead, tLine, tFmt, sAlign
))
if self._doComments and self._keepMarkdown:
if self._doComments and self._keepMD:
tmpMarkdown.append(f"{aLine}\n")
elif aLine.startswith("@"):
@@ -560,11 +604,14 @@ class Tokenizer(ABC):
# are automatically skipped.
valid, bits, _ = self._project.index.scanThis(aLine)
if valid and bits and bits[0] not in self._skipKeywords:
self._tokens.append((
if (
valid and bits and bits[0] in nwLabels.KEY_NAME
and bits[0] not in self._skipKeywords
):
tokens.append((
self.T_KEYWORD, nHead, aLine[1:].strip(), [], sAlign
))
if self._doKeywords and self._keepMarkdown:
if self._doKeywords and self._keepMD:
tmpMarkdown.append(f"{aLine}\n")
elif aLine.startswith(("# ", "#! ")):
@@ -597,10 +644,10 @@ class Tokenizer(ABC):
self._hFormatter.resetAll()
self._noSep = True
self._tokens.append((
tokens.append((
tType, nHead, tText, [], tStyle
))
if self._keepMarkdown:
if self._keepMD:
tmpMarkdown.append(f"{aLine}\n")
elif aLine.startswith(("## ", "##! ")):
@@ -632,10 +679,10 @@ class Tokenizer(ABC):
self._hFormatter.resetScene()
self._noSep = True
self._tokens.append((
tokens.append((
tType, nHead, tText, [], tStyle
))
if self._keepMarkdown:
if self._keepMD:
tmpMarkdown.append(f"{aLine}\n")
elif aLine.startswith(("### ", "###! ")):
@@ -673,10 +720,10 @@ class Tokenizer(ABC):
tStyle = self.A_NONE if self._noSep else self.A_CENTRE
self._noSep = False
self._tokens.append((
tokens.append((
tType, nHead, tText, [], tStyle
))
if self._keepMarkdown:
if self._keepMD:
tmpMarkdown.append(f"{aLine}\n")
elif aLine.startswith("#### "):
@@ -703,10 +750,10 @@ class Tokenizer(ABC):
tType = self.T_SEP
tStyle = self.A_CENTRE
self._tokens.append((
tokens.append((
tType, nHead, tText, [], tStyle
))
if self._keepMarkdown:
if self._keepMD:
tmpMarkdown.append(f"{aLine}\n")
else:
@@ -750,54 +797,91 @@ class Tokenizer(ABC):
sAlign |= self.A_IND_R
# Process formats
tLine, fmtPos = self._extractFormats(aLine)
self._tokens.append((
self.T_TEXT, nHead, tLine, fmtPos, sAlign
tLine, tFmt = self._extractFormats(aLine)
tokens.append((
self.T_TEXT, nHead, tLine, tFmt, sAlign
))
if self._keepMarkdown:
if self._keepMD:
tmpMarkdown.append(f"{aLine}\n")
# If we have content, turn off the first page flag
if self._isFirst and self._tokens:
if self._isFirst and tokens:
self._isFirst = False # First document has been processed
# Make sure the token array doesn't start with a page break
# on the very first page, adding a blank first page.
if self._tokens[0][4] & self.A_PBB:
token = self._tokens[0]
self._tokens[0] = (
token[0], token[1], token[2], token[3], token[4] & ~self.A_PBB
if tokens[0][4] & self.A_PBB:
cToken = tokens[0]
tokens[0] = (
cToken[0], cToken[1], cToken[2], cToken[3], cToken[4] & ~self.A_PBB
)
# Always add an empty line at the end of the file
self._tokens.append((
tokens.append((
self.T_EMPTY, nHead, "", [], self.A_NONE
))
if self._keepMarkdown:
if self._keepMD:
tmpMarkdown.append("\n")
self._allMarkdown.append("".join(tmpMarkdown))
self._markdown.append("".join(tmpMarkdown))
# Second Pass
# ===========
# Some items need a second pass
# This second pass strips away consecutive blank lines, and
# combines consecutive text lines into the same paragraph.
# It also ensures that there isn't paragraph spacing between
# meta data lines for formats that has spacing.
pToken = (self.T_EMPTY, 0, "", [], self.A_NONE)
nToken = (self.T_EMPTY, 0, "", [], self.A_NONE)
tCount = len(self._tokens)
for n, token in enumerate(self._tokens):
self._tokens = []
pToken: T_Token = (self.T_EMPTY, 0, "", [], self.A_NONE)
nToken: T_Token = (self.T_EMPTY, 0, "", [], self.A_NONE)
lineSep = "\n" if self._keepBreaks else " "
pLines: list[T_Token] = []
tCount = len(tokens)
for n, cToken in enumerate(tokens):
if n > 0:
pToken = self._tokens[n-1]
pToken = tokens[n-1] # Look behind
if n < tCount - 1:
nToken = self._tokens[n+1]
nToken = tokens[n+1] # Look ahead
if token[0] == self.T_KEYWORD:
aStyle = token[4]
if cToken[0] == self.T_EMPTY:
# We don't need to keep the empty lines after this pass
pass
elif cToken[0] == self.T_KEYWORD:
# Adjust margins for lines in a list of keyword lines
aStyle = cToken[4]
if pToken[0] == self.T_KEYWORD:
aStyle |= self.A_Z_TOPMRG
if nToken[0] == self.T_KEYWORD:
aStyle |= self.A_Z_BTMMRG
self._tokens[n] = (token[0], token[1], token[2], token[3], aStyle)
self._tokens.append((
cToken[0], cToken[1], cToken[2], cToken[3], aStyle
))
elif cToken[0] == self.T_TEXT:
# Combine lines from the same paragraph
pLines.append(cToken)
if nToken[0] != self.T_TEXT:
nLines = len(pLines)
if nLines == 1:
self._tokens.append(pLines[0])
elif nLines > 1:
tTxt = ""
tFmt: T_Formats = []
for aToken in pLines:
tLen = len(tTxt)
tTxt += f"{aToken[2]}{lineSep}"
tFmt.extend((p+tLen, fmt, key) for p, fmt, key in aToken[3])
self._tokens.append((
self.T_TEXT, pLines[0][1], tTxt[:-1], tFmt, pLines[0][4]
))
pLines = []
else:
self._tokens.append(cToken)
return
@@ -840,7 +924,6 @@ class Tokenizer(ABC):
textWordChars = self._counts.get("textWordChars", 0)
titleWordChars = self._counts.get("titleWordChars", 0)
para = []
for tType, _, tText, _, _ in self._tokens:
tText = tText.replace(nwUnicode.U_ENDASH, " ")
tText = tText.replace(nwUnicode.U_EMDASH, " ")
@@ -850,22 +933,19 @@ class Tokenizer(ABC):
nChars = len(tText)
nWChars = len("".join(tWords))
if tType == self.T_EMPTY:
if len(para) > 0:
tTemp = "\n".join(para)
tPWords = tTemp.split()
nPWords = len(tPWords)
nPChars = len(tTemp)
nPWChars = len("".join(tPWords))
if tType == self.T_TEXT:
tPWords = tText.split()
nPWords = len(tPWords)
nPChars = len(tText)
nPWChars = len("".join(tPWords))
paragraphCount += 1
allWords += nPWords
textWords += nPWords
allChars += nPChars
textChars += nPChars
allWordChars += nPWChars
textWordChars += nPWChars
para = []
paragraphCount += 1
allWords += nPWords
textWords += nPWords
allChars += nPChars
textChars += nPChars
allWordChars += nPWChars
textWordChars += nPWChars
elif tType in self.L_HEADINGS:
titleCount += 1
@@ -881,9 +961,6 @@ class Tokenizer(ABC):
allChars += nChars
allWordChars += nWChars
elif tType == self.T_TEXT:
para.append(tText.rstrip())
elif tType == self.T_SYNOPSIS and self._doSynopsis:
text = "{0}: {1}".format(self._localLookup("Synopsis"), tText)
words = text.split()
@@ -935,7 +1012,7 @@ class Tokenizer(ABC):
def saveRawMarkdown(self, path: str | Path) -> None:
"""Save the raw text to a plain text file."""
with open(path, mode="w", encoding="utf-8") as outFile:
for nwdPage in self._allMarkdown:
for nwdPage in self._markdown:
outFile.write(nwdPage)
return
@@ -950,7 +1027,7 @@ class Tokenizer(ABC):
"buildTimeStr": formatTimeStamp(timeStamp),
},
"text": {
"nwd": [page.rstrip("\n").split("\n") for page in self._allMarkdown],
"nwd": [page.rstrip("\n").split("\n") for page in self._markdown],
}
}
with open(path, mode="w", encoding="utf-8") as fObj:
@@ -961,9 +1038,9 @@ class Tokenizer(ABC):
# Internal Functions
##
def _extractFormats(self, text: str) -> tuple[str, list[tuple[int, int]]]:
def _extractFormats(self, text: str, skip: int = 0) -> tuple[str, T_Formats]:
"""Extract format markers from a text paragraph."""
temp = []
temp: list[tuple[int, int, int, str]] = []
# Match Markdown
for regEx, fmts in self._rxMarkdown:
@@ -971,7 +1048,7 @@ class Tokenizer(ABC):
while rxItt.hasNext():
rxMatch = rxItt.next()
temp.extend(
[rxMatch.capturedStart(n), rxMatch.capturedLength(n), fmt]
(rxMatch.capturedStart(n), rxMatch.capturedLength(n), fmt, "")
for n, fmt in enumerate(fmts) if fmt > 0
)
@@ -979,25 +1056,37 @@ class Tokenizer(ABC):
rxItt = self._rxShortCodes.globalMatch(text, 0)
while rxItt.hasNext():
rxMatch = rxItt.next()
temp.append([
temp.append((
rxMatch.capturedStart(1),
rxMatch.capturedLength(1),
self._shortCodeFmt.get(rxMatch.captured(1).lower(), 0)
])
self._shortCodeFmt.get(rxMatch.captured(1).lower(), 0),
"",
))
# Post-process text and format markers
# Match Shortcode w/Values
rxItt = self._rxShortCodeVals.globalMatch(text, 0)
tHandle = self._handle or ""
while rxItt.hasNext():
rxMatch = rxItt.next()
kind = self._shortCodeVals.get(rxMatch.captured(1).lower(), 0)
temp.append((
rxMatch.capturedStart(0),
rxMatch.capturedLength(0),
self.FMT_STRIP if kind == skip else kind,
f"{tHandle}:{rxMatch.captured(2)}",
))
# Post-process text and format
result = text
formats = []
for pos, n, fmt in reversed(sorted(temp, key=lambda x: x[0])):
for pos, n, fmt, key in reversed(sorted(temp, key=lambda x: x[0])):
if fmt > 0:
result = result[:pos] + result[pos+n:]
formats = [(p-n, f) for p, f in formats]
formats.insert(0, (pos, fmt))
formats = [(p-n, f, k) for p, f, k in formats]
formats.insert(0, (pos, fmt, key))
return result, formats
# END Class Tokenizer
class HeadingFormatter:
@@ -1067,5 +1156,3 @@ class HeadingFormatter:
hFormat = hFormat.replace(nwHeadFmt.CHAR_FOCUS, fText)
return hFormat
# END Class HeadingFormatter

View File

@@ -29,11 +29,50 @@ from pathlib import Path
from novelwriter.constants import nwHeadFmt, nwLabels, nwUnicode
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer
from novelwriter.core.tokenizer import T_Formats, Tokenizer
logger = logging.getLogger(__name__)
# Standard Markdown
STD_MD = {
Tokenizer.FMT_B_B: "**",
Tokenizer.FMT_B_E: "**",
Tokenizer.FMT_I_B: "_",
Tokenizer.FMT_I_E: "_",
Tokenizer.FMT_D_B: "",
Tokenizer.FMT_D_E: "",
Tokenizer.FMT_U_B: "",
Tokenizer.FMT_U_E: "",
Tokenizer.FMT_M_B: "",
Tokenizer.FMT_M_E: "",
Tokenizer.FMT_SUP_B: "",
Tokenizer.FMT_SUP_E: "",
Tokenizer.FMT_SUB_B: "",
Tokenizer.FMT_SUB_E: "",
Tokenizer.FMT_STRIP: "",
}
# Extended Markdown
EXT_MD = {
Tokenizer.FMT_B_B: "**",
Tokenizer.FMT_B_E: "**",
Tokenizer.FMT_I_B: "_",
Tokenizer.FMT_I_E: "_",
Tokenizer.FMT_D_B: "~~",
Tokenizer.FMT_D_E: "~~",
Tokenizer.FMT_U_B: "",
Tokenizer.FMT_U_E: "",
Tokenizer.FMT_M_B: "==",
Tokenizer.FMT_M_E: "==",
Tokenizer.FMT_SUP_B: "^",
Tokenizer.FMT_SUP_E: "^",
Tokenizer.FMT_SUB_B: "~",
Tokenizer.FMT_SUB_E: "~",
Tokenizer.FMT_STRIP: "",
}
class ToMarkdown(Tokenizer):
"""Core: Markdown Document Writer
@@ -42,14 +81,11 @@ class ToMarkdown(Tokenizer):
supports concatenating novelWriter markup files.
"""
M_STD = 0 # Standard Markdown
M_EXT = 1 # Extended Markdown
def __init__(self, project: NWProject) -> None:
super().__init__(project)
self._genMode = self.M_STD
self._fullMD: list[str] = []
self._preserveBreaks = True
self._usedNotes: dict[str, int] = {}
self._extended = True
return
##
@@ -65,19 +101,9 @@ class ToMarkdown(Tokenizer):
# Setters
##
def setStandardMarkdown(self) -> None:
"""Set the converter to use standard Markdown formatting."""
self._genMode = self.M_STD
return
def setExtendedMarkdown(self) -> None:
def setExtendedMarkdown(self, state: bool) -> None:
"""Set the converter to use Extended Markdown formatting."""
self._genMode = self.M_EXT
return
def setPreserveBreaks(self, state: bool) -> None:
"""Preserve line breaks in paragraphs."""
self._preserveBreaks = state
self._extended = state
return
##
@@ -90,58 +116,21 @@ class ToMarkdown(Tokenizer):
def doConvert(self) -> None:
"""Convert the list of text tokens into a Markdown document."""
if self._genMode == self.M_STD:
# Standard Markdown
mdTags = {
self.FMT_B_B: "**",
self.FMT_B_E: "**",
self.FMT_I_B: "_",
self.FMT_I_E: "_",
self.FMT_D_B: "",
self.FMT_D_E: "",
self.FMT_U_B: "",
self.FMT_U_E: "",
self.FMT_M_B: "",
self.FMT_M_E: "",
self.FMT_SUP_B: "",
self.FMT_SUP_E: "",
self.FMT_SUB_B: "",
self.FMT_SUB_E: "",
}
cSkip = ""
else:
# Extended Markdown
mdTags = {
self.FMT_B_B: "**",
self.FMT_B_E: "**",
self.FMT_I_B: "_",
self.FMT_I_E: "_",
self.FMT_D_B: "~~",
self.FMT_D_E: "~~",
self.FMT_U_B: "",
self.FMT_U_E: "",
self.FMT_M_B: "==",
self.FMT_M_E: "==",
self.FMT_SUP_B: "^",
self.FMT_SUP_E: "^",
self.FMT_SUB_B: "~",
self.FMT_SUB_E: "~",
}
cSkip = nwUnicode.U_MMSP
self._result = ""
para = []
lines = []
lineSep = " \n" if self._preserveBreaks else " "
if self._extended:
mTags = EXT_MD
cSkip = nwUnicode.U_MMSP
else:
mTags = STD_MD
cSkip = ""
lines = []
for tType, _, tText, tFormat, tStyle in self._tokens:
if tType == self.T_EMPTY:
if para:
tTemp = (lineSep.join(para)).rstrip(" ")
lines.append(f"{tTemp}\n\n")
para = []
if tType == self.T_TEXT:
tTemp = self._formatText(tText, tFormat, mTags).replace("\n", " \n")
lines.append(f"{tTemp}\n\n")
elif tType == self.T_TITLE:
tHead = tText.replace(nwHeadFmt.BR, "\n")
@@ -169,23 +158,17 @@ class ToMarkdown(Tokenizer):
elif tType == self.T_SKIP:
lines.append(f"{cSkip}\n\n")
elif tType == self.T_TEXT:
tTemp = tText
for pos, fmt in reversed(tFormat):
tTemp = f"{tTemp[:pos]}{mdTags[fmt]}{tTemp[pos:]}"
para.append(tTemp.rstrip())
elif tType == self.T_SYNOPSIS and self._doSynopsis:
label = self._localLookup("Synopsis")
lines.append(f"**{label}:** {tText}\n\n")
lines.append(f"**{label}:** {self._formatText(tText, tFormat, mTags)}\n\n")
elif tType == self.T_SHORT and self._doSynopsis:
label = self._localLookup("Short Description")
lines.append(f"**{label}:** {tText}\n\n")
lines.append(f"**{label}:** {self._formatText(tText, tFormat, mTags)}\n\n")
elif tType == self.T_COMMENT and self._doComments:
label = self._localLookup("Comment")
lines.append(f"**{label}:** {tText}\n\n")
lines.append(f"**{label}:** {self._formatText(tText, tFormat, mTags)}\n\n")
elif tType == self.T_KEYWORD and self._doKeywords:
lines.append(self._formatKeywords(tText, tStyle))
@@ -195,6 +178,27 @@ class ToMarkdown(Tokenizer):
return
def appendFootnotes(self) -> None:
"""Append the footnotes in the buffer."""
if self._usedNotes:
tags = EXT_MD if self._extended else STD_MD
footnotes = self._localLookup("Footnotes")
lines = []
lines.append(f"### {footnotes}\n\n")
for key, index in self._usedNotes.items():
if content := self._footnotes.get(key):
marker = f"{index}. "
text = self._formatText(*content, tags)
lines.append(f"{marker}{text}\n")
lines.append("\n")
result = "".join(lines)
self._result += result
self._fullMD.append(result)
return
def saveMarkdown(self, path: str | Path) -> None:
"""Save the data to a plain text file."""
with open(path, mode="w", encoding="utf-8") as outFile:
@@ -206,14 +210,31 @@ class ToMarkdown(Tokenizer):
"""Replace tabs with spaces."""
spaces = spaceChar*nSpaces
self._fullMD = [p.replace("\t", spaces) for p in self._fullMD]
if self._keepMarkdown:
self._allMarkdown = [p.replace("\t", spaces) for p in self._allMarkdown]
if self._keepMD:
self._markdown = [p.replace("\t", spaces) for p in self._markdown]
return
##
# Internal Functions
##
def _formatText(self, text: str, tFmt: T_Formats, tags: dict[int, str]) -> str:
"""Apply formatting tags to text."""
temp = text
for pos, fmt, data in reversed(tFmt):
md = ""
if fmt == self.FMT_FNOTE:
if data in self._footnotes:
index = len(self._usedNotes) + 1
self._usedNotes[data] = index
md = f"[{index}]"
else:
md = "[ERR]"
else:
md = tags.get(fmt, "")
temp = f"{temp[:pos]}{md}{temp[pos:]}"
return temp
def _formatKeywords(self, text: str, style: int) -> str:
"""Apply Markdown formatting to keywords."""
valid, bits, _ = self._project.index.scanThis("@"+text)
@@ -229,5 +250,3 @@ class ToMarkdown(Tokenizer):
result += " \n" if style & self.A_Z_BTMMRG else "\n\n"
return result
# END Class ToMarkdown

View File

@@ -29,17 +29,20 @@ from __future__ import annotations
import logging
import xml.etree.ElementTree as ET
from collections.abc import Sequence
from datetime import datetime
from hashlib import sha256
from pathlib import Path
from zipfile import ZipFile
from datetime import datetime
from collections.abc import Sequence
from PyQt5.QtGui import QFont
from novelwriter import __version__
from novelwriter.common import xmlIndent
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer, stripEscape
from novelwriter.core.tokenizer import T_Formats, Tokenizer, stripEscape
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS
logger = logging.getLogger(__name__)
@@ -96,6 +99,22 @@ M_MRK = ~X_MRK
M_SUP = ~X_SUP
M_SUB = ~X_SUB
# ODT Styles
S_TITLE = "Title"
S_HEAD1 = "Heading_20_1"
S_HEAD2 = "Heading_20_2"
S_HEAD3 = "Heading_20_3"
S_HEAD4 = "Heading_20_4"
S_SEP = "Separator"
S_FIND = "First_20_line_20_indent"
S_TEXT = "Text_20_body"
S_META = "Text_20_Meta"
S_HNF = "Header_20_and_20_Footer"
# Font Data
FONT_WEIGHT_NUM = ["100", "200", "300", "400", "500", "600", "700", "800", "900"]
FONT_WEIGHT_MAP = {"400": "normal", "700": "bold"}
class ToOdt(Tokenizer):
"""Core: Open Document Writer
@@ -130,20 +149,25 @@ class ToOdt(Tokenizer):
self._autoPara: dict[str, ODTParagraphStyle] = {} # Auto-generated paragraph styles
self._autoText: dict[int, ODTTextStyle] = {} # Auto-generated text styles
# Footnotes
self._nNote = 0
self._etNotes: dict[str, ET.Element] = {} # Generated note elements
self._errData = [] # List of errors encountered
# Properties
self._textFont = "Liberation Serif"
self._textSize = 12
self._textFixed = False
self._textFont = QFont("Liberation Serif", 12)
self._colourHead = False
self._firstIndent = False
self._headerFormat = ""
self._pageOffset = 0
# Internal
self._fontFamily = "&apos;Liberation Serif&apos;"
self._fontFamily = "Liberation Serif"
self._fontSize = 12
self._fontWeight = "normal"
self._fontStyle = "normal"
self._fontPitch = "variable"
self._fontBold = "bold"
self._fSizeTitle = "30pt"
self._fSizeHead1 = "24pt"
self._fSizeHead2 = "20pt"
@@ -151,6 +175,7 @@ class ToOdt(Tokenizer):
self._fSizeHead4 = "14pt"
self._fSizeHead = "14pt"
self._fSizeText = "12pt"
self._fSizeFoot = "10pt"
self._fLineHeight = "115%"
self._fBlockIndent = "1.693cm"
self._fTextIndent = "0.499cm"
@@ -167,6 +192,7 @@ class ToOdt(Tokenizer):
self._mTopHead = "0.423cm"
self._mTopText = "0.000cm"
self._mTopMeta = "0.000cm"
self._mTopSep = "0.247cm"
self._mBotTitle = "0.212cm"
self._mBotHead1 = "0.212cm"
@@ -176,6 +202,10 @@ class ToOdt(Tokenizer):
self._mBotHead = "0.212cm"
self._mBotText = "0.247cm"
self._mBotMeta = "0.106cm"
self._mBotSep = "0.247cm"
self._mBotFoot = "0.106cm"
self._mLeftFoot = "0.600cm"
# Document Size and Margins
self._mDocWidth = "21.0cm"
@@ -232,11 +262,6 @@ class ToOdt(Tokenizer):
self._pageOffset = offset
return
def setFirstLineIndent(self, state: bool) -> None:
"""Enable or disable first line indent."""
self._firstIndent = state
return
##
# Class Methods
##
@@ -246,18 +271,25 @@ class ToOdt(Tokenizer):
# Initialise Variables
# ====================
self._fontFamily = self._textFont
if len(self._textFont.split()) > 1:
self._fontFamily = f"'{self._textFont}'"
self._fontPitch = "fixed" if self._textFixed else "variable"
intWeight = FONT_WEIGHTS.get(self._textFont.weight(), 400)
fontWeight = str(intWeight)
fontBold = str(min(intWeight + 300, 900))
self._fSizeTitle = f"{round(2.50 * self._textSize):d}pt"
self._fSizeHead1 = f"{round(2.00 * self._textSize):d}pt"
self._fSizeHead2 = f"{round(1.60 * self._textSize):d}pt"
self._fSizeHead3 = f"{round(1.30 * self._textSize):d}pt"
self._fSizeHead4 = f"{round(1.15 * self._textSize):d}pt"
self._fSizeHead = f"{round(1.15 * self._textSize):d}pt"
self._fSizeText = f"{self._textSize:d}pt"
self._fontFamily = self._textFont.family()
self._fontSize = self._textFont.pointSize()
self._fontWeight = FONT_WEIGHT_MAP.get(fontWeight, fontWeight)
self._fontStyle = FONT_STYLE.get(self._textFont.style(), "normal")
self._fontPitch = "fixed" if self._textFont.fixedPitch() else "variable"
self._fontBold = FONT_WEIGHT_MAP.get(fontBold, fontBold)
self._fSizeTitle = f"{round(2.50 * self._fontSize):d}pt"
self._fSizeHead1 = f"{round(2.00 * self._fontSize):d}pt"
self._fSizeHead2 = f"{round(1.60 * self._fontSize):d}pt"
self._fSizeHead3 = f"{round(1.30 * self._fontSize):d}pt"
self._fSizeHead4 = f"{round(1.15 * self._fontSize):d}pt"
self._fSizeHead = f"{round(1.15 * self._fontSize):d}pt"
self._fSizeText = f"{self._fontSize:d}pt"
self._fSizeFoot = f"{round(0.8*self._fontSize):d}pt"
mScale = self._lineHeight/1.15
@@ -269,6 +301,7 @@ class ToOdt(Tokenizer):
self._mTopHead = self._emToCm(mScale * self._marginHead4[0])
self._mTopText = self._emToCm(mScale * self._marginText[0])
self._mTopMeta = self._emToCm(mScale * self._marginMeta[0])
self._mTopSep = self._emToCm(mScale * self._marginSep[0])
self._mBotTitle = self._emToCm(mScale * self._marginTitle[1])
self._mBotHead1 = self._emToCm(mScale * self._marginHead1[1])
@@ -278,6 +311,10 @@ class ToOdt(Tokenizer):
self._mBotHead = self._emToCm(mScale * self._marginHead4[1])
self._mBotText = self._emToCm(mScale * self._marginText[1])
self._mBotMeta = self._emToCm(mScale * self._marginMeta[1])
self._mBotSep = self._emToCm(mScale * self._marginSep[1])
self._mLeftFoot = self._emToCm(self._marginFoot[0])
self._mBotFoot = self._emToCm(self._marginFoot[1])
if self._colourHead:
self._colHead12 = "#2a6099"
@@ -289,6 +326,7 @@ class ToOdt(Tokenizer):
self._fLineHeight = f"{round(100 * self._lineHeight):d}%"
self._fBlockIndent = self._emToCm(self._blockIndent)
self._fTextIndent = self._emToCm(self._firstWidth)
self._textAlign = "justify" if self._doJustify else "left"
# Clear Errors
@@ -301,7 +339,7 @@ class ToOdt(Tokenizer):
tAttr[_mkTag("office", "version")] = X_VERS
fAttr = {}
fAttr[_mkTag("style", "name")] = self._textFont
fAttr[_mkTag("style", "name")] = self._fontFamily
fAttr[_mkTag("style", "font-pitch")] = self._fontPitch
if self._isFlat:
@@ -399,9 +437,7 @@ class ToOdt(Tokenizer):
"""Convert the list of text tokens into XML elements."""
self._result = "" # Not used, but cleared just in case
pFmt = []
pText = []
pStyle = None
xText = self._xText
pIndent = True
for tType, _, tText, tFormat, tStyle in self._tokens:
@@ -433,82 +469,59 @@ class ToOdt(Tokenizer):
if tStyle & self.A_IND_R:
oStyle.setMarginRight(self._fBlockIndent)
if tType not in (self.T_EMPTY, self.T_TEXT):
if not self._indentFirst and tType in self.L_SKIP_INDENT:
pIndent = False
# Process Text Types
if tType == self.T_EMPTY:
if len(pText) > 1 and pStyle is not None:
if self._doJustify:
pStyle.setTextAlign("left")
if len(pText) > 0 and pStyle is not None:
tTxt = ""
tFmt = []
for nText, nFmt in zip(pText, pFmt):
tLen = len(tTxt)
tTxt += f"{nText}\n"
tFmt.extend((p+tLen, fmt) for p, fmt in nFmt)
# Don't indent a paragraph if it has alignment set
tIndent = self._firstIndent and pIndent and pStyle.isUnaligned()
self._addTextPar(
"First_20_line_20_indent" if tIndent else "Text_20_body",
pStyle, tTxt.rstrip(), tFmt=tFmt
)
pIndent = True
pFmt = []
pText = []
pStyle = None
if tType == self.T_TEXT:
if self._firstIndent and pIndent and oStyle.isUnaligned():
self._addTextPar(xText, S_FIND, oStyle, tText, tFmt=tFormat)
else:
self._addTextPar(xText, S_TEXT, oStyle, tText, tFmt=tFormat)
pIndent = True
elif tType == self.T_TITLE:
# Title must be text:p
tHead = tText.replace(nwHeadFmt.BR, "\n")
self._addTextPar("Title", oStyle, tHead, isHead=False) # Title must be text:p
self._addTextPar(xText, S_TITLE, oStyle, tHead, isHead=False)
elif tType == self.T_HEAD1:
tHead = tText.replace(nwHeadFmt.BR, "\n")
self._addTextPar("Heading_20_1", oStyle, tHead, isHead=True, oLevel="1")
self._addTextPar(xText, S_HEAD1, oStyle, tHead, isHead=True, oLevel="1")
elif tType == self.T_HEAD2:
tHead = tText.replace(nwHeadFmt.BR, "\n")
self._addTextPar("Heading_20_2", oStyle, tHead, isHead=True, oLevel="2")
self._addTextPar(xText, S_HEAD2, oStyle, tHead, isHead=True, oLevel="2")
elif tType == self.T_HEAD3:
tHead = tText.replace(nwHeadFmt.BR, "\n")
self._addTextPar("Heading_20_3", oStyle, tHead, isHead=True, oLevel="3")
self._addTextPar(xText, S_HEAD3, oStyle, tHead, isHead=True, oLevel="3")
elif tType == self.T_HEAD4:
tHead = tText.replace(nwHeadFmt.BR, "\n")
self._addTextPar("Heading_20_4", oStyle, tHead, isHead=True, oLevel="4")
self._addTextPar(xText, S_HEAD4, oStyle, tHead, isHead=True, oLevel="4")
elif tType == self.T_SEP:
self._addTextPar("Separator", oStyle, tText)
self._addTextPar(xText, S_SEP, oStyle, tText)
elif tType == self.T_SKIP:
self._addTextPar("Separator", oStyle, "")
elif tType == self.T_TEXT:
if pStyle is None:
pStyle = oStyle
pText.append(tText)
pFmt.append(tFormat)
self._addTextPar(xText, S_TEXT, oStyle, "")
elif tType == self.T_SYNOPSIS and self._doSynopsis:
tTemp, fTemp = self._formatSynopsis(tText, True)
self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp)
tTemp, tFmt = self._formatSynopsis(tText, tFormat, True)
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
elif tType == self.T_SHORT and self._doSynopsis:
tTemp, fTemp = self._formatSynopsis(tText, False)
self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp)
tTemp, tFmt = self._formatSynopsis(tText, tFormat, False)
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
elif tType == self.T_COMMENT and self._doComments:
tTemp, fTemp = self._formatComments(tText)
self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp)
tTemp, tFmt = self._formatComments(tText, tFormat)
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
elif tType == self.T_KEYWORD and self._doKeywords:
tTemp, fTemp = self._formatKeywords(tText)
self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp)
tTemp, tFmt = self._formatKeywords(tText)
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
return
@@ -569,28 +582,32 @@ class ToOdt(Tokenizer):
# Internal Functions
##
def _formatSynopsis(self, text: str, synopsis: bool) -> tuple[str, list[tuple[int, int]]]:
def _formatSynopsis(self, text: str, fmt: T_Formats, synopsis: bool) -> tuple[str, T_Formats]:
"""Apply formatting to synopsis lines."""
name = self._localLookup("Synopsis" if synopsis else "Short Description")
shift = len(name) + 2
rTxt = f"{name}: {text}"
rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(name) + 1, self.FMT_B_E, "")]
rFmt.extend((p + shift, f, d) for p, f, d in fmt)
return rTxt, rFmt
def _formatComments(self, text: str) -> tuple[str, list[tuple[int, int]]]:
def _formatComments(self, text: str, fmt: T_Formats) -> tuple[str, T_Formats]:
"""Apply formatting to comments."""
name = self._localLookup("Comment")
shift = len(name) + 2
rTxt = f"{name}: {text}"
rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(name) + 1, self.FMT_B_E, "")]
rFmt.extend((p + shift, f, d) for p, f, d in fmt)
return rTxt, rFmt
def _formatKeywords(self, text: str) -> tuple[str, list[tuple[int, int]]]:
def _formatKeywords(self, text: str) -> tuple[str, T_Formats]:
"""Apply formatting to keywords."""
valid, bits, _ = self._project.index.scanThis("@"+text)
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
return "", []
rTxt = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: "
rFmt = [(0, self.FMT_B_B), (len(rTxt) - 1, self.FMT_B_E)]
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(rTxt) - 1, self.FMT_B_E, "")]
if len(bits) > 1:
if bits[0] == nwKeyWords.TAG_KEY:
rTxt += bits[1]
@@ -600,8 +617,8 @@ class ToOdt(Tokenizer):
return rTxt, rFmt
def _addTextPar(
self, styleName: str, oStyle: ODTParagraphStyle, tText: str,
tFmt: Sequence[tuple[int, int]] = [], isHead: bool = False, oLevel: str | None = None
self, xParent: ET.Element, styleName: str, oStyle: ODTParagraphStyle, tText: str,
tFmt: Sequence[tuple[int, int, str]] = [], isHead: bool = False, oLevel: str | None = None
) -> None:
"""Add a text paragraph to the text XML element."""
tAttr = {_mkTag("text", "style-name"): self._paraStyle(styleName, oStyle)}
@@ -609,7 +626,7 @@ class ToOdt(Tokenizer):
tAttr[_mkTag("text", "outline-level")] = oLevel
pTag = "h" if isHead else "p"
xElem = ET.SubElement(self._xText, _mkTag("text", pTag), attrib=tAttr)
xElem = ET.SubElement(xParent, _mkTag("text", pTag), attrib=tAttr)
# It's important to set the initial text field to empty, otherwise
# xmlIndent will add a line break if the first subelement is a span.
@@ -627,7 +644,13 @@ class ToOdt(Tokenizer):
xFmt = 0x00
tFrag = ""
fLast = 0
for fPos, fFmt in tFmt:
xNode = None
for fPos, fFmt, fData in tFmt:
# Add any extra nodes
if xNode is not None:
parProc.appendNode(xNode)
xNode = None
# Add the text up to the current fragment
if tFrag := tText[fLast:fPos]:
@@ -665,11 +688,18 @@ class ToOdt(Tokenizer):
xFmt |= X_SUB
elif fFmt == self.FMT_SUB_E:
xFmt &= M_SUB
elif fFmt == self.FMT_FNOTE:
xNode = self._generateFootnote(fData)
elif fFmt == self.FMT_STRIP:
pass
else:
pErr += 1
fLast = fPos
if xNode is not None:
parProc.appendNode(xNode)
if tFrag := tText[fLast:]:
if xFmt == 0x00:
parProc.appendText(tFrag)
@@ -715,7 +745,7 @@ class ToOdt(Tokenizer):
style = ODTTextStyle(f"T{len(self._autoText)+1:d}")
if hFmt & X_BLD:
style.setFontWeight("bold")
style.setFontWeight(self._fontBold)
if hFmt & X_ITA:
style.setFontStyle("italic")
if hFmt & X_DEL:
@@ -735,9 +765,25 @@ class ToOdt(Tokenizer):
return style.name
def _generateFootnote(self, key: str) -> ET.Element | None:
"""Generate a footnote XML object."""
if content := self._footnotes.get(key):
self._nNote += 1
nStyle = ODTParagraphStyle("New")
xNote = ET.Element(_mkTag("text", "note"), attrib={
_mkTag("text", "id"): f"ftn{self._nNote}",
_mkTag("text", "note-class"): "footnote",
})
xCite = ET.SubElement(xNote, _mkTag("text", "note-citation"))
xCite.text = str(self._nNote)
xBody = ET.SubElement(xNote, _mkTag("text", "note-body"))
self._addTextPar(xBody, "Footnote", nStyle, content[0], tFmt=content[1])
return xNote
return None
def _emToCm(self, value: float) -> str:
"""Converts an em value to centimetres."""
return f"{value*2.54/72*self._textSize:.3f}cm"
return f"{value*2.54/72*self._fontSize:.3f}cm"
##
# Style Elements
@@ -757,7 +803,6 @@ class ToOdt(Tokenizer):
_mkTag("fo", "margin-bottom"): self._mDocBtm,
_mkTag("fo", "margin-left"): self._mDocLeft,
_mkTag("fo", "margin-right"): self._mDocRight,
_mkTag("fo", "print-orientation"): "portrait",
})
xHead = ET.SubElement(xPage, _mkTag("style", "header-style"))
@@ -782,8 +827,10 @@ class ToOdt(Tokenizer):
_mkTag("style", "writing-mode"): "page",
})
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
_mkTag("style", "font-name"): self._textFont,
_mkTag("style", "font-name"): self._fontFamily,
_mkTag("fo", "font-family"): self._fontFamily,
_mkTag("fo", "font-weight"): self._fontWeight,
_mkTag("fo", "font-style"): self._fontStyle,
_mkTag("fo", "font-size"): self._fSizeText,
_mkTag("fo", "language"): self._dLanguage,
_mkTag("fo", "country"): self._dCountry,
@@ -796,8 +843,10 @@ class ToOdt(Tokenizer):
_mkTag("style", "class"): "text",
})
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
_mkTag("style", "font-name"): self._textFont,
_mkTag("style", "font-name"): self._fontFamily,
_mkTag("fo", "font-family"): self._fontFamily,
_mkTag("fo", "font-weight"): self._fontWeight,
_mkTag("fo", "font-style"): self._fontStyle,
_mkTag("fo", "font-size"): self._fSizeText,
})
@@ -806,7 +855,7 @@ class ToOdt(Tokenizer):
_mkTag("style", "name"): "Heading",
_mkTag("style", "family"): "paragraph",
_mkTag("style", "parent-style-name"): "Standard",
_mkTag("style", "next-style-name"): "Text_20_body",
_mkTag("style", "next-style-name"): S_TEXT,
_mkTag("style", "class"): "text",
})
ET.SubElement(xStyl, _mkTag("style", "paragraph-properties"), attrib={
@@ -815,14 +864,16 @@ class ToOdt(Tokenizer):
_mkTag("fo", "keep-with-next"): "always",
})
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
_mkTag("style", "font-name"): self._textFont,
_mkTag("style", "font-name"): self._fontFamily,
_mkTag("fo", "font-family"): self._fontFamily,
_mkTag("fo", "font-weight"): self._fontWeight,
_mkTag("fo", "font-style"): self._fontStyle,
_mkTag("fo", "font-size"): self._fSizeHead,
})
# Add Header and Footer Styles
ET.SubElement(self._xStyl, _mkTag("style", "style"), attrib={
_mkTag("style", "name"): "Header_20_and_20_Footer",
_mkTag("style", "name"): S_HNF,
_mkTag("style", "display-name"): "Header and Footer",
_mkTag("style", "family"): "paragraph",
_mkTag("style", "parent-style-name"): "Standard",
@@ -834,7 +885,7 @@ class ToOdt(Tokenizer):
def _useableStyles(self) -> None:
"""Set the usable styles."""
# Add Text Body Style
style = ODTParagraphStyle("Text_20_body")
style = ODTParagraphStyle(S_TEXT)
style.setDisplayName("Text body")
style.setParentStyleName("Standard")
style.setClass("text")
@@ -842,136 +893,139 @@ class ToOdt(Tokenizer):
style.setMarginBottom(self._mBotText)
style.setLineHeight(self._fLineHeight)
style.setTextAlign(self._textAlign)
style.setFontName(self._textFont)
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeText)
style.setFontWeight(self._fontWeight)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
# Add First Line Indent Style
style = ODTParagraphStyle("First_20_line_20_indent")
style = ODTParagraphStyle(S_FIND)
style.setDisplayName("First line indent")
style.setParentStyleName("Text_20_body")
style.setParentStyleName(S_TEXT)
style.setClass("text")
style.setTextIndent(self._fTextIndent)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
# Add Text Meta Style
style = ODTParagraphStyle("Text_20_Meta")
style = ODTParagraphStyle(S_META)
style.setDisplayName("Text Meta")
style.setParentStyleName("Standard")
style.setClass("text")
style.setMarginTop(self._mTopMeta)
style.setMarginBottom(self._mBotMeta)
style.setLineHeight(self._fLineHeight)
style.setFontName(self._textFont)
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeText)
style.setFontWeight(self._fontWeight)
style.setColour(self._colMetaTx)
style.setOpacity(self._opaMetaTx)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
# Add Title Style
style = ODTParagraphStyle("Title")
style = ODTParagraphStyle(S_TITLE)
style.setDisplayName("Title")
style.setParentStyleName("Heading")
style.setNextStyleName("Text_20_body")
style.setNextStyleName(S_TEXT)
style.setClass("chapter")
style.setMarginTop(self._mTopTitle)
style.setMarginBottom(self._mBotTitle)
style.setTextAlign("center")
style.setFontName(self._textFont)
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeTitle)
style.setFontWeight("bold")
style.setFontWeight(self._fontBold)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
# Add Separator Style
style = ODTParagraphStyle("Separator")
style = ODTParagraphStyle(S_SEP)
style.setDisplayName("Separator")
style.setParentStyleName("Standard")
style.setNextStyleName("Text_20_body")
style.setNextStyleName(S_TEXT)
style.setClass("text")
style.setMarginTop(self._mTopText)
style.setMarginBottom(self._mBotText)
style.setMarginTop(self._mTopSep)
style.setMarginBottom(self._mBotSep)
style.setLineHeight(self._fLineHeight)
style.setTextAlign("center")
style.setFontName(self._textFont)
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeText)
style.setFontWeight(self._fontWeight)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
# Add Heading 1 Style
style = ODTParagraphStyle("Heading_20_1")
style = ODTParagraphStyle(S_HEAD1)
style.setDisplayName("Heading 1")
style.setParentStyleName("Heading")
style.setNextStyleName("Text_20_body")
style.setNextStyleName(S_TEXT)
style.setOutlineLevel("1")
style.setClass("text")
style.setMarginTop(self._mTopHead1)
style.setMarginBottom(self._mBotHead1)
style.setFontName(self._textFont)
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeHead1)
style.setFontWeight("bold")
style.setFontWeight(self._fontBold)
style.setColour(self._colHead12)
style.setOpacity(self._opaHead12)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
# Add Heading 2 Style
style = ODTParagraphStyle("Heading_20_2")
style = ODTParagraphStyle(S_HEAD2)
style.setDisplayName("Heading 2")
style.setParentStyleName("Heading")
style.setNextStyleName("Text_20_body")
style.setNextStyleName(S_TEXT)
style.setOutlineLevel("2")
style.setClass("text")
style.setMarginTop(self._mTopHead2)
style.setMarginBottom(self._mBotHead2)
style.setFontName(self._textFont)
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeHead2)
style.setFontWeight("bold")
style.setFontWeight(self._fontBold)
style.setColour(self._colHead12)
style.setOpacity(self._opaHead12)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
# Add Heading 3 Style
style = ODTParagraphStyle("Heading_20_3")
style = ODTParagraphStyle(S_HEAD3)
style.setDisplayName("Heading 3")
style.setParentStyleName("Heading")
style.setNextStyleName("Text_20_body")
style.setNextStyleName(S_TEXT)
style.setOutlineLevel("3")
style.setClass("text")
style.setMarginTop(self._mTopHead3)
style.setMarginBottom(self._mBotHead3)
style.setFontName(self._textFont)
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeHead3)
style.setFontWeight("bold")
style.setFontWeight(self._fontBold)
style.setColour(self._colHead34)
style.setOpacity(self._opaHead34)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
# Add Heading 4 Style
style = ODTParagraphStyle("Heading_20_4")
style = ODTParagraphStyle(S_HEAD4)
style.setDisplayName("Heading 4")
style.setParentStyleName("Heading")
style.setNextStyleName("Text_20_body")
style.setNextStyleName(S_TEXT)
style.setOutlineLevel("4")
style.setClass("text")
style.setMarginTop(self._mTopHead4)
style.setMarginBottom(self._mBotHead4)
style.setFontName(self._textFont)
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeHead4)
style.setFontWeight("bold")
style.setFontWeight(self._fontBold)
style.setColour(self._colHead34)
style.setOpacity(self._opaHead34)
style.packXML(self._xStyl)
@@ -980,11 +1034,23 @@ class ToOdt(Tokenizer):
# Add Header Style
style = ODTParagraphStyle("Header")
style.setDisplayName("Header")
style.setParentStyleName("Header_20_and_20_Footer")
style.setParentStyleName(S_HNF)
style.setTextAlign("right")
style.packXML(self._xStyl)
self._mainPara[style.name] = style
# Add Footnote Style
style = ODTParagraphStyle("Footnote")
style.setDisplayName("Footnote")
style.setParentStyleName("Standard")
style.setClass("extra")
style.setMarginLeft(self._mLeftFoot)
style.setMarginBottom(self._mBotFoot)
style.setTextIndent("-"+self._mLeftFoot)
style.setFontSize(self._fSizeFoot)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
return
def _writeHeader(self) -> None:
@@ -1026,12 +1092,9 @@ class ToOdt(Tokenizer):
return
# END Class ToOdt
# =============================================================================================== #
# Auto-Style Classes
# =============================================================================================== #
# Auto-Style Classes
# ==================
class ODTParagraphStyle:
"""Wrapper class for the paragraph style setting used by the
@@ -1041,8 +1104,8 @@ class ODTParagraphStyle:
VALID_ALIGN = ["start", "center", "end", "justify", "inside", "outside", "left", "right"]
VALID_BREAK = ["auto", "column", "page", "even-page", "odd-page", "inherit"]
VALID_LEVEL = ["1", "2", "3", "4"]
VALID_CLASS = ["text", "chapter"]
VALID_WEIGHT = ["normal", "inherit", "bold"]
VALID_CLASS = ["text", "chapter", "extra"]
VALID_WEIGHT = ["normal", "bold"] + FONT_WEIGHT_NUM
def __init__(self, name: str) -> None:
@@ -1279,16 +1342,14 @@ class ODTParagraphStyle:
return
# END Class ODTParagraphStyle
class ODTTextStyle:
"""Wrapper class for the text style setting used by the exporter.
Only the used settings are exposed here to keep the class minimal
and fast.
"""
VALID_WEIGHT = ["normal", "inherit", "bold"]
VALID_STYLE = ["normal", "inherit", "italic"]
VALID_WEIGHT = ["normal", "bold"] + FONT_WEIGHT_NUM
VALID_STYLE = ["normal", "italic", "oblique"]
VALID_POS = ["super", "sub"]
VALID_LSTYLE = ["none", "solid"]
VALID_LTYPE = ["single", "double"]
@@ -1404,12 +1465,9 @@ class ODTTextStyle:
ET.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=attr)
return
# END Class ODTTextStyle
# =============================================================================================== #
# XML Complex Element Helper Class
# =============================================================================================== #
# XML Complex Element Helper Class
# ================================
X_ROOT_TEXT = 0
X_ROOT_TAIL = 1
@@ -1464,7 +1522,6 @@ class XMLParagraph:
if c == " ":
nSpaces += 1
continue
elif nSpaces > 0:
self._processSpaces(nSpaces)
nSpaces = 0
@@ -1475,26 +1532,22 @@ class XMLParagraph:
self._xTail.tail = ""
self._nState = X_ROOT_TAIL
self._chrPos += 1
elif self._nState in (X_SPAN_TEXT, X_SPAN_SING):
self._xSing = ET.SubElement(self._xTail, TAG_BR)
self._xSing.tail = ""
self._nState = X_SPAN_SING
self._chrPos += 1
elif c == "\t":
if self._nState in (X_ROOT_TEXT, X_ROOT_TAIL):
self._xTail = ET.SubElement(self._xRoot, TAG_TAB)
self._xTail.tail = ""
self._nState = X_ROOT_TAIL
self._chrPos += 1
elif self._nState in (X_SPAN_TEXT, X_SPAN_SING):
self._xSing = ET.SubElement(self._xTail, TAG_TAB)
self._xSing.tail = ""
self._chrPos += 1
self._nState = X_SPAN_SING
else:
if self._nState == X_ROOT_TEXT:
self._xRoot.text = (self._xRoot.text or "") + c
@@ -1529,6 +1582,19 @@ class XMLParagraph:
self._nState = X_ROOT_TAIL
return
def appendNode(self, xNode: ET.Element | None) -> None:
"""Append an XML node to the paragraph. We only check for the
X_ROOT_TEXT and X_ROOT_TAIL states. X_SPAN_TEXT is not possible
at all, and X_SPAN_SING only happens internally in an appendSpan
call, returning us to an X_ROOT_TAIL state.
"""
if xNode is not None and self._nState in (X_ROOT_TEXT, X_ROOT_TAIL):
self._xRoot.append(xNode)
self._xTail = xNode
self._xTail.tail = ""
self._nState = X_ROOT_TAIL
return
def checkError(self) -> tuple[int, str]:
"""Check that the number of characters written matches the
number of characters received.
@@ -1600,5 +1666,3 @@ class XMLParagraph:
self._chrPos += nSpaces - 1
return
# END Class XMLParagraph

403
novelwriter/core/toqdoc.py Normal file
View File

@@ -0,0 +1,403 @@
"""
novelWriter – QTextDocument Converter
=====================================
File History:
Created: 2024-05-21 [2.5b1] ToQTextDocument
This file is a part of novelWriter
Copyright 2018–2024, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import logging
from PyQt5.QtGui import (
QColor, QFont, QFontMetrics, QTextBlockFormat, QTextCharFormat,
QTextCursor, QTextDocument
)
from novelwriter.constants import nwHeaders, nwHeadFmt, nwKeyWords, nwLabels, nwUnicode
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import T_Formats, Tokenizer
from novelwriter.types import (
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight,
QtBlack, QtPageBreakAfter, QtPageBreakBefore, QtTransparent,
QtVAlignNormal, QtVAlignSub, QtVAlignSuper
)
logger = logging.getLogger(__name__)
T_TextStyle = tuple[QTextBlockFormat, QTextCharFormat]
class TextDocumentTheme:
text: QColor = QtBlack
highlight: QColor = QtTransparent
head: QColor = QtBlack
comment: QColor = QtBlack
note: QColor = QtBlack
code: QColor = QtBlack
modifier: QColor = QtBlack
keyword: QColor = QtBlack
tag: QColor = QtBlack
optional: QColor = QtBlack
def newBlock(cursor: QTextCursor, bFmt: QTextBlockFormat) -> None:
if cursor.position() > 0:
cursor.insertBlock(bFmt)
else:
cursor.setBlockFormat(bFmt)
class ToQTextDocument(Tokenizer):
"""Core: QTextDocument Writer
Extend the Tokenizer class to generate a QTextDocument output. This
is intended for usage in the document viewer and build tool preview.
"""
def __init__(self, project: NWProject) -> None:
super().__init__(project)
self._document = QTextDocument()
self._document.setUndoRedoEnabled(False)
self._document.setDocumentMargin(0)
self._theme = TextDocumentTheme()
self._styles: dict[int, T_TextStyle] = {}
self._usedNotes: dict[str, int] = {}
self._init = False
self._bold = QFont.Weight.Bold
self._normal = QFont.Weight.Normal
return
def initDocument(self, font: QFont, theme: TextDocumentTheme) -> None:
"""Initialise all computed values of the document."""
self._textFont = font
self._theme = theme
self._document.setUndoRedoEnabled(False)
self._document.blockSignals(True)
self._document.clear()
self._document.setDefaultFont(self._textFont)
qMetric = QFontMetrics(self._textFont)
mScale = qMetric.height()
fPt = self._textFont.pointSizeF()
# Scaled Sizes
# ============
self._mHead = {
self.T_TITLE: (mScale * self._marginTitle[0], mScale * self._marginTitle[1]),
self.T_HEAD1: (mScale * self._marginHead1[0], mScale * self._marginHead1[1]),
self.T_HEAD2: (mScale * self._marginHead2[0], mScale * self._marginHead2[1]),
self.T_HEAD3: (mScale * self._marginHead3[0], mScale * self._marginHead3[1]),
self.T_HEAD4: (mScale * self._marginHead4[0], mScale * self._marginHead4[1]),
}
self._sHead = {
self.T_TITLE: nwHeaders.H_SIZES.get(0, 1.0) * fPt,
self.T_HEAD1: nwHeaders.H_SIZES.get(1, 1.0) * fPt,
self.T_HEAD2: nwHeaders.H_SIZES.get(2, 1.0) * fPt,
self.T_HEAD3: nwHeaders.H_SIZES.get(3, 1.0) * fPt,
self.T_HEAD4: nwHeaders.H_SIZES.get(4, 1.0) * fPt,
}
self._mText = (mScale * self._marginText[0], mScale * self._marginText[1])
self._mMeta = (mScale * self._marginMeta[0], mScale * self._marginMeta[1])
self._mSep = (mScale * self._marginSep[0], mScale * self._marginSep[1])
self._mIndent = mScale * 2.0
# Block Format
# ============
self._blockFmt = QTextBlockFormat()
self._blockFmt.setTopMargin(self._mText[0])
self._blockFmt.setBottomMargin(self._mText[1])
self._blockFmt.setAlignment(QtAlignJustify if self._doJustify else QtAlignAbsolute)
# Character Formats
# =================
self._cText = QTextCharFormat()
self._cText.setBackground(QtTransparent)
self._cText.setForeground(self._theme.text)
self._cHead = QTextCharFormat(self._cText)
self._cHead.setForeground(self._theme.head)
self._cComment = QTextCharFormat(self._cText)
self._cComment.setForeground(self._theme.comment)
self._cCommentMod = QTextCharFormat(self._cText)
self._cCommentMod.setForeground(self._theme.comment)
self._cCommentMod.setFontWeight(self._bold)
self._cNote = QTextCharFormat(self._cText)
self._cNote.setForeground(self._theme.note)
self._cCode = QTextCharFormat(self._cText)
self._cCode.setForeground(self._theme.code)
self._cModifier = QTextCharFormat(self._cText)
self._cModifier.setForeground(self._theme.modifier)
self._cModifier.setFontWeight(self._bold)
self._cKeyword = QTextCharFormat(self._cText)
self._cKeyword.setForeground(self._theme.keyword)
self._cTag = QTextCharFormat(self._cText)
self._cTag.setForeground(self._theme.tag)
self._cOptional = QTextCharFormat(self._cText)
self._cOptional.setForeground(self._theme.optional)
self._init = True
return
##
# Properties
##
@property
def document(self) -> QTextDocument:
"""Return the document."""
return self._document
##
# Class Methods
##
def doConvert(self) -> None:
"""Write text tokens into the document."""
if not self._init:
return
self._document.blockSignals(True)
cursor = QTextCursor(self._document)
cursor.movePosition(QTextCursor.MoveOperation.End)
for tType, nHead, tText, tFormat, tStyle in self._tokens:
# Styles
bFmt = QTextBlockFormat(self._blockFmt)
if tStyle is not None:
if tStyle & self.A_LEFT:
bFmt.setAlignment(QtAlignLeft)
elif tStyle & self.A_RIGHT:
bFmt.setAlignment(QtAlignRight)
elif tStyle & self.A_CENTRE:
bFmt.setAlignment(QtAlignCenter)
elif tStyle & self.A_JUSTIFY:
bFmt.setAlignment(QtAlignJustify)
if tStyle & self.A_PBB:
bFmt.setPageBreakPolicy(QtPageBreakBefore)
if tStyle & self.A_PBA:
bFmt.setPageBreakPolicy(QtPageBreakAfter)
if tStyle & self.A_Z_BTMMRG:
bFmt.setBottomMargin(0.0)
if tStyle & self.A_Z_TOPMRG:
bFmt.setTopMargin(0.0)
if tStyle & self.A_IND_L:
bFmt.setLeftMargin(self._mIndent)
if tStyle & self.A_IND_R:
bFmt.setRightMargin(self._mIndent)
if tType == self.T_TEXT:
newBlock(cursor, bFmt)
self._insertFragments(tText, tFormat, cursor, self._cText)
elif tType in self.L_HEADINGS:
bFmt, cFmt = self._genHeadStyle(tType, nHead, bFmt)
newBlock(cursor, bFmt)
cursor.insertText(tText.replace(nwHeadFmt.BR, "\n"), cFmt)
elif tType == self.T_SEP:
sFmt = QTextBlockFormat(bFmt)
sFmt.setTopMargin(self._mSep[0])
sFmt.setBottomMargin(self._mSep[1])
newBlock(cursor, sFmt)
cursor.insertText(tText, self._cText)
elif tType == self.T_SKIP:
newBlock(cursor, bFmt)
cursor.insertText(nwUnicode.U_NBSP, self._cText)
elif tType in self.L_SUMMARY and self._doSynopsis:
newBlock(cursor, bFmt)
modifier = self._localLookup(
"Short Description" if tType == self.T_SHORT else "Synopsis"
)
cursor.insertText(f"{modifier}: ", self._cModifier)
self._insertFragments(tText, tFormat, cursor, self._cNote)
elif tType == self.T_COMMENT and self._doComments:
newBlock(cursor, bFmt)
modifier = self._localLookup("Comment")
cursor.insertText(f"{modifier}: ", self._cCommentMod)
self._insertFragments(tText, tFormat, cursor, self._cComment)
elif tType == self.T_KEYWORD and self._doKeywords:
newBlock(cursor, bFmt)
self._insertKeywords(tText, cursor)
self._document.blockSignals(False)
return
def appendFootnotes(self) -> None:
"""Append the footnotes in the buffer."""
if self._usedNotes:
self._document.blockSignals(True)
cursor = QTextCursor(self._document)
cursor.movePosition(QTextCursor.MoveOperation.End)
bFmt, cFmt = self._genHeadStyle(self.T_HEAD4, -1, self._blockFmt)
newBlock(cursor, bFmt)
cursor.insertText(self._localLookup("Footnotes"), cFmt)
for key, index in self._usedNotes.items():
if content := self._footnotes.get(key):
cFmt = QTextCharFormat(self._cCode)
cFmt.setAnchor(True)
cFmt.setAnchorNames([f"footnote_{index}"])
newBlock(cursor, self._blockFmt)
cursor.insertText(f"{index}. ", cFmt)
self._insertFragments(*content, cursor, self._cText)
self._document.blockSignals(False)
return
##
# Internal Functions
##
def _insertFragments(
self, text: str, tFmt: T_Formats, cursor: QTextCursor, dFmt: QTextCharFormat
) -> None:
"""Apply formatting tags to text."""
cFmt = QTextCharFormat(dFmt)
start = 0
temp = text.replace("\n", nwUnicode.U_LSEP)
for pos, fmt, data in tFmt:
# Insert buffer with previous format
cursor.insertText(temp[start:pos], cFmt)
# Construct next format
if fmt == self.FMT_B_B:
cFmt.setFontWeight(self._bold)
elif fmt == self.FMT_B_E:
cFmt.setFontWeight(self._normal)
elif fmt == self.FMT_I_B:
cFmt.setFontItalic(True)
elif fmt == self.FMT_I_E:
cFmt.setFontItalic(False)
elif fmt == self.FMT_D_B:
cFmt.setFontStrikeOut(True)
elif fmt == self.FMT_D_E:
cFmt.setFontStrikeOut(False)
elif fmt == self.FMT_U_B:
cFmt.setFontUnderline(True)
elif fmt == self.FMT_U_E:
cFmt.setFontUnderline(False)
elif fmt == self.FMT_M_B:
cFmt.setBackground(self._theme.highlight)
elif fmt == self.FMT_M_E:
cFmt.setBackground(QtTransparent)
elif fmt == self.FMT_SUP_B:
cFmt.setVerticalAlignment(QtVAlignSuper)
elif fmt == self.FMT_SUP_E:
cFmt.setVerticalAlignment(QtVAlignNormal)
elif fmt == self.FMT_SUB_B:
cFmt.setVerticalAlignment(QtVAlignSub)
elif fmt == self.FMT_SUB_E:
cFmt.setVerticalAlignment(QtVAlignNormal)
elif fmt == self.FMT_FNOTE:
xFmt = QTextCharFormat(self._cCode)
xFmt.setVerticalAlignment(QtVAlignSuper)
if data in self._footnotes:
index = len(self._usedNotes) + 1
self._usedNotes[data] = index
xFmt.setAnchor(True)
xFmt.setAnchorHref(f"#footnote_{index}")
xFmt.setFontUnderline(True)
cursor.insertText(f"[{index}]", xFmt)
else:
cursor.insertText("[ERR]", cFmt)
# Move pos for next pass
start = pos
# Insert whatever is left in the buffer
cursor.insertText(temp[start:], cFmt)
return
def _insertKeywords(self, text: str, cursor: QTextCursor) -> None:
"""Apply Markdown formatting to keywords."""
valid, bits, _ = self._project.index.scanThis("@"+text)
if valid and bits:
key = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: "
cursor.insertText(key, self._cKeyword)
if (num := len(bits)) > 1:
if bits[0] == nwKeyWords.TAG_KEY:
one, two = self._project.index.parseValue(bits[1])
cFmt = QTextCharFormat(self._cTag)
cFmt.setAnchor(True)
cFmt.setAnchorNames([f"tag_{one}".lower()])
cursor.insertText(one, cFmt)
if two:
cursor.insertText(" | ", self._cText)
cursor.insertText(two, self._cOptional)
else:
for n, bit in enumerate(bits[1:], 2):
cFmt = QTextCharFormat(self._cTag)
cFmt.setFontUnderline(True)
cFmt.setAnchor(True)
cFmt.setAnchorHref(f"#tag_{bit}".lower())
cursor.insertText(bit, cFmt)
if n < num:
cursor.insertText(", ", self._cText)
return
def _genHeadStyle(self, hType: int, nHead: int, rFmt: QTextBlockFormat) -> T_TextStyle:
"""Generate a heading style set."""
mTop, mBottom = self._mHead.get(hType, (0.0, 0.0))
bFmt = QTextBlockFormat(rFmt)
bFmt.setTopMargin(mTop)
bFmt.setBottomMargin(mBottom)
cFmt = QTextCharFormat(self._cText if hType == self.T_TITLE else self._cHead)
cFmt.setFontWeight(self._bold)
cFmt.setFontPointSize(self._sHead.get(hType, 1.0))
if nHead >= 0:
cFmt.setAnchorNames([f"{self._handle}:T{nHead:04d}"])
cFmt.setAnchor(True)
return bFmt, cFmt

View File

@@ -23,18 +23,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import random
import logging
import random
from typing import TYPE_CHECKING, Literal, overload
from pathlib import Path
from collections.abc import Iterable, Iterator
from pathlib import Path
from typing import TYPE_CHECKING, Literal, overload
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
from novelwriter.common import isHandle
from novelwriter.constants import nwFiles
from novelwriter.core.item import NWItem
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
if TYPE_CHECKING: # pragma: no cover
from novelwriter.core.project import NWProject
@@ -520,5 +520,3 @@ class NWTree:
handle = self._makeHandle()
return handle
# END Class NWTree

View File

@@ -27,20 +27,20 @@ import logging
from PyQt5.QtGui import QCloseEvent, QColor
from PyQt5.QtWidgets import (
QDialog, QDialogButtonBox, QHBoxLayout, QLabel, QTextBrowser, QVBoxLayout,
QWidget
QDialogButtonBox, QHBoxLayout, QLabel, QTextBrowser, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import cssCol, readTextFile
from novelwriter.extensions.configlayout import NColourLabel
from novelwriter.extensions.modified import NNonBlockingDialog
from novelwriter.extensions.versioninfo import VersionInfoWidget
from novelwriter.types import QtAlignRightTop, QtDialogClose
logger = logging.getLogger(__name__)
class GuiAbout(QDialog):
class GuiAbout(NNonBlockingDialog):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -152,5 +152,3 @@ class GuiAbout(QDialog):
f"QTextBrowser {{border: none; background: {baseCol};}} "
)
return
# END Class GuiAbout

View File

@@ -34,8 +34,8 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.configlayout import NColourLabel
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtDialogCancel, QtDialogOk, QtDialogReset, QtUserRole
logger = logging.getLogger(__name__)
@@ -182,5 +182,3 @@ class GuiDocMerge(QDialog):
self.listBox.addItem(newItem)
return
# END Class GuiDocMerge

View File

@@ -34,8 +34,8 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.configlayout import NColourLabel
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtDialogCancel, QtDialogOk, QtUserRole
logger = logging.getLogger(__name__)
@@ -257,5 +257,3 @@ class GuiDocSplit(QDialog):
self.listBox.addItem(newItem)
return
# END Class GuiDocSplit

View File

@@ -94,5 +94,3 @@ class GuiEditLabel(QDialog):
accepted = cls.result() == QDialog.DialogCode.Accepted
cls.deleteLater()
return label, accepted
# END Class GuiEditLabel

View File

@@ -27,14 +27,14 @@ from __future__ import annotations
import logging
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QFont, QKeyEvent, QKeySequence
from PyQt5.QtGui import QCloseEvent, QKeyEvent, QKeySequence
from PyQt5.QtWidgets import (
QAbstractButton, QApplication, QCompleter, QDialog, QDialogButtonBox,
QFileDialog, QFontDialog, QHBoxLayout, QLineEdit, QPushButton, QVBoxLayout,
QWidget
QFileDialog, QHBoxLayout, QLineEdit, QPushButton, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import describeFont
from novelwriter.constants import nwConst, nwUnicode
from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.extensions.configlayout import NColourLabel, NScrollableForm
@@ -133,7 +133,13 @@ class GuiPreferences(QDialog):
"""Build the settings form."""
section = 0
iSz = SHARED.theme.baseIconSize
boxFixed = 5*SHARED.theme.textNWidth
minWidth = CONFIG.pxInt(200)
fontWidth = CONFIG.pxInt(162)
# Temporary Variables
self._guiFont = CONFIG.guiFont
self._textFont = CONFIG.textFont
# Label
self.sidebar.addLabel(self.tr("General"))
@@ -173,27 +179,17 @@ class GuiPreferences(QDialog):
# Application Font Family
self.guiFont = QLineEdit(self)
self.guiFont.setReadOnly(True)
self.guiFont.setMinimumWidth(CONFIG.pxInt(162))
self.guiFont.setText(CONFIG.guiFont)
self.guiFontButton = NIconToolButton(self, iSz, "more")
self.guiFont.setMinimumWidth(fontWidth)
self.guiFont.setText(describeFont(self._guiFont))
self.guiFont.setCursorPosition(0)
self.guiFontButton = NIconToolButton(self, iSz, "font")
self.guiFontButton.clicked.connect(self._selectGuiFont)
self.mainForm.addRow(
self.tr("Application font family"), self.guiFont,
self.tr("Application font"), self.guiFont,
self.tr("Requires restart to take effect."), stretch=(3, 2),
button=self.guiFontButton
)
# Application Font Size
self.guiFontSize = NSpinBox(self)
self.guiFontSize.setMinimum(8)
self.guiFontSize.setMaximum(60)
self.guiFontSize.setSingleStep(1)
self.guiFontSize.setValue(CONFIG.guiFontSize)
self.mainForm.addRow(
self.tr("Application font size"), self.guiFontSize,
self.tr("Requires restart to take effect."), unit=self.tr("pt")
)
# Vertical Scrollbars
self.hideVScroll = NSwitch(self)
self.hideVScroll.setChecked(CONFIG.hideVScroll)
@@ -210,6 +206,14 @@ class GuiPreferences(QDialog):
self.tr("Scrolling available with mouse wheel and keys only.")
)
# Native Font Dialog
self.nativeFont = NSwitch(self)
self.nativeFont.setChecked(CONFIG.nativeFont)
self.mainForm.addRow(
self.tr("Use the system's font selection dialog"), self.nativeFont,
self.tr("Turn off to use the Qt font dialog, which may have more options.")
)
# Document Style
# ==============
@@ -233,27 +237,17 @@ class GuiPreferences(QDialog):
# Document Font Family
self.textFont = QLineEdit(self)
self.textFont.setReadOnly(True)
self.textFont.setMinimumWidth(CONFIG.pxInt(162))
self.textFont.setText(CONFIG.textFont)
self.textFontButton = NIconToolButton(self, iSz, "more")
self.textFont.setMinimumWidth(fontWidth)
self.textFont.setText(describeFont(CONFIG.textFont))
self.textFont.setCursorPosition(0)
self.textFontButton = NIconToolButton(self, iSz, "font")
self.textFontButton.clicked.connect(self._selectTextFont)
self.mainForm.addRow(
self.tr("Document font family"), self.textFont,
self.tr("Document font"), self.textFont,
self.tr("Applies to both document editor and viewer."), stretch=(3, 2),
button=self.textFontButton
)
# Document Font Size
self.textSize = NSpinBox(self)
self.textSize.setMinimum(8)
self.textSize.setMaximum(60)
self.textSize.setSingleStep(1)
self.textSize.setValue(CONFIG.textSize)
self.mainForm.addRow(
self.tr("Document font size"), self.textSize,
self.tr("Applies to both document editor and viewer."), unit=self.tr("pt")
)
# Emphasise Labels
self.emphLabels = NSwitch(self)
self.emphLabels.setChecked(CONFIG.emphLabels)
@@ -538,26 +532,59 @@ class GuiPreferences(QDialog):
self.sidebar.addButton(title, section)
self.mainForm.addGroupLabel(title, section)
self.highlightQuotes = NSwitch(self)
self.highlightQuotes.setChecked(CONFIG.highlightQuotes)
self.highlightQuotes.toggled.connect(self._toggleHighlightQuotes)
self.dialogStyle = NComboBox(self)
self.dialogStyle.addItem(self.tr("None"), 0)
self.dialogStyle.addItem(self.tr("Single Quotes"), 1)
self.dialogStyle.addItem(self.tr("Double Quotes"), 2)
self.dialogStyle.addItem(self.tr("Both"), 3)
self.dialogStyle.setCurrentData(CONFIG.dialogStyle, 2)
self.mainForm.addRow(
self.tr("Highlight text wrapped in quotes"), self.highlightQuotes,
self.tr("Applies to the document editor only.")
self.tr("Highlight dialogue"), self.dialogStyle,
self.tr("Applies to the selected quote styles.")
)
self.allowOpenSQuote = NSwitch(self)
self.allowOpenSQuote.setChecked(CONFIG.allowOpenSQuote)
self.allowOpenDial = NSwitch(self)
self.allowOpenDial.setChecked(CONFIG.allowOpenDial)
self.mainForm.addRow(
self.tr("Allow open-ended single quotes"), self.allowOpenSQuote,
self.tr("Highlight single-quoted line with no closing quote.")
self.tr("Allow open-ended sialogue"), self.allowOpenDial,
self.tr("Highlight dialogue line with no closing quote.")
)
self.allowOpenDQuote = NSwitch(self)
self.allowOpenDQuote.setChecked(CONFIG.allowOpenDQuote)
self.narratorBreak = QLineEdit(self)
self.narratorBreak.setMaxLength(1)
self.narratorBreak.setFixedWidth(boxFixed)
self.narratorBreak.setAlignment(QtAlignCenter)
self.narratorBreak.setText(CONFIG.narratorBreak)
self.mainForm.addRow(
self.tr("Allow open-ended double quotes"), self.allowOpenDQuote,
self.tr("Highlight double-quoted line with no closing quote.")
self.tr("Dialogue narrator break symbol"), self.narratorBreak,
self.tr("Symbol to indicate injected narrator break.")
)
self.dialogLine = QLineEdit(self)
self.dialogLine.setMaxLength(1)
self.dialogLine.setFixedWidth(boxFixed)
self.dialogLine.setAlignment(QtAlignCenter)
self.dialogLine.setText(CONFIG.dialogLine)
self.mainForm.addRow(
self.tr("Dialogue line symbol"), self.dialogLine,
self.tr("Lines starting with this symbol are dialogue.")
)
self.altDialogOpen = QLineEdit(self)
self.altDialogOpen.setMaxLength(4)
self.altDialogOpen.setFixedWidth(boxFixed)
self.altDialogOpen.setAlignment(QtAlignCenter)
self.altDialogOpen.setText(CONFIG.altDialogOpen)
self.altDialogClose = QLineEdit(self)
self.altDialogClose.setMaxLength(4)
self.altDialogClose.setFixedWidth(boxFixed)
self.altDialogClose.setAlignment(QtAlignCenter)
self.altDialogClose.setText(CONFIG.altDialogClose)
self.mainForm.addRow(
self.tr("Alternative dialog symbols"), [self.altDialogOpen, self.altDialogClose],
self.tr("Custom highlighting of dialog text.")
)
self.highlightEmph = NSwitch(self)
@@ -667,16 +694,15 @@ class GuiPreferences(QDialog):
self.mainForm.addGroupLabel(title, section)
self.quoteSym = {}
qWidth = CONFIG.pxInt(40)
# Single Quote Style
self.quoteSym["SO"] = QLineEdit(self)
self.quoteSym["SO"].setMaxLength(1)
self.quoteSym["SO"].setReadOnly(True)
self.quoteSym["SO"].setFixedWidth(qWidth)
self.quoteSym["SO"].setFixedWidth(boxFixed)
self.quoteSym["SO"].setAlignment(QtAlignCenter)
self.quoteSym["SO"].setText(CONFIG.fmtSQuoteOpen)
self.btnSingleStyleO = NIconToolButton(self, iSz, "more")
self.btnSingleStyleO = NIconToolButton(self, iSz, "quote")
self.btnSingleStyleO.clicked.connect(lambda: self._getQuote("SO"))
self.mainForm.addRow(
self.tr("Single quote open style"), self.quoteSym["SO"],
@@ -687,10 +713,10 @@ class GuiPreferences(QDialog):
self.quoteSym["SC"] = QLineEdit(self)
self.quoteSym["SC"].setMaxLength(1)
self.quoteSym["SC"].setReadOnly(True)
self.quoteSym["SC"].setFixedWidth(qWidth)
self.quoteSym["SC"].setFixedWidth(boxFixed)
self.quoteSym["SC"].setAlignment(QtAlignCenter)
self.quoteSym["SC"].setText(CONFIG.fmtSQuoteClose)
self.btnSingleStyleC = NIconToolButton(self, iSz, "more")
self.btnSingleStyleC = NIconToolButton(self, iSz, "quote")
self.btnSingleStyleC.clicked.connect(lambda: self._getQuote("SC"))
self.mainForm.addRow(
self.tr("Single quote close style"), self.quoteSym["SC"],
@@ -702,10 +728,10 @@ class GuiPreferences(QDialog):
self.quoteSym["DO"] = QLineEdit(self)
self.quoteSym["DO"].setMaxLength(1)
self.quoteSym["DO"].setReadOnly(True)
self.quoteSym["DO"].setFixedWidth(qWidth)
self.quoteSym["DO"].setFixedWidth(boxFixed)
self.quoteSym["DO"].setAlignment(QtAlignCenter)
self.quoteSym["DO"].setText(CONFIG.fmtDQuoteOpen)
self.btnDoubleStyleO = NIconToolButton(self, iSz, "more")
self.btnDoubleStyleO = NIconToolButton(self, iSz, "quote")
self.btnDoubleStyleO.clicked.connect(lambda: self._getQuote("DO"))
self.mainForm.addRow(
self.tr("Double quote open style"), self.quoteSym["DO"],
@@ -716,10 +742,10 @@ class GuiPreferences(QDialog):
self.quoteSym["DC"] = QLineEdit(self)
self.quoteSym["DC"].setMaxLength(1)
self.quoteSym["DC"].setReadOnly(True)
self.quoteSym["DC"].setFixedWidth(qWidth)
self.quoteSym["DC"].setFixedWidth(boxFixed)
self.quoteSym["DC"].setAlignment(QtAlignCenter)
self.quoteSym["DC"].setText(CONFIG.fmtDQuoteClose)
self.btnDoubleStyleC = NIconToolButton(self, iSz, "more")
self.btnDoubleStyleC = NIconToolButton(self, iSz, "quote")
self.btnDoubleStyleC.clicked.connect(lambda: self._getQuote("DC"))
self.mainForm.addRow(
self.tr("Double quote close style"), self.quoteSym["DC"],
@@ -785,25 +811,21 @@ class GuiPreferences(QDialog):
@pyqtSlot()
def _selectGuiFont(self) -> None:
"""Open the QFontDialog and set a font for the font style."""
current = QFont()
current.setFamily(CONFIG.guiFont)
current.setPointSize(CONFIG.guiFontSize)
font, status = QFontDialog.getFont(current, self)
font, status = SHARED.getFont(self._guiFont, self.nativeFont.isChecked())
if status:
self.guiFont.setText(font.family())
self.guiFontSize.setValue(font.pointSize())
self.guiFont.setText(describeFont(font))
self.guiFont.setCursorPosition(0)
self._guiFont = font
return
@pyqtSlot()
def _selectTextFont(self) -> None:
"""Open the QFontDialog and set a font for the font style."""
current = QFont()
current.setFamily(CONFIG.textFont)
current.setPointSize(CONFIG.textSize)
font, status = QFontDialog.getFont(current, self)
font, status = SHARED.getFont(self._textFont, self.nativeFont.isChecked())
if status:
self.textFont.setText(font.family())
self.textSize.setValue(font.pointSize())
self.textFont.setText(describeFont(font))
self.textFont.setCursorPosition(0)
self._textFont = font
return
@pyqtSlot()
@@ -823,13 +845,6 @@ class GuiPreferences(QDialog):
self.askBeforeBackup.setEnabled(state)
return
@pyqtSlot(bool)
def _toggleHighlightQuotes(self, state: bool) -> None:
"""Toggle switches controlled by the highlight quotes switch."""
self.allowOpenSQuote.setEnabled(state)
self.allowOpenDQuote.setEnabled(state)
return
@pyqtSlot(bool)
def _toggleAutoReplaceMain(self, state: bool) -> None:
"""Toggle switches controlled by the auto replace switch."""
@@ -866,20 +881,17 @@ class GuiPreferences(QDialog):
# Appearance
guiLocale = self.guiLocale.currentData()
guiTheme = self.guiTheme.currentData()
guiFont = self.guiFont.text()
guiFontSize = self.guiFontSize.value()
updateTheme |= CONFIG.guiTheme != guiTheme
needsRestart |= CONFIG.guiLocale != guiLocale
needsRestart |= CONFIG.guiFont != guiFont
needsRestart |= CONFIG.guiFontSize != guiFontSize
needsRestart |= CONFIG.guiFont != self._guiFont
CONFIG.guiLocale = guiLocale
CONFIG.guiTheme = guiTheme
CONFIG.guiFont = guiFont
CONFIG.guiFontSize = guiFontSize
CONFIG.hideVScroll = self.hideVScroll.isChecked()
CONFIG.hideHScroll = self.hideHScroll.isChecked()
CONFIG.nativeFont = self.nativeFont.isChecked()
CONFIG.setGuiFont(self._guiFont)
# Document Style
guiSyntax = self.guiSyntax.currentData()
@@ -892,7 +904,7 @@ class GuiPreferences(QDialog):
CONFIG.emphLabels = emphLabels
CONFIG.showFullPath = self.showFullPath.isChecked()
CONFIG.incNotesWCount = self.incNotesWCount.isChecked()
CONFIG.setTextFont(self.textFont.text(), self.textSize.value())
CONFIG.setTextFont(self._textFont)
# Auto Save
CONFIG.autoSaveDoc = self.autoSaveDoc.value()
@@ -927,19 +939,32 @@ class GuiPreferences(QDialog):
CONFIG.scrollPastEnd = self.scrollPastEnd.isChecked()
# Text Highlighting
highlightQuotes = self.highlightQuotes.isChecked()
dialogueStyle = self.dialogStyle.currentData()
allowOpenDial = self.allowOpenDial.isChecked()
narratorBreak = self.narratorBreak.text()
dialogueLine = self.dialogLine.text()
altDialogOpen = self.altDialogOpen.text()
altDialogClose = self.altDialogClose.text()
highlightEmph = self.highlightEmph.isChecked()
showMultiSpaces = self.showMultiSpaces.isChecked()
updateSyntax |= CONFIG.highlightQuotes != highlightQuotes
updateSyntax |= CONFIG.dialogStyle != dialogueStyle
updateSyntax |= CONFIG.allowOpenDial != allowOpenDial
updateSyntax |= CONFIG.narratorBreak != narratorBreak
updateSyntax |= CONFIG.dialogLine != dialogueLine
updateSyntax |= CONFIG.altDialogOpen != altDialogOpen
updateSyntax |= CONFIG.altDialogClose != altDialogClose
updateSyntax |= CONFIG.highlightEmph != highlightEmph
updateSyntax |= CONFIG.showMultiSpaces != showMultiSpaces
CONFIG.highlightQuotes = highlightQuotes
CONFIG.dialogStyle = dialogueStyle
CONFIG.allowOpenDial = allowOpenDial
CONFIG.narratorBreak = narratorBreak
CONFIG.dialogLine = dialogueLine
CONFIG.altDialogOpen = altDialogOpen
CONFIG.altDialogClose = altDialogClose
CONFIG.highlightEmph = highlightEmph
CONFIG.showMultiSpaces = showMultiSpaces
CONFIG.allowOpenSQuote = self.allowOpenSQuote.isChecked()
CONFIG.allowOpenDQuote = self.allowOpenDQuote.isChecked()
# Text Automation
CONFIG.doReplace = self.doReplace.isChecked()
@@ -962,5 +987,3 @@ class GuiPreferences(QDialog):
self.newPreferencesReady.emit(needsRestart, refreshTree, updateTheme, updateSyntax)
return
# END Class GuiPreferences

View File

@@ -27,20 +27,26 @@ from __future__ import annotations
import logging
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
from PyQt5.QtGui import QCloseEvent, QColor
from PyQt5.QtWidgets import (
QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
QLineEdit, QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem,
QVBoxLayout, QWidget
QAbstractItemView, QApplication, QColorDialog, QDialog, QDialogButtonBox,
QHBoxLayout, QLineEdit, QMenu, QStackedWidget, QToolButton, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import simplified
from novelwriter.constants import nwLabels, trConst
from novelwriter.core.status import NWStatus, StatusEntry
from novelwriter.enum import nwStatusShape
from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
from novelwriter.extensions.modified import NComboBox, NIconToolButton
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtDialogCancel, QtDialogSave, QtUserRole
from novelwriter.types import (
QtDialogCancel, QtDialogSave, QtSizeMinimum, QtSizeMinimumExpanding,
QtUserRole
)
logger = logging.getLogger(__name__)
@@ -179,19 +185,19 @@ class GuiProjectSettings(QDialog):
rebuildTrees = False
if self.statusPage.wasChanged:
newList, delList = self.statusPage.getNewList()
project.setStatusColours(newList, delList)
if self.statusPage.changed:
logger.debug("Updating status labels")
project.data.itemStatus.update(self.statusPage.getNewList())
rebuildTrees = True
if self.importPage.wasChanged:
newList, delList = self.importPage.getNewList()
project.setImportColours(newList, delList)
if self.importPage.changed:
logger.debug("Updating importance labels")
project.data.itemImport.update(self.importPage.getNewList())
rebuildTrees = True
if self.replacePage.wasChanged:
newList = self.replacePage.getNewList()
project.data.setAutoReplace(newList)
if self.replacePage.changed:
logger.debug("Updating auto-replace settings")
project.data.setAutoReplace(self.replacePage.getNewList())
self.newProjectSettingsReady.emit(rebuildTrees)
QApplication.processEvents()
@@ -221,8 +227,6 @@ class GuiProjectSettings(QDialog):
return
# END Class GuiProjectSettings
class _SettingsPage(NScrollableForm):
@@ -296,17 +300,15 @@ class _SettingsPage(NScrollableForm):
return
# END Class _SettingsPage
class _StatusPage(NFixedPage):
COL_LABEL = 0
COL_USAGE = 1
C_DATA = 0
C_LABEL = 0
C_USAGE = 1
KEY_ROLE = QtUserRole
COL_ROLE = QtUserRole + 1
NUM_ROLE = QtUserRole + 2
D_KEY = QtUserRole
D_ENTRY = QtUserRole + 1
def __init__(self, parent: QWidget, isStatus: bool) -> None:
super().__init__(parent=parent)
@@ -325,12 +327,21 @@ class _StatusPage(NFixedPage):
)
self._changed = False
self._colDeleted = []
self._selColour = QColor(100, 100, 100)
self._color = QColor(100, 100, 100)
self._shape = nwStatusShape.SQUARE
self._icons = {}
self.iPx = SHARED.theme.baseIconHeight
self._iPx = SHARED.theme.baseIconHeight
iSz = SHARED.theme.baseIconSize
bSz = SHARED.theme.buttonIconSize
bPd = CONFIG.pxInt(4)
iColor = self.palette().text().color()
# Labels
self.trCountNone = self.tr("Not in use")
self.trCountOne = self.tr("Used once")
self.trCountMore = self.tr("Used by {0} items")
self.trSelColor = self.tr("Select Colour")
# Title
self.pageTitle = NColourLabel(
@@ -341,12 +352,14 @@ class _StatusPage(NFixedPage):
# List Box
self.listBox = QTreeWidget(self)
self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")])
self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
self.listBox.setColumnWidth(self.C_LABEL, wCol0)
self.listBox.setIndentation(0)
self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.listBox.itemSelectionChanged.connect(self._selectionChanged)
for key, entry in status.items():
self._addItem(key, entry["name"], entry["cols"], entry["count"])
for key, entry in status.iterItems():
self._addItem(key, StatusEntry.duplicate(entry))
# List Controls
self.addButton = NIconToolButton(self, iSz, "add")
@@ -367,16 +380,43 @@ class _StatusPage(NFixedPage):
self.editName.setPlaceholderText(self.tr("Select item to edit"))
self.editName.setEnabled(False)
self.colPixmap = QPixmap(self.iPx, self.iPx)
self.colPixmap.fill(QColor(100, 100, 100))
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"), self)
self.colButton.setIconSize(bSz)
self.colButton.setEnabled(False)
self.colButton.clicked.connect(self._selectColour)
buttonStyle = (
f"QToolButton {{padding: 0 {bPd}px;}} "
"QToolButton::menu-indicator {image: none;}"
)
self.saveButton = QPushButton(self.tr("Save"), self)
self.saveButton.setEnabled(False)
self.saveButton.clicked.connect(self._saveItem)
self.colorButton = NIconToolButton(self, iSz)
self.colorButton.setToolTip(self.tr("Colour"))
self.colorButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
self.colorButton.setStyleSheet(buttonStyle)
self.colorButton.setEnabled(False)
self.colorButton.clicked.connect(self._selectColour)
def buildMenu(menu: QMenu, items: dict[nwStatusShape, str]) -> None:
for shape, label in items.items():
icon = NWStatus.createIcon(self._iPx, iColor, shape)
action = menu.addAction(icon, trConst(label))
action.triggered.connect(lambda _, shape=shape: self._selectShape(shape))
self._icons[shape] = icon
self.shapeMenu = QMenu(self)
buildMenu(self.shapeMenu, nwLabels.SHAPES_PLAIN)
buildMenu(self.shapeMenu.addMenu(self.tr("Circles ...")), nwLabels.SHAPES_CIRCLE)
buildMenu(self.shapeMenu.addMenu(self.tr("Bars ...")), nwLabels.SHAPES_BARS)
buildMenu(self.shapeMenu.addMenu(self.tr("Blocks ...")), nwLabels.SHAPES_BLOCKS)
self.shapeButton = NIconToolButton(self, iSz)
self.shapeButton.setMenu(self.shapeMenu)
self.shapeButton.setToolTip(self.tr("Shape"))
self.shapeButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
self.shapeButton.setStyleSheet(buttonStyle)
self.shapeButton.setEnabled(False)
self.applyButton = QToolButton(self)
self.applyButton.setText(self.tr("Apply"))
self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
self.applyButton.setEnabled(False)
self.applyButton.clicked.connect(self._applyChanges)
# Assemble
self.listControls = QVBoxLayout()
@@ -387,28 +427,30 @@ class _StatusPage(NFixedPage):
self.listControls.addStretch(1)
self.editBox = QHBoxLayout()
self.editBox.addWidget(self.editName)
self.editBox.addWidget(self.colButton)
self.editBox.addWidget(self.saveButton)
self.editBox.addWidget(self.editName, 1)
self.editBox.addWidget(self.colorButton, 0)
self.editBox.addWidget(self.shapeButton, 0)
self.editBox.addWidget(self.applyButton, 0)
self.mainBox = QVBoxLayout()
self.mainBox.addWidget(self.listBox)
self.mainBox.addLayout(self.editBox)
self.mainBox.addWidget(self.listBox, 1)
self.mainBox.addLayout(self.editBox, 0)
self.innerBox = QHBoxLayout()
self.innerBox.addLayout(self.mainBox)
self.innerBox.addLayout(self.listControls)
self.innerBox.addLayout(self.mainBox, 1)
self.innerBox.addLayout(self.listControls, 0)
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(self.pageTitle)
self.outerBox.addLayout(self.innerBox)
self.outerBox.addWidget(self.pageTitle, 0)
self.outerBox.addLayout(self.innerBox, 1)
self.setCentralLayout(self.outerBox)
self._setButtonIcons()
return
@property
def wasChanged(self) -> bool:
def changed(self) -> bool:
"""The user changed these settings."""
return self._changed
@@ -416,20 +458,17 @@ class _StatusPage(NFixedPage):
# Methods
##
def getNewList(self) -> tuple[list, list]:
def getNewList(self) -> list[tuple[str | None, StatusEntry]]:
"""Return list of entries."""
if self._changed:
newList = []
update = []
for n in range(self.listBox.topLevelItemCount()):
item = self.listBox.topLevelItem(n)
if item is not None:
newList.append({
"key": item.data(self.COL_LABEL, self.KEY_ROLE),
"name": item.text(self.COL_LABEL),
"cols": item.data(self.COL_LABEL, self.COL_ROLE),
})
return newList, self._colDeleted
return [], []
if item := self.listBox.topLevelItem(n):
key = item.data(self.C_DATA, self.D_KEY)
entry = item.data(self.C_DATA, self.D_ENTRY)
update.append((key, entry))
return update
return []
def columnWidth(self) -> int:
"""Return the size of the header column."""
@@ -442,124 +481,119 @@ class _StatusPage(NFixedPage):
@pyqtSlot()
def _selectColour(self) -> None:
"""Open a dialog to select the status icon colour."""
if self._selColour is not None:
newCol = QColorDialog.getColor(
self._selColour, self, self.tr("Select Colour")
)
if newCol.isValid():
self._selColour = newCol
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(newCol)
self.colButton.setIcon(QIcon(pixmap))
self.colButton.setIconSize(pixmap.rect().size())
if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid():
self._color = color
self._setButtonIcons()
return
@pyqtSlot()
def _newItem(self) -> None:
"""Create a new status item."""
self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
color = QColor(100, 100, 100)
shape = nwStatusShape.SQUARE
icon = NWStatus.createIcon(self._iPx, color, shape)
self._addItem(None, StatusEntry(self.tr("New Item"), color, shape, icon, 0))
self._changed = True
return
@pyqtSlot()
def _delItem(self) -> None:
"""Delete a status item."""
selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem):
iRow = self.listBox.indexOfTopLevelItem(selItem)
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
if item := self._getSelectedItem():
iRow = self.listBox.indexOfTopLevelItem(item)
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
if entry.count > 0:
SHARED.error(self.tr("Cannot delete a status item that is in use."))
else:
self.listBox.takeTopLevelItem(iRow)
self._colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
self._changed = True
return
@pyqtSlot()
def _saveItem(self) -> None:
def _applyChanges(self) -> None:
"""Save changes made to a status item."""
selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem):
selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
selItem.setIcon(self.COL_LABEL, self.colButton.icon())
selItem.setData(self.COL_LABEL, self.COL_ROLE, (
self._selColour.red(), self._selColour.green(), self._selColour.blue()
))
if item := self._getSelectedItem():
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
name = simplified(self.editName.text())
icon = NWStatus.createIcon(self._iPx, self._color, self._shape)
entry.name = name
entry.color = self._color
entry.shape = self._shape
entry.icon = icon
item.setText(self.C_LABEL, name)
item.setIcon(self.C_LABEL, icon)
self._changed = True
return
@pyqtSlot()
def _selectedItem(self) -> None:
def _selectionChanged(self) -> None:
"""Extract the info of a selected item and populate the settings
boxes and button. If no item is selected, clear the form.
"""
selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem):
cols = selItem.data(self.COL_LABEL, self.COL_ROLE)
name = selItem.text(self.COL_LABEL)
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(*cols))
self._selColour = QColor(*cols)
self.editName.setText(name)
self.colButton.setIcon(QIcon(pixmap))
if item := self._getSelectedItem():
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
self._color = entry.color
self._shape = entry.shape
self._setButtonIcons()
self.editName.setText(entry.name)
self.editName.selectAll()
self.editName.setFocus()
self.editName.setEnabled(True)
self.colButton.setEnabled(True)
self.saveButton.setEnabled(True)
self.colorButton.setEnabled(True)
self.shapeButton.setEnabled(True)
self.applyButton.setEnabled(True)
else:
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(100, 100, 100))
self._selColour = QColor(100, 100, 100)
self._color = QColor(100, 100, 100)
self._shape = nwStatusShape.SQUARE
self._setButtonIcons()
self.editName.setText("")
self.colButton.setIcon(QIcon(pixmap))
self.editName.setEnabled(False)
self.colButton.setEnabled(False)
self.saveButton.setEnabled(False)
self.colorButton.setEnabled(False)
self.shapeButton.setEnabled(False)
self.applyButton.setEnabled(False)
return
##
# Internal Functions
##
def _addItem(self, key: str | None, name: str,
colour: tuple[int, int, int], count: int) -> None:
def _selectShape(self, shape: nwStatusShape) -> None:
"""Set the current shape."""
self._shape = shape
self._setButtonIcons()
return
def _addItem(self, key: str | None, entry: StatusEntry) -> None:
"""Add a status item to the list."""
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(*colour))
item = QTreeWidgetItem()
item.setText(self.COL_LABEL, name)
item.setIcon(self.COL_LABEL, QIcon(pixmap))
item.setData(self.COL_LABEL, self.KEY_ROLE, key)
item.setData(self.COL_LABEL, self.COL_ROLE, colour)
item.setData(self.COL_LABEL, self.NUM_ROLE, count)
item.setText(self.COL_USAGE, self._usageString(count))
item.setText(self.C_LABEL, entry.name)
item.setIcon(self.C_LABEL, entry.icon)
item.setText(self.C_USAGE, self._usageString(entry.count))
item.setData(self.C_DATA, self.D_KEY, key)
item.setData(self.C_DATA, self.D_ENTRY, entry)
self.listBox.addTopLevelItem(item)
return
def _moveItem(self, step: int) -> None:
"""Move and item up or down step."""
selItem = self._getSelectedItem()
if selItem is None:
return
tIndex = self.listBox.indexOfTopLevelItem(selItem)
nChild = self.listBox.topLevelItemCount()
nIndex = tIndex + step
if nIndex < 0 or nIndex >= nChild:
return
cItem = self.listBox.takeTopLevelItem(tIndex)
self.listBox.insertTopLevelItem(nIndex, cItem)
self.listBox.clearSelection()
if cItem is not None:
cItem.setSelected(True)
self._changed = True
if item := self._getSelectedItem():
tIdx = self.listBox.indexOfTopLevelItem(item)
nItm = self.listBox.topLevelItemCount()
nIdx = tIdx + step
if (0 <= nIdx < nItm) and (cItem := self.listBox.takeTopLevelItem(tIdx)):
self.listBox.insertTopLevelItem(nIdx, cItem)
self.listBox.clearSelection()
cItem.setSelected(True)
self._changed = True
return
def _getSelectedItem(self) -> QTreeWidgetItem | None:
@@ -571,19 +605,24 @@ class _StatusPage(NFixedPage):
def _usageString(self, count: int) -> str:
"""Generate usage string."""
if count == 0:
return self.tr("Not in use")
return self.trCountNone
elif count == 1:
return self.tr("Used once")
return self.trCountOne
else:
return self.tr("Used by {0} items").format(count)
return self.trCountMore.format(count)
# END Class _StatusPage
def _setButtonIcons(self) -> None:
"""Set the colour of the colour button."""
icon = NWStatus.createIcon(self._iPx, self._color, nwStatusShape.SQUARE)
self.colorButton.setIcon(icon)
self.shapeButton.setIcon(self._icons[self._shape])
return
class _ReplacePage(NFixedPage):
COL_KEY = 0
COL_REPL = 1
C_KEY = 0
C_REPL = 1
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -605,15 +644,17 @@ class _ReplacePage(NFixedPage):
# List Box
self.listBox = QTreeWidget(self)
self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")])
self.listBox.setColumnWidth(self.COL_KEY, wCol0)
self.listBox.setColumnWidth(self.C_KEY, wCol0)
self.listBox.setIndentation(0)
self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.listBox.itemSelectionChanged.connect(self._selectionChanged)
for aKey, aVal in SHARED.project.data.autoReplace.items():
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
self.listBox.addTopLevelItem(newItem)
self.listBox.sortByColumn(self.COL_KEY, Qt.SortOrder.AscendingOrder)
self.listBox.sortByColumn(self.C_KEY, Qt.SortOrder.AscendingOrder)
self.listBox.setSortingEnabled(True)
# List Controls
@@ -633,8 +674,10 @@ class _ReplacePage(NFixedPage):
self.editValue.setEnabled(False)
self.editValue.setMaxLength(80)
self.saveButton = QPushButton(self.tr("Save"), self)
self.saveButton.clicked.connect(self._saveEntry)
self.applyButton = QToolButton(self)
self.applyButton.setText(self.tr("Apply"))
self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
self.applyButton.clicked.connect(self._applyChanges)
# Assemble
self.listControls = QVBoxLayout()
@@ -645,11 +688,11 @@ class _ReplacePage(NFixedPage):
self.editBox = QHBoxLayout()
self.editBox.addWidget(self.editKey, 4)
self.editBox.addWidget(self.editValue, 5)
self.editBox.addWidget(self.saveButton, 0)
self.editBox.addWidget(self.applyButton, 0)
self.mainBox = QVBoxLayout()
self.mainBox.addWidget(self.listBox)
self.mainBox.addLayout(self.editBox)
self.mainBox.addWidget(self.listBox, 1)
self.mainBox.addLayout(self.editBox, 0)
self.innerBox = QHBoxLayout()
self.innerBox.addLayout(self.mainBox)
@@ -664,7 +707,7 @@ class _ReplacePage(NFixedPage):
return
@property
def wasChanged(self) -> bool:
def changed(self) -> bool:
"""The user changed these settings."""
return self._changed
@@ -672,15 +715,13 @@ class _ReplacePage(NFixedPage):
# Methods
##
def getNewList(self) -> dict:
def getNewList(self) -> dict[str, str]:
"""Extract the list from the widget."""
new = {}
for n in range(self.listBox.topLevelItemCount()):
if tItem := self.listBox.topLevelItem(n):
aKey = self._stripNotAllowed(tItem.text(0))
aVal = tItem.text(1)
if len(aKey) > 0:
new[aKey] = aVal
if item := self.listBox.topLevelItem(n):
if key := self._stripNotAllowed(item.text(self.C_KEY)):
new[key] = item.text(self.C_REPL)
return new
def columnWidth(self) -> int:
@@ -692,51 +733,48 @@ class _ReplacePage(NFixedPage):
##
@pyqtSlot()
def _selectedItem(self) -> None:
def _selectionChanged(self) -> None:
"""Extract the details from the selected item and populate the
edit form.
"""
if selItem := self._getSelectedItem():
editKey = self._stripNotAllowed(selItem.text(0))
editVal = selItem.text(1)
self.editKey.setText(editKey)
self.editValue.setText(editVal)
if item := self._getSelectedItem():
self.editKey.setText(self._stripNotAllowed(item.text(self.C_KEY)))
self.editValue.setText(item.text(self.C_REPL))
self.editKey.setEnabled(True)
self.editValue.setEnabled(True)
self.editKey.selectAll()
self.editKey.setFocus()
else:
self.editKey.setText("")
self.editValue.setText("")
self.editKey.setEnabled(False)
self.editValue.setEnabled(False)
return
@pyqtSlot()
def _saveEntry(self) -> None:
def _applyChanges(self) -> None:
"""Save the form data into the list widget."""
if selItem := self._getSelectedItem():
newKey = self.editKey.text()
newVal = self.editValue.text()
saveKey = self._stripNotAllowed(newKey)
if len(saveKey) > 0 and len(newVal) > 0:
selItem.setText(self.COL_KEY, "<%s>" % saveKey)
selItem.setText(self.COL_REPL, newVal)
self.editKey.clear()
self.editValue.clear()
self.editKey.setEnabled(False)
self.editValue.setEnabled(False)
self.listBox.clearSelection()
if item := self._getSelectedItem():
key = self._stripNotAllowed(self.editKey.text())
value = self.editValue.text()
if key and value:
item.setText(self.C_KEY, f"<{key}>")
item.setText(self.C_REPL, value)
self._changed = True
return
@pyqtSlot()
def _addEntry(self) -> None:
"""Add a new list entry."""
saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1)
self.listBox.addTopLevelItem(QTreeWidgetItem([saveKey, ""]))
key = f"<keyword{self.listBox.topLevelItemCount() + 1:d}>"
self.listBox.addTopLevelItem(QTreeWidgetItem([key, ""]))
return
@pyqtSlot()
def _delEntry(self) -> None:
"""Delete the selected entry."""
if selItem := self._getSelectedItem():
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
if item := self._getSelectedItem():
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(item))
self._changed = True
return
@@ -753,5 +791,3 @@ class _ReplacePage(NFixedPage):
def _stripNotAllowed(self, key: str) -> str:
"""Clean up the replace key string."""
return "".join(c for c in key if c.isalnum())
# END Class _ReplacePage

View File

@@ -25,15 +25,15 @@ from __future__ import annotations
import logging
from PyQt5.QtGui import QFontMetrics
from PyQt5.QtCore import QSize, pyqtSlot
from PyQt5.QtGui import QFontMetrics
from PyQt5.QtWidgets import (
QDialog, QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
QListWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG
from novelwriter.constants import trConst, nwQuotes
from novelwriter.constants import nwQuotes, trConst
from novelwriter.types import QtAlignCenter, QtAlignTop, QtDialogCancel, QtDialogOk, QtUserRole
logger = logging.getLogger(__name__)
@@ -50,6 +50,7 @@ class GuiQuoteSelect(QDialog):
logger.debug("Create: GuiQuoteSelect")
self.setObjectName("GuiQuoteSelect")
self.setWindowTitle(self.tr("Select Quote Style"))
self.outerBox = QVBoxLayout()
self.innerBox = QHBoxLayout()
@@ -141,5 +142,3 @@ class GuiQuoteSelect(QDialog):
self.previewLabel.setText(quote)
self._selected = quote
return
# END Class GuiQuoteSelect

View File

@@ -25,14 +25,13 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from pathlib import Path
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import (
QAbstractItemView, QApplication, QDialog, QDialogButtonBox, QFileDialog,
QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout
QHBoxLayout, QLineEdit, QListWidget, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
@@ -42,9 +41,6 @@ from novelwriter.extensions.configlayout import NColourLabel
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.types import QtDialogClose, QtDialogSave
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__)
@@ -52,8 +48,8 @@ class GuiWordList(QDialog):
newWordListReady = pyqtSignal()
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
logger.debug("Create: GuiWordList")
self.setObjectName("GuiWordList")
@@ -257,5 +253,3 @@ class GuiWordList(QDialog):
if (item := self.listBox.item(i)) and (word := item.text().strip()):
result.append(word)
return result
# END Class GuiWordList

View File

@@ -32,8 +32,6 @@ class nwItemType(Enum):
FOLDER = 2
FILE = 3
# END Enum nwItemType
class nwItemClass(Enum):
@@ -50,8 +48,6 @@ class nwItemClass(Enum):
TEMPLATE = 10
TRASH = 11
# END Enum nwItemClass
class nwItemLayout(Enum):
@@ -59,16 +55,17 @@ class nwItemLayout(Enum):
DOCUMENT = 1
NOTE = 2
# END Enum nwItemLayout
class nwComment(Enum):
PLAIN = 0
SYNOPSIS = 1
SHORT = 2
# END Enum nwComment
IGNORE = 1
SYNOPSIS = 2
SHORT = 3
NOTE = 4
FOOTNOTE = 5
COMMENT = 6
STORY = 7
class nwTrinary(Enum):
@@ -77,16 +74,12 @@ class nwTrinary(Enum):
NEUTRAL = 0
POSITIVE = 1
# END Enum nwTrinary
class nwDocMode(Enum):
VIEW = 0
EDIT = 1
# END Enum nwDocMode
class nwDocAction(Enum):
@@ -129,8 +122,6 @@ class nwDocAction(Enum):
SC_SUP = 36
SC_SUB = 37
# END Enum nwDocAction
class nwDocInsert(Enum):
@@ -145,8 +136,7 @@ class nwDocInsert(Enum):
VSPACE_S = 8
VSPACE_M = 9
LIPSUM = 10
# END Enum nwDocInsert
FOOTNOTE = 11
class nwView(Enum):
@@ -157,8 +147,6 @@ class nwView(Enum):
OUTLINE = 3
SEARCH = 4
# END Enum nwView
class nwWidget(Enum):
@@ -167,8 +155,6 @@ class nwWidget(Enum):
VIEWER = 3
OUTLINE = 4
# END Enum nwWidget
class nwOutline(Enum):
@@ -190,8 +176,6 @@ class nwOutline(Enum):
CUSTOM = 15
SYNOP = 16
# END Enum nwOutline
class nwBuildFmt(Enum):
@@ -204,4 +188,26 @@ class nwBuildFmt(Enum):
J_HTML = 6
J_NWD = 7
# END Enum nwBuildFormat
class nwStatusShape(Enum):
SQUARE = 0
TRIANGLE = 1
NABLA = 2
DIAMOND = 3
PENTAGON = 4
HEXAGON = 5
STAR = 6
PACMAN = 7
CIRCLE_Q = 8
CIRCLE_H = 9
CIRCLE_T = 10
CIRCLE = 11
BARS_1 = 12
BARS_2 = 13
BARS_3 = 14
BARS_4 = 15
BLOCK_1 = 16
BLOCK_2 = 17
BLOCK_3 = 18
BLOCK_4 = 19

View File

@@ -23,17 +23,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import sys
import random
import logging
import random
import sys
from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QFont, QFontDatabase
from PyQt5.QtWidgets import (
QApplication, QWidget, QDialog, QGridLayout, QStyle, QPlainTextEdit,
QLabel, QDialogButtonBox
QApplication, QDialog, QDialogButtonBox, QGridLayout, QLabel,
QPlainTextEdit, QStyle, QWidget
)
if TYPE_CHECKING: # pragma: no cover
@@ -42,10 +42,6 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__)
# =============================================================================================== #
# Utility Functions
# =============================================================================================== #
def logException() -> None:
"""Log the content of an exception message."""
exType, exValue, _ = sys.exc_info()
@@ -61,10 +57,6 @@ def formatException(exc: BaseException) -> str:
return f"{type(exc).__name__}: {str(exc)}"
# =============================================================================================== #
# Error Handler
# =============================================================================================== #
class NWErrorMessage(QDialog):
def __init__(self, parent: QWidget) -> None:
@@ -125,9 +117,11 @@ class NWErrorMessage(QDialog):
error traceback.
"""
from traceback import format_tb
from PyQt5.QtCore import PYQT_VERSION_STR, QT_VERSION_STR, QSysInfo
from novelwriter import __version__
from novelwriter.constants import nwConst
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QSysInfo
self.msgHead.setText(
"<p>An unhandled error has been encountered.</p>"
@@ -175,12 +169,11 @@ class NWErrorMessage(QDialog):
self.close()
return
# END Class NWErrorMessage
def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackType) -> None:
"""Function to catch unhandled global exceptions."""
from traceback import print_tb
from PyQt5.QtWidgets import QApplication
logger.critical("%s: %s", exType.__name__, str(exValue))

View File

@@ -26,11 +26,12 @@ from __future__ import annotations
from math import ceil
from PyQt5.QtCore import QRect
from PyQt5.QtGui import QBrush, QColor, QPaintEvent, QPainter, QPen
from PyQt5.QtWidgets import QProgressBar, QSizePolicy, QWidget
from PyQt5.QtGui import QBrush, QColor, QPainter, QPaintEvent, QPen
from PyQt5.QtWidgets import QProgressBar, QWidget
from novelwriter.types import (
QtPaintAnitAlias, QtAlignCenter, QtRoundCap, QtSolidLine, QtTransparent
QtAlignCenter, QtPaintAnitAlias, QtRoundCap, QtSizeFixed, QtSolidLine,
QtTransparent
)
@@ -59,7 +60,7 @@ class NProgressCircle(QProgressBar):
bar=self.palette().highlight().color(),
text=self.palette().text().color()
)
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
self.setSizePolicy(QtSizeFixed, QtSizeFixed)
self.setFixedWidth(size)
self.setFixedHeight(size)
return
@@ -100,5 +101,3 @@ class NProgressCircle(QProgressBar):
painter.setPen(self._tColor)
painter.drawText(self._cRect, QtAlignCenter, self._text or f"{progress:.1f} %")
return
# END Class NProgressCircle

View File

@@ -27,8 +27,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
from PyQt5.QtGui import QColor, QFont, QPalette
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QFont, QPalette
from PyQt5.QtWidgets import (
QAbstractButton, QFrame, QHBoxLayout, QLabel, QLayout, QScrollArea,
QVBoxLayout, QWidget
@@ -64,8 +64,6 @@ class NFixedPage(QFrame):
self.setLayout(layout)
return
# END Class NFixedPage
class NScrollablePage(QScrollArea):
"""Extension: Scrollable Page Widget
@@ -89,8 +87,6 @@ class NScrollablePage(QScrollArea):
self._widget.setLayout(layout)
return
# END Class NScrollablePage
class NScrollableForm(QScrollArea):
"""Extension: Scrollable Form Widget
@@ -184,16 +180,26 @@ class NScrollableForm(QScrollArea):
self._sections[identifier] = qLabel
return
def addRow(self, label: str, widget: QWidget, helpText: str = "", unit: str | None = None,
button: QWidget | None = None, editable: str | None = None,
def addRow(self, label: str, widget: QWidget | list[QWidget], helpText: str = "",
unit: str | None = None, button: QWidget | None = None, editable: str | None = None,
stretch: tuple[int, int] = (1, 0)) -> None:
"""Add a label and a widget as a new row of the form."""
row = QHBoxLayout()
row.setSpacing(CONFIG.pxInt(12))
if isinstance(widget, list):
wBox = QHBoxLayout()
wBox.setContentsMargins(0, 0, 0, 0)
for item in widget:
wBox.addWidget(item)
qWidget = QWidget(self)
qWidget.setLayout(wBox)
else:
qWidget = widget
qLabel = QLabel(label, self)
qLabel.setIndent(self._indent)
qLabel.setBuddy(widget)
qLabel.setBuddy(qWidget)
if helpText:
qHelp = NColourLabel(
@@ -212,19 +218,19 @@ class NScrollableForm(QScrollArea):
if isinstance(unit, str):
box = QHBoxLayout()
box.addWidget(widget, 1)
box.addWidget(qWidget, 1)
box.addWidget(QLabel(unit, self), 0)
row.addLayout(box, stretch[1])
elif isinstance(button, QAbstractButton):
box = QHBoxLayout()
box.addWidget(widget, 1)
box.addWidget(qWidget, 1)
box.addWidget(button, 0)
row.addLayout(box, stretch[1])
else:
row.addWidget(widget, stretch[1])
row.addWidget(qWidget, stretch[1])
self._layout.addLayout(row)
self._index[label.strip()] = widget
self._index[label.strip()] = qWidget
self._first = False
return
@@ -235,8 +241,6 @@ class NScrollableForm(QScrollArea):
self._layout.addStretch(1)
return
# END Class NScrollableForm
class NColourLabel(QLabel):
"""Extension: A Coloured Label
@@ -267,8 +271,6 @@ class NColourLabel(QLabel):
return
# END Class NColourLabel
class NWrappedWidgetBox(QHBoxLayout):
"""Extension: A Text-Wrapped Widget Box
@@ -287,5 +289,3 @@ class NWrappedWidgetBox(QHBoxLayout):
if after:
self.addWidget(QLabel(after.lstrip()))
return
# END Class NWrappedWidgetBox

View File

@@ -24,8 +24,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
from PyQt5.QtGui import QStatusTipEvent, QWheelEvent
from PyQt5.QtCore import QEvent, QObject
from PyQt5.QtGui import QStatusTipEvent, QWheelEvent
from PyQt5.QtWidgets import QWidget
@@ -63,13 +63,9 @@ class WheelEventFilter(QObject):
self._locked = False
return False
# END Class WheelEventFilter
class StatusTipFilter(QObject):
def eventFilter(self, obj: QObject, event: QEvent) -> bool:
"""Filter out status tip events on menus."""
return True if isinstance(event, QStatusTipEvent) else super().eventFilter(obj, event)
# END Class StatusTipFilter

View File

@@ -6,6 +6,8 @@ File History:
Created: 2024-02-01 [2.3b1] NComboBox
Created: 2024-02-01 [2.3b1] NSpinBox
Created: 2024-02-01 [2.3b1] NDoubleSpinBox
Created: 2024-05-01 [2.5b1] NToolDialog
Created: 2024-05-01 [2.5b1] NNonBlockingDialog
This file is a part of novelWriter
Copyright 2018–2024, Veronica Berglyd Olsen
@@ -25,11 +27,56 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QWheelEvent
from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QToolButton, QWidget
from PyQt5.QtWidgets import (
QApplication, QComboBox, QDialog, QDoubleSpinBox, QSpinBox, QToolButton,
QWidget
)
from novelwriter import SHARED
from novelwriter import CONFIG, SHARED
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
class NToolDialog(QDialog):
def __init__(self, parent: GuiMain) -> None:
super().__init__(parent=parent)
self.setModal(False)
if CONFIG.osDarwin:
self.setWindowFlag(Qt.WindowType.Tool)
return
def activateDialog(self) -> None:
"""Helper function to activate dialog on various systems."""
self.show()
if CONFIG.osWindows:
self.activateWindow()
self.raise_()
QApplication.processEvents()
return
class NNonBlockingDialog(QDialog):
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent=parent)
self.setModal(True)
return
def activateDialog(self) -> None:
"""Helper function to activate dialog on various systems."""
self.show()
if CONFIG.osWindows:
self.activateWindow()
self.raise_()
QApplication.processEvents()
return
class NComboBox(QComboBox):
@@ -46,14 +93,12 @@ class NComboBox(QComboBox):
event.ignore()
return
def setCurrentData(self, data: str, default: str) -> None:
def setCurrentData(self, data: str | int | Enum, default: str | int | Enum) -> None:
"""Set the current index from data, with a fallback."""
idx = self.findData(data)
self.setCurrentIndex(self.findData(default) if idx < 0 else idx)
return
# END Class NComboBox
class NSpinBox(QSpinBox):
@@ -69,8 +114,6 @@ class NSpinBox(QSpinBox):
event.ignore()
return
# END Class NSpinBox
class NDoubleSpinBox(QDoubleSpinBox):
@@ -86,8 +129,6 @@ class NDoubleSpinBox(QDoubleSpinBox):
event.ignore()
return
# END Class NDoubleSpinBox
class NIconToolButton(QToolButton):
@@ -105,8 +146,6 @@ class NIconToolButton(QToolButton):
self.setIcon(SHARED.theme.getIcon(iconKey))
return
# END Class NIconToolButton
class NIconToggleButton(QToolButton):
@@ -126,5 +165,3 @@ class NIconToggleButton(QToolButton):
iconSize = self.iconSize()
self.setIcon(SHARED.theme.getToggleIcon(iconKey, (iconSize.width(), iconSize.height())))
return
# END Class NUnfoldButton

View File

@@ -29,8 +29,8 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QComboBox, QWidget
from novelwriter import SHARED
from novelwriter.enum import nwItemClass
from novelwriter.constants import nwLabels
from novelwriter.enum import nwItemClass
logger = logging.getLogger(__name__)
@@ -126,5 +126,3 @@ class NovelSelector(QComboBox):
if not self._blockSignal:
self.novelSelectionChanged.emit(self.currentData())
return
# END Class NovelSelector

View File

@@ -25,14 +25,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
from PyQt5.QtGui import QColor, QPaintEvent, QPainter, QPolygon
from PyQt5.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QColor, QPainter, QPaintEvent, QPolygon
from PyQt5.QtWidgets import (
QAbstractButton, QAction, QButtonGroup, QLabel, QSizePolicy, QStyle,
QAbstractButton, QAction, QButtonGroup, QLabel, QStyle,
QStyleOptionToolButton, QToolBar, QToolButton, QWidget
)
from novelwriter.types import QtPaintAnitAlias, QtAlignLeft, QtMouseOver, QtNoBrush, QtNoPen
from novelwriter.types import (
QtAlignLeft, QtMouseOver, QtNoBrush, QtNoPen, QtPaintAnitAlias,
QtSizeExpanding, QtSizeFixed
)
class NPagedSideBar(QToolBar):
@@ -59,7 +62,7 @@ class NPagedSideBar(QToolBar):
self.setOrientation(Qt.Orientation.Vertical)
stretch = QWidget(self)
stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
self._stretchAction = self.addWidget(stretch)
return
@@ -109,8 +112,6 @@ class NPagedSideBar(QToolBar):
self.buttonClicked.emit(buttonId)
return
# END Class NPagedSideBar
class _NPagedToolButton(QToolButton):
@@ -119,7 +120,7 @@ class _NPagedToolButton(QToolButton):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.setSizePolicy(QtSizeExpanding, QtSizeFixed)
self.setCheckable(True)
fH = self.fontMetrics().height()
@@ -187,8 +188,6 @@ class _NPagedToolButton(QToolButton):
return
# END Class _NPagedToolButton
class _NPagedToolLabel(QLabel):
@@ -197,7 +196,7 @@ class _NPagedToolLabel(QLabel):
def __init__(self, parent: QWidget, textColor: QColor | None = None) -> None:
super().__init__(parent=parent)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.setSizePolicy(QtSizeExpanding, QtSizeFixed)
fH = self.fontMetrics().height()
self._bH = round(fH * 1.7)
@@ -228,5 +227,3 @@ class _NPagedToolLabel(QLabel):
paint.drawText(QRectF(self._lM, self._tM, tW, tH), QtAlignLeft, self.text())
return
# END Class _NPagedToolLabel

View File

@@ -25,7 +25,7 @@ from __future__ import annotations
from math import ceil
from PyQt5.QtGui import QPaintEvent, QPainter
from PyQt5.QtGui import QPainter, QPaintEvent
from PyQt5.QtWidgets import QProgressBar, QWidget
from novelwriter.types import QtPaintAnitAlias
@@ -51,5 +51,3 @@ class NProgressSimple(QProgressBar):
painter.setBrush(self.palette().highlight())
painter.drawRect(0, 0, progress, self.height())
return
# END Class NProgressSimple

View File

@@ -27,7 +27,7 @@ import logging
from typing import Literal
from PyQt5.QtGui import QColor, QPaintEvent, QPainter
from PyQt5.QtGui import QColor, QPainter, QPaintEvent
from PyQt5.QtWidgets import QAbstractButton, QWidget
from novelwriter.types import QtPaintAnitAlias
@@ -75,5 +75,3 @@ class StatusLED(QAbstractButton):
painter.setOpacity(1.0)
painter.drawEllipse(1, 1, self.width() - 2, self.height() - 2)
return
# END Class StatusLED

View File

@@ -23,12 +23,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent
from PyQt5.QtCore import QEvent, QPropertyAnimation, Qt, pyqtProperty
from PyQt5.QtWidgets import QAbstractButton, QSizePolicy, QWidget
from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent
from PyQt5.QtWidgets import QAbstractButton, QWidget
from novelwriter import CONFIG, SHARED
from novelwriter.types import QtPaintAnitAlias, QtMouseLeft, QtNoPen
from novelwriter.types import QtMouseLeft, QtNoPen, QtPaintAnitAlias, QtSizeFixed
class NSwitch(QAbstractButton):
@@ -46,7 +46,7 @@ class NSwitch(QAbstractButton):
self._rR = self._xR - self._rB
self.setCheckable(True)
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
self.setSizePolicy(QtSizeFixed, QtSizeFixed)
self.setFixedWidth(self._xW)
self.setFixedHeight(self._xH)
self._offset = self._xR
@@ -133,5 +133,3 @@ class NSwitch(QAbstractButton):
self.setCursor(Qt.CursorShape.PointingHandCursor)
super().enterEvent(event)
return
# END Class NSwitch

View File

@@ -23,12 +23,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QSizePolicy, QWidget
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QWidget
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtAlignLeft, QtAlignRight, QtAlignRightMiddle
from novelwriter.types import (
QtAlignLeft, QtAlignRight, QtAlignRightMiddle, QtSizeMinimum,
QtSizeMinimumExpanding
)
class NSwitchBox(QScrollArea):
@@ -59,7 +62,7 @@ class NSwitchBox(QScrollArea):
self._content.setColumnStretch(1, 1)
self._widget = QWidget(self)
self._widget.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Minimum)
self._widget.setSizePolicy(QtSizeMinimumExpanding, QtSizeMinimum)
self._widget.setLayout(self._content)
self.setWidgetResizable(True)
@@ -124,5 +127,3 @@ class NSwitchBox(QScrollArea):
self._content.setRowStretch(self._index + 1, 1)
self._index += 1
return
# END Class NSwitchBox

Some files were not shown because too many files have changed in this diff Show More